Un ataque de inyección de SQL

martes 12 julio 2011 | In English

Renuncia de responsabilidad: Por supuesto que no hago esto en los sistemas de otros (sin consentimiento) y tampoco deberías de hacerlo tu. Aún si no causas ningún daño, en básicamente todos los países es contra la ley acceder a un sistema de esta manera.

Ya que escribí dos guías diferentes sobre el uso de sentencias preparadas de MySQL en PHP, y una de las razones para usar esto es para prevenir ataques de inyección de SQL, voy a escribir un poco sobre estos ataques que tantos sitios web han sufrido y continúan sufriendo incluso hoy. Más que definir que es este ataque, el objetivo es mostrar como funciona.

Comenzemos usando una tabla como ejemplo

usuarios
id nombre usuario contrasea grupo
1 admin admin loquesea admin
2 Juan juan algo normal
3 Valencia valencia otro normal
4 Cintia cintia nada normal
Nota: NO guardes las contraseñas en texto plano, yo lo hago aquí como un ejemplo, pero NO debes de hacer esto, por que si tu base de datos es comprometida, el atacante gana acceso a cada cuenta sin ningún esfuerzo. Utiliza hash y salt en las contraseñas, desafortunadamente mi guía sobre esto aún no ha sido terminada, pero Google es tu amigo. Además, NO utilices contraseñas así de sencillas.

Una manera simple de conectarse como un usuario, es buscar en la base de datos el nombre de usuario y la contraseña que las persona nos provee, y permitir la conexión de dicho usuario si ambos valores son encontrados en un registro, o mostrar un mensaje de error si no son encontrados. Por ejemplo, dados estos dos campos de entrada en una forma:

<input type="text" name="usuario" />
<input type="password" name="contrasea" />

Debemos recibir esto en dos variables post (no debes enviar contraseñas en la URL de el sitio web utilizando el método get, por que entonces es guardado en el historial del navegador y alguien más con acceso a la computadora puede fácilmente acceder la cuenta):

$_POST["usuario"]
$_POST["contrasea"]

Ahora, ESTO ES LO QUE NUNCA DEBES HACER, ni esto ni algo similar que resulte en enviar la entrada del usuario directamente a la base de datos:

$peticion = "select * from `usuarios` where `usuario`='{$_POST["usuario"]}' and `contrasea`='{$_POST["contrasea"]}'";
mysqli_query($link, $peticion);
or
$mysqli->query($peticion);

Por supuesto que esto va a funcionar, pero en un momento verás por que esta idea es tan terrible. Y, lo triste y preocupante es que he visto muchos sistemas que hacen esto. No se cual sea la experiencia de otros, pero en mi propia experiencia personal, la mayoría de los sistemas hechos internamente hacen esto.

Digamos que llenamos los dos campos de entrada usando estos dos valores:

Cintia
ninguna

Si sustituimos los valores en las variables respectivas, la petición que terminamos enviando a la base de datos sería como sigue:

select * from `usuarios` where `usuario`='Cintia' and `contrasea`='ninguna'

Esto nos devolvería una entrada de la base de datos, entonces podríamos guardar los valores en variables de sesión para manejar la conexión, por ejemplo, podríamos terminar con los siguientes valores:

$_SESSION["id"] = "4"
$_SESSION["nombre"] = "Cintia"
$_SESSION["grupo"] = "normal"

Todo parece bien y funciona, pero, ¿qué tal si en vez de llenar los campos de entrada con esos valores, alguien los llena con estos valores en su lugar?:

loquesea
' or '1' = '1

Despues de hacer las sustituciones, ¡Terminamos con la siguiente petición!

select * from `usuarios` where `usuario`='loquesea' and `contrasea`='' or '1' = '1'

Cuando MySQL realiza esta operación, lo siguiente podría ocurrir, ya que ni "loquesea" ni el campo de contraseña vacío existen, y uno siempre es igual a uno:

FALSO and FALSO or VERDADERO

El operador AND (Y) toma precedencia sobre el operador OR (o), y la operación AND solo es VERDADERO si ambos valores son VERDADERO. En este caso, ambos valores son FALSO, por lo que terminamos con la siguiente operación:

FALSO or VERDADERO

El operador OR es VERDADERO si cualquiera de los dos operandos es VERDADERO, así que en este caso el resultado de todas las operaciones condicionales es un simple VERDADERO para cada registro, haciendo que esto actúe como una petición select sin una condición. Y una operación select sin una condición nos daría la tabla entera (cada registro es una coincidencia).

Ahora, ya que el script de PHP recibe la tabla de la base de datos pero solo le importa el primer registro que lee, por que solo está esperando un registro como resultado, entonces comenzaríamos una sesión con el primer usuario encontrado.

$_SESSION["id"] = "1"
$_SESSION["name"] = "admin"
$_SESSION["group"] = "admin"

El primer usuario en la mayoría de los sistemas es casi siempre el usuario administrativo. Así que estamos acabados, nuestro sistema está bajo el control de alguna persona que probablemente no tiene las mejores intenciones.

Esto es como funciona un ataque de inyección de SQL, "inyecta" código que nuestra base de datos ejecuta. Y esto es como puede ser explotado para ganar privilegios administrativos. Nunca debes de confiar en la entrada de los usuarios de tu sistema, siempre debes sanear la entrada.

Por supuesto que la petición puede ser diferente, en este ejemplo solo modifico la petición select. Lo que puede ser hecho puede variar con el sistema. Solo usaré este ejemplo para ilustrar esto, pero tras analizar un sistema puedes casi siempre obtener la información de otros usuarios, de otras tablas en la base de datos, puedes obtener la base de datos entera, cambiar información en la base de datos, incluso borrar otras tablas o borrar la base de datos entera. Y por supuesto, si te conectas como administrador ya podrías tener los privilegios necesarios para causar verdadero caos y daños serios.

En las siguientes publicaciones cubro el uso de sentencias preparadas de MySQL que ofrecen protección contra esto, aún si esto no fue por que fueron originalmente implementadas (fue por velocidad). En un futuro cercano espero también terminar una guía sobre como usar hash y salt para proteger las contraseñas en la base de datos, y como proteger las sesiones. También espero publicar un simple sistema de usuarios implementando toda la información de estas guias. Solo necesito más tiempo en la vida real =/

Artículos relacionados

Categorías: MySQL, PHP, Programación