Inyección SQL evadiendo mysql_real_escape_string(): mitos y realidad

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: ¿deberíamos temer a mysql_real_escape_string()?

\n\n

Seguramente 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.

\n\n

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\n

Cómo funciona mysql_real_escape_string()

\n\n

La 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.

\n\n
<?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\n

Parece que funciona. Pero hay matices.

\n\n

Escenario 1: Problema con la codificación (GBK / Big5)

\n\n

La 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.

\n\n

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.

\n\n
<?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\n

Conclusió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.

\n\n

Escenario 2: Uso de campos numéricos

\n\n

El 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.

\n\n
<?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\n

Incluso 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\n

Conclusión: El escape no reemplaza la verificación del tipo de dato. Para números, usa intval() o ctype_digit().

\n\n

Escenario 3: Consultas LIKE y caracteres comodín

\n\n

mysql_real_escape_string() escapa ' y \\, pero no toca los caracteres

Blogs

Book Recommendations