Evasión de autenticación con 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:

[...]
$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:

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:

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:

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:

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:

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:

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:

SELECT * FROM users WHERE usuario = 'asdf' OR 'a'='a'#' AND password = 
      '28248f883bd3e12dce1f60842a8dea39'

Lo marcado en naranja se ignoraría, y quedaría:

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:

SELECT * FROM users WHERE usuario = 'admin'#

El resultado será que la aplicación se autenticará como usuario admin, siempre que sea válido, y sin tener que conocer la contraseña.

Otro ataque posible, en lugar de usar el carácter de terminación de sentencia, es añadir una condición válida adicional, para “anular” de alguna forma la condición de la contraseña, que al no conocerla, saldrá siempre falsa. La manera de hacerlo es: asdf’ OR a=a OR ‘a’=’a

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.

En resumen, un ataque de inyección SQL es un arma poderosa para saltarse un formulario de autenticación en una aplicación web, sin tener que lanzar ataques de fuerza bruta, siempre y cuando el desarrollador no haya filtrado los parámetros de entrada, que como ya conté en un post anterior, es imprescindible tenerlo en cuenta en todos y cada uno de los puntos de entrada de la aplicación.

Comments

  1. http://cajagusa says

    Esta genial tu articulo… la verdad siempre pensé que era demasiado necesario realizar una encriptacion de la contraseña para evitar estas cosas y para evitar si se roban la base de datos que no sepan que contraseña es. pero lo que no me imagine es que simplemente con el usuario también se podría hacer. muchas gracias …

    saludos

  2. Hola, muy interesante. Yo no sé nada de todo lo que pones pero te sigo hace casi un año. A proposito de entra entrada me viene a la memoria el dia que me tiraron el blog con ese sistema y tardé algunos meses en encontrar a quién me pudiese ayudar y aún está caído. En, mi intención sería conocer formas de rpotegerme sabiendo que tengo un blog de wordpress. Gracias y un saludo.

  3. Gracias cajagusa y Rimbaud.

    Te aconsejo Rimbaud que le eches un ojo al par de post de nuestro compañero Manu sobre unas interesantes recomendaciones para securizar wordpress.

    https://www.securityartwork.es/2013/02/15/securizando-wordpress/
    https://www.securityartwork.es/2013/06/20/securizando-wordpress-ii/

  4. Si os soy sincero no me imaginaba que era tan fácil burlar formularios mediante inyección SQL. Claro, que lo habéis explicado muy bien. Buen aporte

    También tengo un blog en wordpress asi que revisaré esa información, gracias.

    Saludos