Como incluir caracteres especiais e valores NULL nas consultas MySQL

Usar aspas simples ou duplas, bem como outros caracteres especiais, em queries MySQL pode trazer alguma dor de cabeça para iniciantes.
Neste texto, vou procurar jogar alguma luz sobre este assunto e ajudar a resolvê-lo de forma definitiva.
Se achar interessante, inclua esta página nos seus favoritos, para futuras consultas — uma vez que o problema não é dos que incomodam com tanta frequência.
Capa do tutorial PHP - a função include e require

O problema dos caracteres especiais nas queries MySQL

Nos vários exemplos, usados nos artigos deste site, tenho evitado, quando possível, as “saias justas” com caracteres especiais.
Veja a seguinte query INSERT:

INSERT INTO perfil (nome,data_nasc,cor,comida,quantidade)
VALUES('Alvarez','1973-10-12','azul','panelada',3);

A query, acima, funciona bem e há pouca possibilidade de alguém ter problemas com as suas aspas.
Contudo, se mudarmos o valor da coluna nome para “Mont’Alverne”, o problema aparece. Veja:

INSERT INTO perfil (nome,data_nasc,cor,comida,quantidade)
VALUES('Mont'Alverne','1973-10-12','azul','panelada',3);

Note que o plugin de reconhecimento de sintaxe tem dificuldade para interpretar o valor entre as aspas simples.


O interpretador “pensa” que a segunda aspa está lá para fechar a primeira. Mas trata-se apenas de um apóstrofo — o suficiente para invalidar toda esta query.
Para evitar a confusão, há duas abordagens básicas possíveis.
Na primeira, você precede a aspa adicional com outra:

INSERT INTO perfil (nome,data_nasc,cor,comida,quantidade)
VALUES('Mont''Alverne','1973-10-12','azul','panelada',3);

Na segunda, use uma barra invertida ou backslash:

INSERT INTO perfil (nome,data_nasc,cor,comida,quantidade)
VALUES('Mont\'Alverne','1973-10-12','azul','panelada',3);

Note a diferença entre estes 2 resultados e o primeiro.


Uma outra alternativa, é usar as tradicionais aspas duplas em torno do valor Mont'Alverne. Isto também funciona:

INSERT INTO perfil (nome,data_nasc,cor,comida,quantidade)
VALUES("Mont'Alverne",'1973-10-12','azul','panelada',3);

Como lidar com valores NULL no MySQL

Se você tiver intenção de armazenar dados binários, tais como imagens ou audio, tem que ter em mente que estes valores contém todo tipo de caracteres especiais (e problemáticos), além de nulls (bytes de valor zero).
Embora o valor SQL null não seja um caractere especial, requer tratamento diferenciado.
No SQL, NULL indica “sem valor” ou “valor nulo”.
Em outros artigos, neste site, os valores NULL também têm sido evitados. Hoje, vamos encarar o problema e mostrar algumas formas de lidar com ele.
Por exemplo, se você não souber a cor preferida do Senhor Mont’Alverne, não pode preencher o valor com NULL, assim:

INSERT INTO perfil (nome,data_nasc,cor,comida,quantidade)
VALUES('Mont\'Alverne','1973-10-12','NULL','panelada',3);

O valor NULL dispensa as aspas. O correto é como se vê abaixo:

INSERT INTO perfil (nome,data_nasc,cor,comida,quantidade)
VALUES('Mont\'Alverne','1973-10-12',NULL,'panelada',3);

Como tratar os caracteres especiais dentro dentro de um script PHP

Cartoon - how sql injection work
Clique para ampliar.

Os problemas, acima, podem ser resolvidos com muita naturalidade, diretamente, por que as queries foram digitadas dentro do cliente MySQL — onde você mesmo está fornecendo os dados e pode corrigir e controlar as entradas.

A necessidade de lidar apropriadamente com os caracteres especiais é especialmente importante no ambiente da web, para o qual as queries são feitas com formulários input (form input).
Você precisa estar pronto para lidar com qualquer tipo de entrada de dados feita pelo usuário, de maneira geral.
Não é incomum, usuários maliciosos digitarem informações contendo caracteres problemáticos, com o objetivo deliberado de tentar burlar o seu
sistema
.

Se você estiver usando uma variável nome para receber aquele valor, em um script, não pode ter qualquer controle sobre o que o usuário do sistema vai te fornecer.
Neste caso, é preciso criar mecanismos para prever as diversas possibilidades de entradas e manipulá-las adequadamente.
No PHP, a função addslashes(), pode ser usada para tratar as entradas e torná-las seguras para ser inseridas em suas consultas MySQL.
Adicionalmente, a função unset() pode ser usada para forçar uma variável a não ter valor algum.

A função addslashes() retorna uma string com barras invertidas antes de caracteres que precisam ser escapados. Estes caracteres são aspas simples (‘), aspas duplas (“), barra invertida (\) e o byte NULL. manual do PHP.

Desta forma, o resultado de addslashes ("Mont'Alverne"), será Mont\'Alverne — que será recebido e gravado no banco de dados;
Se você precisar exibir este valor, sem o slash (a barra invertida), pode usar a função contrária: stripslashes() — com a mesma sintaxe.

Referências:

Manual do PHP: A função addslashes().

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