«¡Socorro! ¡Mi mysql_real_escape_string() no funciona!»
Si alguna vez has buscado en Google «SQL injection that gets around mysql_real_escape_string()», no estás solo. Esta pregunta en Stack Overflow ha acumulado decenas de miles de visitas y se ha convertido en un clásico de la seguridad web. En teoría, usas mysql_real_escape_string() y estás protegido. Pero no. Los hackers encuentran resquicios, y los desarrolladores, canas.
En este artículo, analizaremos cómo las inyecciones SQL pueden evadir el escape de mysql_real_escape_string() en PHP. Verás ejemplos reales de código, entenderás por qué el «viejo y confiable» enfoque ya no funciona y aprenderás cómo protegerte de verdad. ¡Allá vamos!
¿Por qué mysql_real_escape_string() no es una panacea?
Para empezar, un poco de historia. La función mysql_real_escape_string() apareció en la era de PHP 4 y 5, cuando todos usaban la extensión mysql_*. Escapa caracteres especiales como comillas, barras invertidas y caracteres de control. Pero el problema es que solo funciona en el contexto de una única codificación.
Si la codificación del cliente y del servidor no coinciden, comienza el caos. Y si usas codificaciones multibyte (por ejemplo, GBK o SJIS), la función puede convertirse en tu peor enemigo.
Ejemplo clásico: ataque multibyte
Imagina que tu sitio usa la codificación GBK (china) y envías la cadena:
$_POST['name'] = "0xbf27"; // ¿Apóstrofe escapado o simplemente '?En GBK, el carácter 0xbf27 es un carácter chino válido. Pero si mysql_real_escape_string() «ve» 0x27 (apóstrofe), lo escapa con una barra invertida: 0xbf5c27. El problema es que 0xbf5c también es un carácter válido en GBK. ¡Esto significa que el apóstrofe 0x27 permanece sin escapar, y el hacker puede insertar su código SQL!
// Código vulnerable$name = mysql_real_escape_string($_POST['name']);$query = "SELECT * FROM users WHERE name = '$name'";
// Si se ingresa: 0xbf27// La función devuelve: 0xbf5c27// En GBK esto es: carácter chino + apóstrofe// La consulta se convierte en: SELECT * FROM users WHERE name = 'car_chi' OR 1=1 -- '¡Voilà! La inyección ha tenido éxito. Y esto no es teoría: se han encontrado vulnerabilidades así en proyectos reales.
Vulnerabilidades típicas al usar mysql_real_escape_string()
Enumeremos los errores más comunes que permiten evadir el escape:
- Codificación incorrecta — como en el ejemplo anterior. Si el servidor cree que los datos están en UTF-8, pero el cliente envía GBK, el escape se rompe.
- Uso de campos numéricos sin comillas — si insertas un número directamente, el escape no funciona. Por ejemplo:
SELECT * FROM users WHERE id = $id— y el hacker escribe1 OR 1=1. - Consultas LIKE — el escape no maneja los caracteres
%y_. Se puede realizar una inyección SQL ciega a través de patrones. - Escape múltiple — si llamas a la función dos veces, la barra invertida se duplica y la protección desaparece.
Ejemplo: ataque a un campo numérico
// Código vulnerable$id = mysql_real_escape_string($_GET['id']);$query = "SELECT * FROM products WHERE id = $id"; // ¡No se necesitan comillas!
// El hacker ingresa: 1 UNION SELECT username, password FROM users// La consulta se convierte en: SELECT * FROM products WHERE id = 1 UNION SELECT username, password FROM usersEl escape aquí es inútil: no hay comillas que escapar. El hacker simplemente inserta su SQL directamente.
Métodos modernos de protección (que funcionan)
Basta de historias de terror: pasemos a las soluciones. Si programas en PHP y quieres olvidarte de las inyecciones SQL, aquí tienes tu lista de verificación:
1. Usa PDO o MySQLi con sentencias preparadas
Esto es