Introdução: devemos temer o mysql_real_escape_string()?
\n\nVocê certamente já ouviu o velho ditado dos desenvolvedores PHP: "mysql_real_escape_string() salva de todas as injeções SQL". Será que isso é verdade? Em 2024, quando PDO e prepared statements se tornaram padrão, esse tema parece arcaico. Mas milhares de desenvolvedores ainda mantêm projetos legados onde mysql_real_escape_string() é a única linha de defesa.
No Stack Overflow, a pergunta "SQL injection that gets around mysql_real_escape_string()" acumulou centenas de milhares de visualizações. Por quê? Porque a resposta não é óbvia. Neste artigo, vamos analisar cenários reais onde o escape não salva e comparar diferentes abordagens de proteção. Se você programa em PHP — leia com atenção.
\n\nComo funciona o mysql_real_escape_string()
\n\nA função mysql_real_escape_string() (da extensão mysql_*, agora obsoleta) escapa caracteres especiais em uma string, levando em conta a codificação atual da conexão. Ela adiciona uma barra invertida antes dos caracteres: ', ", \\, \\n, \\r, NULL e Control-Z.
<?php\n$connection = mysql_connect('localhost', 'user', 'pass');\nmysql_select_db('test', $connection);\n$unsafe = "O'Brien";\n$safe = mysql_real_escape_string($unsafe); // O\\'Brien\n$query = "SELECT * FROM users WHERE name = '$safe'";\n?>\n\nParece que funciona. Mas há nuances.
\n\nCenário 1: Problema com codificação (GBK / Big5)
\n\nA forma mais conhecida de burlar é usar codificações multibyte, como GBK (chinesa) ou Big5. Se a conexão estiver configurada para GBK e você enviar a string %bf%27 — a mágica acontece.
Em GBK, o byte 0xbf não é um caractere completo. Quando mysql_real_escape_string() encontra ' (0x27), ele adiciona uma barra invertida 0x5c. O resultado é a sequência: 0xbf + 0x5c + 0x27. Mas em GBK, 0xbf5c é um único caractere válido (por exemplo, o ideograma "縗"). Como resultado, a barra é "engolida" e a aspa simples fica desprotegida!
<?php\n// Configurando codificação GBK\nmysql_query("SET NAMES 'GBK'", $connection);\n$input = "%bf%27"; // Bytes: 0xbf 0x27\n$escaped = mysql_real_escape_string($input, $connection);\n// Resultado: 0xbf5c27 -> em GBK é "縗'" (aspa simples permaneceu)\n$query = "SELECT * FROM users WHERE id = '$escaped'";\n// Na prática, executa: SELECT * FROM users WHERE id = '縗''\n// Agora é possível injetar: ' OR 1=1 -- \n?>\n\nConclusão: Se a codificação da conexão não corresponder à codificação dos dados, mysql_real_escape_string() pode ser burlada. Esse bug foi descrito no famoso relatório de bug do PHP #47168.
Cenário 2: Uso em campos numéricos
\n\nO escape funciona apenas com strings. Se você inserir dados em um campo numérico sem aspas, mysql_real_escape_string() não adianta, pois o valor não é escapado — ele é simplesmente inserido como está.
<?php\n$id = mysql_real_escape_string($_GET['id']); // Suponha "1 OR 1=1"\n$query = "SELECT * FROM users WHERE id = $id"; // Sem aspas!\n// Perigo: SELECT * FROM users WHERE id = 1 OR 1=1\n?>\n\nMesmo que você escape a string, em um contexto numérico isso não faz sentido — não há aspas, então qualquer código SQL pode ser inserido.
\n\nConclusão: O escape não substitui a validação do tipo de dado. Para números, use intval() ou ctype_digit().
Cenário 3: Consultas LIKE e caracteres curinga
\n\nmysql_real_escape_string() escapa ' e \\, mas não mexe nos caracteres