Previna-se de ataques SQL Injection

Este artigo explica o básico do SQL Injection (Injeção de SQL), com um exemplo que mostra como ele se dá e provê métodos de prevenção a estes ataques.
Tal como o nome sugere, este ataque pode ser feito através de queries SQL. Muitos programadores não têm idéia de como um agressor pode usar uma query. Basicamente, um SQL Injection pode ser feito em uma aplicação web que não efetue a filtragem apropriada dos dados fornecidos pelos usuários, confiando em tudo que ele digita – o que pode ser uma requisição SQL não prevista pelo idealizador do software.
Os exemplos mencionados aqui foram testados com os seguintes softwares:

  • PHP 5.3.3-7
  • Apache/2.2.16
  • Postgresql 8.4

Ainda que você não esteja fazendo uso de qualquer um deles (claro que MySQL está incluído), os conceitos se aplicam a qualquer situação que envolva um website com formulários a ser preenchidos pelos visitantes e que dão acesso ao banco de dados.

Um exemplo de SQL Injection

Vamos começar pelo fato de que muitas aplicações web têm uma página de autenticação. Vamos usar o código seguinte, como um exemplo:

index.html

<html>
<head><title>SQL Injection Demo</title></head>
<body onload="document.getElementById('user_name').focus();" >
<form name="login_form" id="login_form" method="post" action="login.php">
<table border=0 align="center" >
<tr>
<td colspan=5 align="center" ><font face="Century Schoolbook L" > Login Page </font></td>
</tr>
<tr>
<td> User Name:</td><td> <input type="text" size="13" id="user_name" name="user_name" value=""></td>
</tr>
<tr>
<td> Password: </td><td> <input type="password" size="13" id="pass_word" name="pass_word" value=""></td>
</tr>
<tr>
<td colspan=2 align="center"><input type="submit" value="Login"> </div></td>
</tr>
</table>
</form>
</body>
</html>

Ao fornecer o nome de usuário (user_name) e senha (pass_word), seus valores são postados em login.php via HTTP_POST.

login.php

<?php
$Host= '192.168.1.8';
$Dbname= 'john';
$User= 'john';
$Password= 'xxx';
$Schema = 'test';

$Conection_string="host=$Host dbname=$Dbname user=$User password=$Password";

/* Conecta ao banco de dados e pede uma nova conexão*/
$Connect=pg_connect($Conection_string,$PGSQL_CONNECT_FORCE_NEW);

/* Erro ao verificar a string de conexao */
if (!$Connect) {
echo "Falha ao conectar ao banco de dados";
exit;
}

$query="SELECT * from $Schema.users where user_name='".$_POST['user_name']."' and password='".$_POST['pass_word']."';";

$result=pg_query($Connect,$query);
$rows = pg_num_rows($result);
if ($rows) {
echo "Sucesso ao logar!";
}
else {
echo "Não foi possivel logar.";
}
?>

Pois bem. A linha 19, no código acima, é vulnerável a uma ataque (me refiro à linha que começa com $query="SELECT *). Trata-se de uma requisição cujo objetivo é encontrar no banco de dados o nome e a senha fornecidos pelo usuário. Tudo vai funcionar bem se forem fornecidos dados corretos e válidos. Contudo, um usuário malicioso pode fornecer outro tipo de informação ao sistema.
No campo nome do usuário, em vez de digitar o que se espera, ele pode digitar o seguinte:

' or 1=1;--

e deixar o campo senha em branco.
Ao clicar em submit, as informações serão postadas em login.php, onde a requisição será vista como:

SELECT * from test.members where user_name='' or '=';--' and password='';

O que se vê acima é uma requisição SQL plenamente válida. No postgresql o -- é um indicador de início de um comentário, ou seja, tudo o que vier depois deste caractere será ignorado. O que será executado é o seguinte:

select * from test.members where user_name='' or '=';

o que será verdadeiro (true) e retornará a mensagem “Login Success”.
Caso o agressor conheça os nomes das tabelas contidas no banco de dados, ele poderá apagar as tabelas com a seguinte entrada, no campo nome do usuário:

';drop table test.lop;--

Alguns scripts de autenticação tendem a agir da seguinte forma:

  • Guardar as senhas no formato md5.
  • Selecionar primeiro o nome,senha no banco de dados, com base no que foi fornecido pelo digitador.
  • Formatar em md5 a senha fornecida e compará-la com a senha no banco.
  • caso sejam iguais, a autenticação segue adiante.

Vejamos como contornar isto, no caso de a query ser vulnerável a um SQL-Injection.

login.php

<?php
$Host= '192.168.1.8';
$Dbname= 'john';
$User= 'john';
$Password= 'xxx';
$Schema = 'test';

$Conection_string="host=$Host dbname=$Dbname user=$User password=$Password";

/* Conecta ao banco de dados e pede nova conexão */
$Connect=pg_connect($Conection_string,$PGSQL_CONNECT_FORCE_NEW);

/* Erro ao verificar a string de conexao */
if (!$Connect) {
echo "Falha ao conectar ao banco de dados";
exit;
}

$query="SELECT * from $Schema.users where user_name='".$_POST['user_name']."' and password='".$_POST['pass_word']."';";

$result=pg_query($Connect,$query);
$rows = pg_num_rows($result);
if ($rows) {
echo "Sucesso ao logar!";
}
else {
echo "Erro ao logar.";
}
?>

Agora digite o seguinte no campo nome de usuário:

' UNION ALL SELECT 'laksh','202cb962ac59075b964b07152d234b70

em seguida, entre “123” no campo senha e clique em submit, sabendo que md5(123) é igual a 202cb962ac59075b964b07152d234b70.
Agora, a query vai se expandir para

SELECT user_name,password from test.members where user_name='' UNION ALL SELECT 'laksh','202cb962ac59075b964b07152d234b70';

e quando for executada, o banco de dados vai retornar ‘laksh‘ como nome de usuário e ‘202cb962ac59075b964b07152d234b70‘ como senha. E, uma vez que postamos “123”, no campo pass_word, o strcmp vai retornar 0 e a autenticação ocorrerá com sucesso.
O que se vê, acima, são algumas das inúmeras possibilidades de ataques SQL Injection. Seguem, abaixo, algumas coisas que podem ser feitas para reduzir as possibilidades de ataques:

  • Sempre verificar o que é digitado (nunca confie no que o usuário vai digitar);
  • Se você espera que se digite um nome de usuário em um determinado campo, certifique-se de que ele contenha apenas caracteres alfanuméricos;
  • Elimine ou filtre caracteres especiais e entradas possivelmente maliciosas dos usuários;
  • Use expressões preparadas para executar as requisições;
  • Não permita que várias requisições sejam feitas em uma única expressão;
  • Não deixe vazar informações sobre o banco de dados através das mensagens de erro, etc…

Esta é uma tradução livre do artigo original, de Lakshmanah Ganapathy, que pode ser encontrado http://miud.in/1aYd

Publicado por

Elias Praciano

Autor de tecnologia (livre, de preferência), apaixonado por programação e astronomia.
Fã de séries, como “Rick and Morty” e “BoJack Horseman”.
Me siga no Twitter e vamos trocar ideias!

2 thoughts on “Previna-se de ataques SQL Injection”

  1. Obrigado pelo post, pois ajudou muita gente aqui (estou ptsaondo novamente pois acho q deu algum erro, meu post nao apareceu)Fiz uma adaptae7e3o do seu script em meu site e ele apresenta o seguinte erro:Warning: session_start() [function.session-start]: Cannot send session cache limiter headers already sent (output started at /home/cbragam/public_html/admin/home.php:1) in /home/cbragam/public_html/admin/home.php on line 2PS: Eu je1 alterei varios pontos do codigo e mesmo assim o erro continua em uma pesquisa rapida no google, eu vi que isso ode ser um erro no PHP.ini, mas segundo meu provedor o cache limiter esta liberado.Vocea acredita que pode ser algo no codigo ?Obrigado, Abs!

    1. Uma causa simples para este “erro” (na verdade, é apenas um aviso, que não deve impedir a execução do restante do código), é a inserção de espaços em branco entre as tags iniciais do seu código PHP. Há casos em que o próprio editor insere estes espaços ou quebras de linha.
      Previna isso mantendo o < ?PHP no topo do script, antes de qualquer outra coisa. Verifique se não há qualquer linha ou espaço em branco antes ou depois desta tag.
      Há quem resolva o problema com um ob_start() para mandar o seu PHP pro buffer antes do HTTP. Em seguida, o ob_flush() pode ser usado para enviar o conteúdo http. Você pode obter mais informações aqui:
      http://www.php.net/manual/pt_BR/function.ob-start.php

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *