Introducción: ¿deberíamos temer a mysql_real_escape_string()?
\n\nSeguramente has escuchado el viejo dicho de los desarrolladores PHP: «mysql_real_escape_string() salva de todas las inyecciones SQL». ¿Es esto realmente cierto? En 2024, cuando PDO y las sentencias preparadas se han convertido en el estándar, este tema parece arcaico. Pero miles de desarrolladores aún heredan proyectos antiguos donde mysql_real_escape_string() es la única línea de defensa.
En Stack Overflow, la pregunta «SQL injection that gets around mysql_real_escape_string()» ha acumulado cientos de miles de visitas. ¿Por qué? Porque la respuesta no es obvia. En este artículo, analizaremos escenarios reales donde el escape no salva, y compararemos diferentes enfoques de protección. Si escribes en PHP, lee con atención.
\n\nCómo funciona mysql_real_escape_string()
\n\nLa función mysql_real_escape_string() (de la extensión mysql_*, ahora obsoleta) escapa los caracteres especiales en una cadena, teniendo en cuenta la codificación actual de la conexión. Agrega una barra invertida antes de los caracteres: ', ", \\, \\n, \\r, NULL y 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. Pero hay matices.
\n\nEscenario 1: Problema con la codificación (GBK / Big5)
\n\nLa forma más conocida de evasión es el uso de codificaciones multibyte, como GBK (china) o Big5. Si la conexión está configurada en GBK y pasas la cadena %bf%27, ocurre la magia.
En GBK, el byte 0xbf no es un carácter completo. Cuando mysql_real_escape_string() encuentra ' (0x27), agrega una barra invertida 0x5c. El resultado es la secuencia: 0xbf + 0x5c + 0x27. Pero en GBK, 0xbf5c es un solo carácter válido (por ejemplo, el ideograma «縗»). Como resultado, la barra invertida se «consume» y la comilla queda sin escapar.
<?php\n// Configurar codificación 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 -> en GBK esto es "縗'" (la comilla permanece)\n$query = "SELECT * FROM users WHERE id = '$escaped'";\n// Realmente se ejecuta: SELECT * FROM users WHERE id = '縗''\n// Ahora se puede inyectar: ' OR 1=1 -- \n?>\n\nConclusión: Si la codificación de la conexión no coincide con la codificación de los datos, mysql_real_escape_string() puede ser evadida. Este error fue descrito en el conocido reporte de error PHP #47168.
Escenario 2: Uso de campos numéricos
\n\nEl escape solo funciona con cadenas. Si insertas datos en un campo numérico sin comillas, mysql_real_escape_string() no ayuda, porque el valor no se escapa, simplemente se inserta tal cual.
<?php\n$id = mysql_real_escape_string($_GET['id']); // Supongamos "1 OR 1=1"\n$query = "SELECT * FROM users WHERE id = $id"; // ¡Sin comillas!\n// Peligro: SELECT * FROM users WHERE id = 1 OR 1=1\n?>\n\nIncluso si escapas la cadena, en un contexto numérico no tiene sentido: no hay comillas, por lo tanto, se puede inyectar cualquier código SQL.
\n\nConclusión: El escape no reemplaza la verificación del tipo de dato. Para números, usa intval() o ctype_digit().
Escenario 3: Consultas LIKE y caracteres comodín
\n\nmysql_real_escape_string() escapa ' y \\, pero no toca los caracteres