Entras en la web de pruebas que montaste sin calentarte mucho la cabeza hace unos dias y ahora ver que la portada ha sido reemplazada por un fondo negro y unas letras verdes en marquesina que dicen "Hacked by ShadowR00t". Muy original el chaval. Lo peor es que la web estaba alojada en un servidor compartido con PHP 4, y el panel de administración era un coladero. No usaron ningún exploit sofisticado, no saturaron el servidor con un ataque raro. Simplemente metieron una comilla simple en el formulario de login.
Si llevas tiempo picando código en web, seguro que has escuchado hablar de la Inyección SQL. Pero me sigue sorprendiendo la cantidad de código en producción que me encuentro a diario donde la seguridad brilla por su ausencia. La gente no aprende.
El coladero de las comillas simples
Para entender el problema, vamos a verlo. Imagina que tienes tu formulario de acceso típico y en el código PHP procesas la petición validando contra tu base de datos MySQL (seguramente la versión 4.0 que viene con el cPanel).
El código que hizo el "primo del cliente" se ve más o menos así:
<?php
$usuario = $_POST['username'];
$clave = $_POST['password'];
// Nos conectamos y lanzamos la query a pelo
$sql = "SELECT id FROM usuarios WHERE username = '$usuario' AND password = '$clave'";
$resultado = mysql_query($sql, $conexion);
if (mysql_num_rows($resultado) > 0) {
echo "¡Bienvenido!";
} else {
echo "Acceso denegado.";
}
?>
A primera vista, parece lógico. El problema es que estamos confiando ciegamente en lo que el usuario escribe en el formulario. ¿Qué pasa si en el campo de usuario alguien con ganas de trastear teclea exactamente esto?
admin' --
Si sustituimos esa entrada en nuestra variable $sql, la consulta que realmente le llega al motor de MySQL queda así:
SELECT id FROM usuarios WHERE username = 'admin' -- ' AND password = 'lo-que-sea'
En SQL, los dos guiones medios (--) indican el comienzo de un comentario. Todo lo que va detrás se ignora por completo. Felicidades, el atacante acaba de iniciar sesión como administrador sin necesidad de conocer la contraseña, simplemente porque hemos concatenado cadenas de texto sin limpiar.
Otro clásico es meter ' OR '1'='1 en el campo del usuario. La consulta evaluará que uno es igual a uno, lo cual siempre es verdadero, devolviendo el primer registro de la tabla (que suele ser el maldito administrador).
¿Cómo parchear esto de una vez?
El parche rápido que todo el mundo usa es cruzar los dedos para que el servidor tenga activado magic_quotes_gpc en el php.ini, pero depender de eso es jugar a la ruleta rusa. Si migras de hosting, te puedes llevar una sorpresa muy desagradable.
La forma correcta de evitar que te revienten la base de datos es escapar siempre los datos antes de meterlos en la consulta. Si esperas un número, fuerza el tipo con intval(). Si trabajas con cadenas de texto, pásalas sí o sí por mysql_real_escape_string():
<?php
// Limpieza básica antes de tocar la base de datos
$usuario = mysql_real_escape_string($_POST['username']);
$clave = mysql_real_escape_string($_POST['password']);
$sql = "SELECT id FROM usuarios WHERE username = '$usuario' AND password = '$clave'";
?>
Esta función se encarga de poner barras invertidas delante de caracteres peligrosos como las comillas simples o dobles, neutralizando la inyección para que MySQL lo entienda como texto plano y no como instrucciones.
El futuro de la validación
Me fascina que en pleno 2005, con la web supuestamente madurando hacia aplicaciones más serias, sigamos teniendo que explicar esto. A veces pienso que la culpa la tienen los tutoriales que circulan por los foros, que enseñan a hacer las cosas rápido para que funcionen, pero se olvidan de la seguridad.
Sinceramente, dudo que las inyecciones SQL desaparezcan pronto. Mientras sea más fácil concatenar una cadena a lo bruto que montar un sistema seguro, la pereza ganará. Supongo que hasta que los lenguajes de programación no nos fuercen a separar el código SQL de los datos por diseño —quizás con esas abstracciones raras de las que hablan ahora en PHP 5 con la POO— seguiremos viendo foros caídos y bases de datos volcadas en internet. Por ahora, nos toca seguir limpiando variables a mano, una a una, y cruzando los dedos para que no se nos escape ninguna. Me voy a hacer otro café.