lunes, 16 de enero de 2012

XPath y SQL Injection: Todos a una

Por desgracia (o por suerte), muchos de nosotros estamos acostumbrado a encontrarnos esas Inyecciones SQL tan de libro como poner un ' AND '1'='1 y a partir de ahí empezar a jugar. Desde luego, son las más rápidas de encontrar y de explotar, pero del mismo modo son las menos divertidas.

Sin embargo, a veces me encuentro con vulnerabilidades más curiosas y divertidas, por ejemplo es muy típico eso de que la misma variable se use para varias cosas dentro de un mismo código, y que para llegar a explotar la vulnerabilidad tengas primero que conseguir que la cadena que metas no provoque errores anteriores.

El caso que os voy a comentar aquí es de este tipo, y está basado en casos reales que me he encontrado por ahí. Imaginad una URL del siguiente tipo:

http://demo.pentester.es/buscar.aspx?ID=666999

La web simplemente muestra la descripción de un articulo concreto, hasta aquí nada "divertido".
Podemos empezar a meter caracteres varios como si no hubiera mañana, a ver como responde, comillas simples, dobles, cadenas típicas de inyección, etc, hasta que introduzcamos algo como:

ID=666999' ERROR

Y nos devuelve un error como el siguiente:



Tiene toda la pinta de ser vulnerable a XPath Injection. Vamos a comprobarlo:

ID=666999' and 'foo'='foo



Wooops! Qué tenemos aquí? Parece que además de usar el valor del parametro ID para la consulta XPath, a posteriori usa ese mismo valor para realizar una petición SQL a una base de datos Oracle.

¿Hará esta petición siempre? ¿O únicamente si la petición XPath devuelve resultados?
Forcemos que no devuelva ninguno, a ver que pasa:

ID=666999' and 'foo'='fuu

Con esto hemos conseguido que la petición XPath sea siempre falsa, y efectivamente no nos devuelve ningún error SQL (y la ficha aparece vacía de datos, evidentemente), así que necesitamos que se nos devuelva algún valor y que la sintaxis sea correcta para que podamos intentar la Inyección SQL.

El problema con el que nos encontramos es que el contenido del parámetro ID es empleado en la consulta SQL como un numerico, así que al poner las comillas para hacer el XPath Injection fastidiamos la sintaxis de la sentencia SQL, y si no ponemos la comilla entonces estamos haciendo que la consulta XPath no devuelva ningún resultado, con lo que nunca se ejecuta la SQL.

¿Cómo salimos de esta?
Vamos a tener que inventarnos algún tipo de cadena de inyección que cumpla las siguientes condiciones:
  • No provoque un error de sintaxis XPath
  • Haga que devuelva algún valor en la consulta XPath
  • No provoque un error de sintaxis SQL
Por suerte tenemos a la vista tanto la sentencia XPath como la sentencia SQL, así que la cosa nos va a resultar mucho más fácil. Seguro que existen mucha manera de resolver este mismo problema, pero a mi la que primero me ha venido la cabeza es la siguiente:

Dado que el principal problema es que no podemos introducir instrucciones SQL por dentro de las comillas simples (que es donde nos hace falta) porque eso provoca que la petición XPath no devuelva ningún resultado y por tanto que la SQL nunca se ejecute, una posible solución sería replicar la condición de la siguiente forma:

ID=666999' or ID=666999

Lo cual, al inyectarse dentro de la petición XPath, la dejará de la siguiente manera:

TEST/REGISTROS['ID=666999' or ID='666999']

Ahora podemos introducir todo lo que queramos antes de la primera comilla (salvo comillas, claro está), y eso no provocará ningún tipo de error XPath ni impedirá que la sentencia devuelva los valores esperados, con lo que ya podemos centrarnos en la SQL.

Si observamos la SQL, tiene un montón de lineas, paréntesis, subqueries y demás. El valor que nosotros introducimos, además, aparece en varios sitios de la query, pero por suerte la primera vez que aparece es en una de las primeras condiciones de los WHERE, así que nos va a resultar bastante sencillo "cargarnos" el resto de la query comentándola:

ID=666999 -- ' or ID='666999

Eso dejaría una query SQL del aspecto de la siguiente:

SELECT a,b,c,d,e,f,g FROM t1,t2,t3,t4,t5,t6,t7,t8 WHERE id=666999 -- ' or ID='666999 [...]

Nota: Ponemos [...] en lugar de todo el churro que ya no nos interesa de ahora en adelante.

Como podeis ver, la cosa parece que se nos pone cuesta abajo a partir de este punto.
Ahora solo tenemos que concatenarle un UNION con el número de columnas adecuado a ver si conseguimos sacar información por pantalla:

ID=666999 UNION SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL FROM DUAL -- ' or ID='666999

Esto debería sacarnos la información del artículo con todo campos vacíos, pero en lugar de eso vemos lo siguiente:



Parece ser que el mismo parámetro se usa una segunda vez para formar otra sentencia SQL, pero esta ya no tiene 7 columnas, sino una sola, así que el UNION que nos va a funcionar con una nunca funcionará con la otra. Esto podría ser un problema, pero no nos pongamos nervioso, quizá simplemente dándole a aceptar...

Efectivamente, aunque la segunda sentencia nos devuelva un error, la primera se ejecuta y además imprime los resultados por pantalla, lo cual podemos comprobarlo por ejemplo mostrando el nombre del usuario:

ID=666999 UNION SELECT NULL,USER,SYS.DATABASE_NAME,NULL,NULL,NULL,NULL FROM DUAL -- ' or ID='666999

A partir de aquí, ya seguiríamos el proceso de Inyección SQL habitual, intentar sacar las tablas, información sensible, intentar escalar privilegios, e incluso ejecutar código en el sistema operativo, si fuera posible.

8 comentarios :

NecronoiD dijo...

Muy intersante el articulo Jose, me lo apunto :)

0verMain dijo...

¡Realmente bueno José!. Se nota que le dedicaste tiempo y cariño a ese test.

Yo también me lo apunto.

Jose Selvi dijo...

Gracias a ambos :)

@0verMain: El poder ver los errores facilitó mucho. Si no hubieran estado hubiera sido muuuucho más complicado.

Rafael Alfaro dijo...

Me ha encantado. Me lo apunto para usarlo cuando sea procedente :)

Alguien dijo...

Muy bueno, como siempre

Creo que si la segunda consulta SQL hubiera impedido que la primera muestre sus resultados aún se podría haber intentado una explotación error based. ¿Qué opinas?

Un saludo.

Jose Selvi dijo...

@Alguien: Mostrándose el error como se muestra por pantalla, en principio sí, claro :)

No lo probé en este caso, porque no hizo falta, pero en teoría debería funcionar.

Víctor Maldonado dijo...

Muy bueno, porcierto daras alguna charla en barcelona? si es así, me gustaría estar!

Jose Selvi dijo...

Suelo ir a alguna charla en Madrid y a alguna en Barcelona todos los años. Este año pasado en Barcelona estuve en la NcN, y en años anteriores en las FIST.

Este año es posible que esté por Barcelona dando un curso del SANS, y seguro que a lo largo del año sale alguna charla por allí.

Siempre suelo poner post en el blog cuando voy a ir a dar alguna charla, así que si estás atento sabrás enseguida cuando voy a ir por ahí por Barna. Yo por mi encantado de tenerte entre la audiencia :)