SQL Injection

SQL Injection é uma técnica em que usuários maliciosos podem injetar comandos SQL em um procedimento SQL através da entrada de dados em uma página web. Esses comandos SQL injetados alteram um SQL já existente e que deveria processar uma requisição pré-definida. Esse SQL que pode ser injetado compromete severamente a segurança de uma aplicação web.

Assim sendo, o ataque de Injeção de SQL (SQL Injection) consiste de inserções diretas de código nos campos de entrada de dados que serão concatenados com comandos SQL e posteriormente executados. A alteração direta de comandos SQL pode expor dados escondidos, sobrescrever dados valiosos, ou ainda executar comandos de sistema perigosos no servidor. No entanto, isso tudo é possível se a aplicação receber dados da entrada do usuário e combinar com parâmetros estáticos para montar a consulta SQL.

Os ataques de Injeção de SQL são divididos em três categorias:

  • O primeiro deles é aquele em que um atacante pode simplesmente entrar com uma string maliciosa e causar a execução imediata do código;
  • O segundo tipo de ataque é aquele em que um atacante insere dados no armazenamento persistente, que é considerado como uma fonte confiável. Um ataque é posteriormente executado por outra atividade.
  • O terceiro tipo de ataque é aquele em que o atacante pode manipular a função implícita To_Char() alterando os valor das variáveis de ambiente, NLS_Date_Format ou NLS_Numeric_Characters.

O primeiro deles é o tipo de ataque mais comumente realizado pelos atacantes.

As principais consequências relacionadas à segurança são:

  • Confidencialidade: Como as bases de dados SQL possuem informações sensíveis, a perda de confidencialidade é um problema frequente com vulnerabilidades de Injeção de SQL;
  • Autenticação: Se um comando SQL é usado para verificar nomes de usuário e senhas, pode ser possível se conectar ao sistema como outro usuário sem conhecimento prévio da senha;
  • Autorização: Se as informações de autorização estão num banco de dados SQL, pode ser possível alterar essas informações por meio da exploração bem-sucedida de uma vulnerabilidade de SQL Injection;
  • Integridade: Assim como pode ser possível ler informações sensíveis, também é possível fazer alterações ou mesmo excluir essas informações com um ataque de SQL Injection.

A melhor forma de verificar se a aplicação está vulnerável a ataques de Injeção de SQL é verificando os passos que os atacantes utilizam para explorar a vulnerabilidade. Normalmente um atacante inicia seu trabalho a partir da análise de alguns pontos específicos do aplicativo, são eles:

  • Realizar uma inspeção nos formulários e pontos de entrada de dados do aplicativo que aparentemente não estejam filtrando o uso de meta-caracteres, ou seja, códigos de erro e mensagem de falha;
  • Realizar testes para construção de pedidos SQL de maneira a contornar eventuais mecanismos de proteção de uso de meta-caracteres. Por exemplo, a utilização de meta-caracteres que sejam interpretados como comentários, como é o caso da sequência “–”, é frequentemente empregada nos ataques de injeção SQL, isso faz com que o restante do código seja ignorado.
  • Combinar o ataque com a utilização de stored procedures para esconder a injeção de meta-caracters;

As aplicações de médio e grande porte costumam utilizar bancos de dados para incluir informações sensíveis como: credenciais de acesso dos usuários, informações pessoais, catálogo completo e histórico de produtos e serviços, pedidos, extratos de conta corrente e informações sobre pagamentos, lista de clientes e de fornecedores, entre outras informações.

Normalmente quando usamos SQL (Structured Query Language) para exibir informações na página web ou processar dados é comum permitirmos que os usuários insiram valores para realizarmos uma pesquisa ou processamento. Isso permite uma filtragem melhor dos dados que realmente interessam ao usuário e permite armazenar informações.

Visto que comandos SQL nada mais são do que textos, é muito simples alterarmos esses comandos SQL com um pouco de código para permitir ao usuário visualizar a informação selecionada como mostra o código da Listagem 1.:

Listagem 1. Selecionando os dados de um usuário específico.


  txtIdUsuario = getRequestString("idUsuario");
  txtSQL = "SELECT * FROM Usuarios WHERE idUsuario = " + txtIdUsuario;

No código mostrado acima criamos um comando SELECT adicionando uma variável (txtIdUsuario) para selecionar um valor atribuído a string. Essa variável é obtida a partir da entrada do usuário na página. Essa entrada pode ser um perigo potencial que pode ocasionar a Injeção de SQL que por sua vez pode destruir com uma base de dados.

A maioria das vulnerabilidades e dos problemas de segurança são originados por falta de atenção quanto à segurança no momento da programação. No restante do artigo analisaremos a Injeção de SQL ou SQL Injection.

Quer se aprofundar mais na linguagem SQL? Confira os cursos de SQL e banco de dados da DevMedia.

Exemplos de Ataques de Injeção de SQL

Um exemplo de Injeção de SQL é no comando "1=1". Por exemplo, digamos que um SQL inicialmente deveria selecionar um usuário com um determinado id. Porém, um atacante malicioso entra com a seguinte entrada no input da página: "105 or 1=1".

Nesse caso teríamos o seguinte SQL: "SELECT * FROM Usuarios WHERE idUsuario = 105 or 1=1".

Podemos verificar que esse SQL é válido e retornará todas as linhas de uma tabela "Usuarios", visto que 1=1 é sempre verdadeiro e, portanto todas as linhas são verdadeiras e serão retornadas.

Aparentemente podemos pensar que não há maiores problemas listarmos os usuários de uma tabela, mas e se essa tabela contém senhas, nomes, cpf e outros dados confidenciais? O problema torna-se mais grave e perigoso.

Outro ataque de Injeção SQL bastante comum é o baseado no ""="" que também sempre é verdadeiro para cada linha de uma tabela. Por exemplo, imaginamos dois campos de entrada numa página web solicitando "Nome de Usuário" e "Senha". Nesse caso, teríamos o seguinte SQL: "SELECT * FROM Usuarios WHERE Nome ="" + nome + "" AND Senha ="" + senha + """

Um hacker poderia ter acesso a todos os nomes de usuários e senhas em uma base de dados apenas inserindo "" ou ""="" dentro do campo de nome de usuário ou senha.

Com isso teremos um SQL válido que resultará no comando SELECT * FROM Usuarios WHERE Nome ="" or ""="" AND Senha ="" or ""="" e como resultado teremos todas as linha de uma tabela Usuarios, visto que ""="" é sempre verdadeiro.

Outro ataque de Injeção de SQL comum são os comandos SQL em lote que é suportado pela maioria das bases de dados. Nesse caso os comandos são separados por ponto e vírgula, como mostra o exemplo da Listagem 2.

Listagem 2. Comandos SQL executados em lote.

SELECT * FROM Usuarios; DROP TABLE Fornecedores

O comando acima retornará todas as linhas na tabela Usuarios e então apagará a tabela Fornecedores.

Podemos imaginar o seguinte comando SQL da Listagem 3.

Listagem 3. Selecionando um usuário dado o id.

“SELECT * FROM Usuarios WHERE idUsuario = " + txtIdUsuario;

E a seguinte entrada fornecida no campo de texto da página web:

105; DROP TABLE Fornecedores

O código resultante será igual ao da Listagem 4.

Listagem 4. Código SQL resultante após a entrada do usuário no campo de entrada.

SELECT * FROM Usuarios WHERE idUsuario = 105; DROP TABLE Fornecedores

Esse SQL é válido e será processado.

Uma forma bastante comum de ataque de Injeção de SQL ocorre nos bancos de dados MSSQL Server onde o atacante envia o valor a%" exec master..xp_cmdshell "net user test testpass /ADD" -- para uma variável que espera uma entrada. Por exemplo, dado o código PHP da Listagem 5 que utiliza uma base de dados MSSQL Server.

Listagem 5. Código PHP que espera uma entrada na variável $prod e lista o produto.


  <?php
  $query  = "SELECT * FROM products WHERE id LIKE "%$prod%"";
  $result = mssql_query($query);
  ?>

Podemos imaginar que o atacante envie o valor a%" exec master..xp_cmdshell "net user test testpass /ADD" – para a variável $prod, assim teríamos o SQL resultante da Listagem 6.

Listagem 6. Código resultante após concatenar com a entrada do usuário.


  <?php
  $query  = "SELECT * FROM products
                      WHERE id LIKE "%a%"
                      exec master..xp_cmdshell "net user test testpass /ADD"--";
  $result = mssql_query($query);
  ?>

Após receber o comando acima o Microsoft SQL Server executa os comandos SQL em lote com um comando para adicionar um novo usuário para o banco de dados de contas locais. Se essa aplicação estiver sendo executada como “as” e o serviço “MSSQLSERVER” estiver sendo executado com privilégios suficientes, o atacante teria agora uma conta com a qual poderia acessar essa máquina, ocasionando um enorme perigo à base de dados.

No banco de dados PostgreSQL também existe uma forma bem comum utilizada pelos atacantes de Injeção de SQL em que se criam super-usuários diretamente no banco. Por exemplo, podemos imaginar o código da Listagem 7.

Listagem 7. Código que lista os produtos e espera uma entrada para variável $offset.


  <?php
  $offset = $argv[0]; 
  $query  = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
  $result = pg_query($conn, $query);
  ?>

Os usuários clicam nos links “próxima” e “anterior” ou então em setas que movem para a próxima página ou para a página anterior. Podemos verificar que a variável $offset é codificada na URL. O script espera que o valor de $offset seja um número decimal. No entanto, atacantes de Injeção de SQL tentam invadir acrescentando a forma codificada por urlencode() como mostrado na Listagem 8.

Listagem 8. Entrada realizada pelo atacante.


  0;
  insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
      select "crack", usesysid, "t","t","crack"
      from pg_shadow where usename="postgres";
  --

Com isso, o script forneceria de presente ao atacante acesso de super-usuário. Podemos verificar que 0; é para fornecer um deslocamento válido para a consulta original e terminá-la, assim após isso o atacante insere o restante do comando SQL que lhe fornece acesso de super-usuário ao banco de dados PostgreSQL.

No final do comando ainda podemos verificar a presença de um "--". Isto é um sinal de comentário que força o SQL a ignorar o resto da consulta escrito pelo desenvolvedor.

Não são apenas comandos SELECT que são suscetíveis a ataques de Injeção de SQL, os comandos UPDATE também tem um risco potencial. Os comandos UPDATE também são ameaçados por cortes e acréscimos de uma nova consulta. Além disso, o atacante poder usar a cláusula SET, mas neste caso ele precisa ter conhecimento sobre o esquema para poder manipular a consulta com sucesso. Para conseguir informações sobre o esquema o atacante poderia examinar os nomes das variáveis do formulário, ou simplesmente tentar por força bruta. Por exemplo, podemos verificar o código da Listagem 9.

Listagem 9. Código em PHP aguardando uma entrada nas variáveis $pwd e $uid.


  <?php
  $query = "UPDATE usertable SET pwd="$pwd" WHERE uid="$uid";";
  ?>

Agora podemos imaginar que um atacante envie o valor " or uid like"%admin%"; -- para a variável $uid para alterar a senha do administrador, ou ainda simplesmente configura $pwd para "qlqrcoisa", admin="yes", trusted=100 " (com um espaço sobrando) para ganhar mais privilégios. Dessa forma, teríamos o código resultante na Listagem 10.

Listagem 10. Código resultante após a entrada de dados fornecida pelo atacante.


  <?php
  // $uid == " or uid like"%admin%"; --
  $query = "UPDATE usertable SET pwd="..." WHERE uid="" or uid like "%admin%"; --";
   
  // $pwd == " qlqrcoisa", admin="yes", trusted=100 "
  $query = "UPDATE usertable SET pwd="qlqrcoisa", admin="yes", trusted=100 WHERE
  ...;";
  ?>

Prevenindo SQL Injection

Os desenvolvedores costumam usar uma "blacklist" de palavras ou caracteres para pesquisar por uma entrada SQL, para prevenir ataques de Injeção de SQL. Essa não é uma ideia muito interessante, visto que muitas dessas palavras como delete ou drop além de caracteres como ";" e " são usados comumente e poderiam ser permitidos em algumas entradas.

Saiba mais sobre SQL Injection nesse artigo da GURU99 (conteúdo em inglês).

A melhor forma de proteger os web sites de ataques de SQL Injection é usar parâmetros SQL. Os parâmetros SQL são valores que são adicionados em uma consulta SQL em tempo de execução e de uma forma controlada.

Por exemplo, em ASP.NET as entradas são tratadas da mesma forma que a apresentada na Listagem 11.

Listagem 11. Utilizando parâmetros para tratar os comandos SQL.


  txtIdUsuario = getRequestString("idUsuario");
  txtSQL = "SELECT * FROM Usuarios WHERE idUsuario = @0";
  db.Execute(txtSQL, txtIdUsuario);

Os parâmetros são tratados por um marcador "@" como mostra o exemplo na Listagem 12 abaixo.

Listagem 12. Outro exemplo utilizando procedimento SQL para tratar os comandos SQL.


  txtNom = getRequestString("NomeCliente");
  txtEnd = getRequestString("Endereco");
  txtCid = getRequestString("Cidade");
  txtSQL = "INSERT INTO Clientes (NomeCliente, Endereco, Cidade) Values(@0,@1,@2)";
  db.Execute(txtSQL, txtNom, txtEnd, txtCid);

Em PHP podemos fazer da mesma forma que a apresentada na Listagem 13.

Listagem 13. Utilizando parâmetros para tratar os comandos SQL.


  $stmt = $dbh->prepare("INSERT INTO Customers (NomeCliente, Endereco, Cidade)
  VALUES (:nom, :end, :cid)");
  $stmt->bindParam(":nom", $txtNom);
  $stmt->bindParam(":end, $txtEnd);
  $stmt->bindParam(":cid", $txtCid);
  $stmt->execute();

Em Java utilizamos Prepared Statements conforme mostra a Listagem 14.

Listagem 14. Utilizando Prepared Statements para tratar os comandos SQL.


  String selectStatement = "SELECT * FROM Usuarios WHERE idUsuario = ? ";
  PreparedStatement prepStmt = con.prepareStatement(selectStatement);
  prepStmt.setString(1, idUsuario);
  ResultSet rs = prepStmt.executeQuery();

Uma forma incorreta utilizada em Java e que deve ser evitada é mostrado na Listagem 15.

Listagem 15. Tratando incorretamente consultas SQL


  String sql = "select * from usuarios where username="" + username +"" and senha="" + senha + """;
  stmt = conn.createStatement();
  rs = stmt.executeQuery(sql);

Outras linguagens têm suas formas de realizar o mesmo procedimento.

Utilizando essa forma a engine do SQL checa cada um dos parâmetros para assegurar que eles estão corretos para as suas respectivas colunas e eles são tratados literalmente, e não como uma parte do SQL a ser executado.

Outra forma de evitar ataque de Injeção de SQL é validar todas as entradas testando o tipo, tamanho, formato, intervalo, etc. Devemos também validar documentos XML, usar Stored Procedures para validar entradas do usuário, nunca usar comandos Transact-SQL diretamente da entrada do usuário, implementar múltiplas camadas de validação na aplicação e não somente na primeira camada e nunca concatenar entradas do usuário.

Uma forma simples de proteger contra ataques de injeção de SQL é utilizando cláusulas LIKE onde caracteres "curinga" devem ser ignorados, conforme mostrado no código da Listagem 16.:

Listagem 16. Escapando caracteres especiais usando a cláusula LIKE.


  s = s.Replace("[", "[[]");
  s = s.Replace("%", "[%]");
  s = s.Replace("_", "[_]");

Revisar o código é sempre algo bastante indicado procurando por possíveis vulnerabilidades de Injeção de SQL. Uma forma de fazer essa busca é procurar por palavras-chaves como chamadas a EXECUTE, EXEC, ou sp_executesql no caso de utilizarmos o SQL Server.

Algumas dicas que devem ser consideradas são: termos bastante cuidado com pacotes prontos disponibilizados na comunidade open source, um atacante poderia adquirir uma cópia do código e acessar dados importantes da base de dados, como informações do esquema. Também nunca devemos confiar em nenhum tipo de entrada, especialmente as entradas do lado do cliente, cuidando até mesmo combos (combobox), campos hidden ou cookies. Outras dicas importantes são:


  • Nunca conectar ao banco de dados como super-usuário ou como o dono do banco de dados. Devemos sempre usar usuários personalizados com privilégios limitados.
  • Sempre verificar se uma entrada qualquer tem o tipo de dados esperado. As linguagens normalmente oferecem funções prontas que já fazem esse tipo de verificação. No PHP temos, por exemplo, as funções is_numeric(), ctype_digit(), entre outras. O PHP também oferece filtros que podem ser utilizados para entradas não confiáveis. Mais informações podem ser encontradas em http://us.php.net/manual/en/intro.filter.php.
  • Devemos adicionar aspas para cada valor não numérico especificado pelo usuário que será passado para o banco de dados com as funções de caracteres de escape, como por exemplo, as funções mysql_real_escape_string(), sqlite_escape_string(), entre outras disponíveis pelo PHP.
  • Nunca devemos imprimir qualquer informação específica do banco de dados, especialmente sobre o esquema.
  • Podemos usar stored procedures e cursores previamente definidos para abstrair acesso aos dados para que os usuários não acessem tabelas ou views diretamente, mas essa solução pode ter outros impactos e deve ser bem analisada previamente.
Além disso, é interessante registrarmos as consultas e operações do banco de dados. Este relatório é interessante para que possamos rastrear qual aplicação foi atacada. O relatório não é útil em si, mas através da informação que ele contém.

Neste artigo vimos que o ataque de Injeção de SQL é bastante comum e pode provocar danos enormes, visto que eles atacam a Confidencialidade, Autenticação, Autorização e a Integridade. O ataque de Injeção de SQL pode ocorrer em qualquer plataforma que provê suporte à linguagem SQL, inclusive em outros pontos de uma aplicação web como parâmetros cookies, além das solicitações http GET e http POST. O ataque é realizado através de caracteres inseridos na entrada de dados para então ser inseridos no comando SQL que será executado posteriormente numa base de dados. Algumas das melhores práticas para evitar Injeção de SQL são através de parametrização das consultas, usar "stored procedures", escapar toda entrada fornecida pelo usuário e limitar privilégios aos acessos.