Los ataques de inyección SQL

Los ataques de inyección SQL son muy conocidos y temidos por tener un impacto tremendo en la seguridad de una aplicación, además de ser la vulnerabilidad más común, según el Top Ten de OWASP. Cuando pensamos en una inyección SQL, enseguida la relacionamos con fuga de información o robo de credenciales, porque el ataque más usual es volcar tablas de la base de datos que utiliza la aplicación vulnerable.



En este post voy a hablaros de otro método para aprovechar una vulnerabilidad de inyección SQL: saltarse un formulario de login, pudiendo autenticarse sin conocer credenciales.



 Partamos de un ejemplo típico: un formulario con un campo de usuario y otro de contraseña. La aplicación hará una consulta a la base de datos muy parecida al código a continuación:

 Código: [...] $usuario=$_POST['usuario']; $pass=$_POST['password']; $query="SELECT * FROM users WHERE usuario = '$usuario' AND password = '$pass'"; [...] Si se introduce como usuario pepe y contraseña patata1, la query resultante sería: Código: SELECT * FROM users WHERE usuario = 'pepe' AND password = 'patata1'

 La forma inicial de conocer si un formulario es vulnerable a inyección SQL es añadiendo una comilla en algún campo, para que la sentencia resultante esté mal formada.

 Así pues, si el usuario es pep’e, la query sería: Código: SELECT * FROM users WHERE usuario = 'pep'e' AND password = 'patata1' El texto resaltado se queda fuera del campo usuario, y formaría parte de la lógica de la sentencia SQL. Como no tiene sentido lo que se ha quedado fuera, lo más probable es que la aplicación muestre un error de sentencia SQL mal formada. Si no apareciera error, igualmente sería vulnerable pero estaríamos hablando de un ataque de inyección ciega, pero esto sería adentrarme en otro berenjenal así que lo dejaremos para otro post ;-)

Entonces, ya que hemos descubierto como inyectar código que forme parte de la sentencia SQL ¿qué ocurriría si usamos como usuario y contraseña asdf’ OR ‘a’='a? Veámoslo:

Código: SELECT * FROM users WHERE usuario = 'asdf' OR 'a'='a' AND password = 'asdf' OR 'a'='a' Fijaos que la segunda a no se le pone una segunda comilla, para poder cerrar la cadena con la comilla original y producir una sentencia SQL bien formada.


 Si se introdujera, se juntarían dos comillas y daría un error de sintaxis. Se estaría inyectando entonces una condición booleana siempre verdadera, para que aunque se introduzcan unas credenciales incorrectas, el resultado final sea siempre verdadero.

Voy a describir el esquema lógico para que se entienda mejor (los AND tienen preferencia sobre los OR):

1. usuario = ‘asdf’ OR ‘a’='a’ AND password = ‘asdf’ OR ‘a’='a’

2. FALSE OR TRUE AND FALSE OR TRUE
3. FALSE OR FALSE OR TRUE 4. TRUE La sentencia final sería: Código: SELECT * FROM users WHERE TRUE Es decir, se van a recuperar todos los usuarios de la tabla users. Lo más probable es que la aplicación seleccione la primera fila de la tabla para autenticarse, que por regla general suele ser el usuario administrador, por ser el primero que se dio de alta en la aplicación. Este escenario es una prueba de concepto muy común, pero en la práctica no suele ser tan directo. Esto es debido a que la contraseña se almacena hasheada en la base de datos, por lo que el campo password se hashea antes de entrar en la sentencia SQL. Si se repitiera el ataque, resultaría la siguiente sentencia SQL: Código: md5(asdf' OR 'a'='a) = 28248f883bd3e12dce1f60842a8dea39; SELECT * FROM users WHERE usuario = 'asdf' OR 'a'='a' AND password = '28248f883bd3e12dce1f60842a8dea39'

1. usuario = ‘asdf’ OR ‘a’='a’ AND password = ’28248f883bd3e12dce1f60842a8dea39′ 2. FALSE OR TRUE AND FALSE

3. FALSE OR FALSE

4. FALSE La sentencia SQL se queda en: Código: SELECT * FROM users WHERE FALSE Por lo que la autenticación no se produciría. Sin embargo, se puede atacar de otra manera, añadiendo un carácter de terminación de sentencia al campo de usuario. En SQL, los caracteres “–” o “#” hacen que el resto de query se ignore. Si se añade en el campo usuario asdf’ OR ‘a’='a’# y lo que sea en el campo password, se ejecutaría: Código: SELECT * FROM users WHERE usuario = 'asdf' OR 'a'='a'#' AND password = '28248f883bd3e12dce1f60842a8dea39' Lo marcado en naranja se ignoraría, y quedaría: Código: SELECT * FROM users WHERE usuario = 'asdf' OR 'a'='a'# Como podréis deducir, el resultado lógico resultante es TRUE, y la autenticación será exitosa.

Si se conoce un nombre de usuario válido, el ataque es más sencillo todavía puesto que no sería necesario inyectar una condición válida de tipo algo=algo: Código: SELECT * FROM users WHERE usuario = 'admin'# Código: SELECT * FROM users WHERE usuario = 'asdf' OR a=a OR 'a'='a' AND password = '28248f883bd3e12dce1f60842a8dea39' Vamos a poner la secuencia lógica para que se vea más claro: 1. usuario = ‘asdf’ OR a=a OR ‘a’='a’ AND password = ’28248f883bd3e12dce1fa39′

2. FALSE OR TRUE OR TRUE AND FALSE

3. FALSE OR TRUE OR FALSE 4. TRUE De nuevo la aplicación se autenticaría con el primer usuario de la tabla. Fuente: google.com