Páginas

quinta-feira, 5 de maio de 2011

Mamães e SQL injection

Caro leitor, este é meu primeiro "post", então estou um pouco nervoso.
Eu espero que vocês sejam gentis comigo nesta primeira vez.
Até pensei em pagar um jantar romântico ou um cinema antes, mas como estou meio duro vamos logo ao que interessa.

Sem mais delongas, já vou começando com uma inserção fatal... e uma mãe louca.


O problema

Certamente nós "garotos de programa" sempre estamos a mercê das loucuras que mães de filhos infelizes podem nos trazer, apesar de normalmente o problema maior ser os "filhos das mães". Bom sou fã do XKCD blog e outro dia me matei de rir com uma tirinha deles. Enviei esta tirinha para meus colegas DBAs e muitos deles, ou não entenderam, ou não tem senso de humor, o que me deixou muito decepcionado, então resolvi escrever sobre ela.






  
Entendeu a piada?

Caso tenha entendido, ou não da a mínima, vá ver outra bobagem na internet ou volte pro Facebook/Orkut e não perca seu tempo com o que escrevi abaixo, porque a coisa agora vai realmente ficar séria.

Caso você não tenha entendido, então você precisa saber que no mundo existem mães que conhecem o problema do SQL Injection, e elas sabem que muitos desenvolvedores como você ainda não conhecem este tipo de falha.


Comecemos com o papo NERD pra valer.



Análise do problema:

SQL injection é uma falha muito comum em aplicações que utilizam bancos de dados, que permite que um código malicioso seja inserido em um banco de dados, e assim permitindo tanto que dados sejam apagados, ou consultados, ou até mesmo que a peformance do banco seja posta em risco causando o chamado "denial-of-service" (negação de serviço).


Tenho visto códigos que permitem este tipo de ataque aos montes ao longo da minha carreira então acho que é mais do que meu dever ajudar meus amigos analistas a entender este tipo de problema.

Apesar de toda informação disponível na internet, ainda existem inúmeros desenvolvedores que insistem em somente concatenar a entrada de suas aplicações com suas chamadas SQL (SQL statements), tanto para DQL (select) como para DML (insert, delete e update). Possibilitando que se "insira o código malicioso", por isto chamamos este tipo de ataque de SQL injection, ou também, SQL insertion.

Então vamos ver como isto ocorre. Vou ilustrar aqui apenas o erro conhecido por filtro incorreto de caracteres de escape, tomando como exemplo o ataque da mamãe do pequeno Bobby Tables:

1) O desenvolvedor implementa da seguinte forma a simples inserção de um aluno:

statement = "insert into STUDENTS(student_name) values ('" + varStudentName  + "');"

2) Ao adicionar o nosso pequeno Bobby Tables no sistema da escola a secretária ingenuamente insere o nome do menino na aplicação, assim a variável varStudentName é preenchida:

varStudentName = "Robert'); drop table STUDENTS;--"

3)  Durante a execução do insert temos o caos:

statement = "insert into STUDENTS(student_name) values ('Robert'); drop table STUDENTS;--');"

Veja que inserção é realizada com sucesso, mas logo em seguida temos um "drop TABLE" e o fim do statement é ignorado como comentário. Por fim, caso o user da aplicação possua grant para efetuar o "drop table", a tabela é deliberadamente apagada.


Sem levarmos em consideração tecnologias como Flashback Table e etc., a escolinha do pequenino Bobby Tables, infelizmente terá que recuperar seus dados do ultimo backup válido.

Existem inúmeras outras possibilidades de códigos que podem ser inseridos com diferentes propósitos em diferentes circunstâncias. Aqui temos um cenário absurdo com uma mãe NERD psicótica, mas o mais preocupante é quando este tipo de brecha é implementada em aplicações Web, onde o próprio usuário final pode inserir códigos ao seu bel-prazer.

Vale lembrar, que além da falha de segurança em si, este tipo de implementação de execução dos SQL statements pode degradar consideravelmente o desempenho do banco de dados pois não faz uso adequado do "library cache" (no caso de um SGDB Oracle), mas talvez abordemos isto em um post futuramente.


Como resolvê-lo:

Existem diversas formas de se resolver este tipo de falha, algumas mais elegantes porém mais trabalhosas para o desenvolvedor, outras mais fáceis e também mais susceptíveis a erros, mas enfim, a minha sugestão é a utilização de uma destas duas técnicas:

1) Parameterized statements (binded variables): é uma pratica própria para os desenvolvedores da camada de persistência na aplicação, que consiste em utilizar rotinas de preparo e execução dos SQL statements, passando os valores das variáveis como parâmetros e não simplesmente concatenando o SQL em um grande string.  
    Diversas linguagens oferecem diferentes APIs, por tanto se familiarize com a linguagem na qual esteja programando. A API Java JDBC, por exemplo, oferece a classe "PreparedStatement" que encapsula a associação de variáveis e a execução dos SQL statements.

Exemplo:


PreparedStatement objPrep = objCon.prepareStatement("insert into STUDENTS(student_name) values(?)");
objPrep.setString(1,varStudentName);
objPrep.executeQuery();


2)    User roles + pl/sql packages: esta é uma forma que gosto muito de trabalhar, pois permite um maior controle pelo desenvolvedor SQL de todas as chamadas que as aplicações fazem ao banco, tanto do ponto de vista de segurança quanto de desempenho.
    No caso específico do Oracle cria-se "packages" PL/SQL, que abstraem a idéia de classes, com seus getters e setters, ou seja, métodos próprios para inserção e consulta dos dados, por meio de procedures e funções que retornam cursores para consultas. E isto permite que sejam criados "user roles" distintos, com permissões especificas de execução para estes pacotes. 
   Assim, todas as solicitações de acesso aos dados são tratadas dentro do código PL/SQL. Este método tem diversos "prós" e "contras", mas eu considero esta uma solução muito boa para se "espremer bits" do banco de dados, mas é importantíssimo que se tenha um bom domínio da linguagem PL/SQL. Lembrando que no caso do Oracle os pacotes procedurais também pode ser implementados em código nativo (C, C++, Java, etc.), o que pode lhe trazer  tanto ganho quanto perda no desempenho.
   É importante não confundir este método de implementação com o "mapeamento objeto-relacional", na verdade este método pode dificultar o mapeamento, assim como dificulta bastante a portabilidade do código para outras plataformas.


Referências: