Blind SQL injection con Python - BoomerNiX

lunes, 14 de mayo de 2018

Blind SQL injection con Python

En este post se va a hablar sobre inyecciones SQL a ciegas. Todo el mundo que se dedica a la seguridad, programación, etc. ha tenido que escuchar sobre los ataques de inyección de SQL y debe saber de qué tratan y cómo evitarlos. Estos ataques, si la página cuenta con la vulnerabilidad, pueden ser muy sencillos y directos cuando la página, por ejemplo, no cuenta con errores genéricos, pero cuando hay algún trabajo por detrás (por supuesto, no completo y correcto), se complica algo más… ¿o no?

En realidad, las inyecciones SQL a ciegas (Blind SQL injection) no son más difíciles de explotar, si no, que incluso son más fáciles de automatizar. ¿Por qué pienso esto? Vamos entenderlo en la próxima sección.

Tipos de ataque

Los ataques Blind SQLi se pueden categorizar en 2 tipos:

  • Basadas en tiempos
    • Se trata de hacer consultas y ver los tiempos de retardo, haremos que para los valores ciertos tarde X segundos y los falsos sea inmediato, tenemos 2 opciones:
      1. Con funciones que proporciona la misma base de datos (por ejemplo, sleep)
      2. Consultas pesadas
  • Comportamiento según el valor booleano de la consulta (True/False)

Haremos consultas y veremos la reacción de la página, para ello tenemos que saber que devuelve en caso de consultas verdaderas y que devuelve en consultas falsas. Podemos tirar de consultas que siempre son falso, por ejemplo, agregando a la consulta and 2=3.

Nota: En ocasiones los operadores lógicos AND y OR no son usables, en nuestro ejemplo funcionan, y creo que para mostrar el funcionamiento es más que suficiente. Se puede leer acerca de esto, en el post que escribió Chema Alonso: Arithmetic Blind SQL Injection. Donde se puede apreciar soluciones de división por 0 o desbordamiento de tipo de datos.

Ejemplo Blind SQLi

Aquí voy a centrarme en el tipo 2, basado en comportamiento, así que, de primeras vamos a ver un ejemplo con una página ficticia, el host “example.com” que recibe un parámetro “id”, y que sabemos que el número 1 devuelve cierto, y es vulnerable a blind SQL injection, pero no al tradicional. Haremos estás 2 consultas:

  • Example.com/?id=1 > Será cierto
  • Example.com/?id=1 and 2=3 > Será una consulta falsa

Bien  si en caso de que sea una consulta con algo que devolver (True), nos devuelve en la página: “BoomerNiX – True” y en caso de que la consulta sea falsa arroja “BoomerNiX – False”… ¡tenemos un ataque muy automatizable! Basta con ir haciendo consultas y comprobando la respuesta para ir sacando la información.

PoC

Vamos a jugar con DVWA, para las pruebas pondremos el nivel de seguridad en medium. Antes de empezar, prueba a meter una comilla simple en el apartado de inyección SQL e inyección SQL a ciegas, ¿qué sucede?

Nos dirigimos a SQL injection (Blind). El objetivo, crear un script en Python, que nos devuelva la versión de la base de datos que está usando.

Necesitamos obtener la Cookie, puedes ir a tu navegador y en las herramientas para desarrolladores encontrarla, aquí os muestro los pasos con Google Chrome [Estos pasos pueden variar con el tiempo y navegador]:

  1. Pulsamos Ctrl + Shift + i
  2. Hacemos click en Application
  3. En el lateral buscamos Storage y dentro Cookies, ahí tendremos la de nuestra página



Las primeras pruebas para detectar el comportamiento ante una consulta verdadera y otra falsa las haremos manualmente. Teniendo en mente el objetivo, obtener la versión de la base de datos, ejecutamos las siguientes consultas.

Siempre verdadera:

-1 union select null, ASCII(substring(@@version,1,1)) < 300

Y siempre falsa:

-1 union select null, ASCII(substring(@@version,1,1)) > 300

En las dos consultas de arriba vemos que hacemos uso de UNION, como la consulta legítima tiene 2 campos y aquí nos vale con 1, el primero lo dejamos en null. Además, no nos interesa que la consulta legítima devuelva valores, por ello ponemos el -1, podíamos dejar un valor positivo con un and 2=3.


Ahora ya tenemos las diferencias que queríamos:

  • Falso > Surname: 0
  • Verdadero > Surname: 1

Ya podemos crear nuestro script y ahorrarnos tiempo para conseguir nuestro objetivo, nos interesa buscar si la respuesta devuelve: Surname: 1, que será una consulta verdadera.
Nuestro script tendrá la cookie y la URL donde realizar la petición GET. En la URL tendremos que ir variando 2 valores, vamos a verlo en la “teoría”, para la versión 5.1, las preguntas que devuelven correcto son:

  • ASCII(substring(@@version,1,1)) = 53 >> ¿Primer carácter es 5?
  • ASCII(substring(@@version,2,1)) = 46 >> ¿Segundo carácter es .?
  • ASCII(substring(@@version,3,1)) = 49 >> ¿Tercer carácter es 1?

Consultar tabla ASCII si no se entiende el 53, 46, 49.

Teniendo esto en cuenta, podemos hacer un bucle para recorrer cada posición de la versión de la base de datos, y probar cada valor, hasta dar con el correcto. Iremos preguntando para la posición 1: ¿Es 1?, ¿Es 2?, …, ¿Es 5?  Seguimos para la posición 2, 3, 4… hasta que no haya más que comprobar, y tengamos la versión.

El script es muy sencillo y se puede ver en la siguiente captura, se hace uso de la librería urllib.request.



Para que funcione en seguridad low, bastaría con añadir en la línea 14 detrás de: ?id=-1 un %27, que equivale a la comilla simpre.

Lo puedes encontrar en mi GitHub.

Probando el script, tenemos la siguiente salida con la versión de la base de datos:


Teniendo en mente el ejemplo visto aquí, ahora podéis practicar intentando automatizar la búsqueda de tablas, columnas, usuarios, etc. En MySQLa partir de la versión 5 te puedes ayudar de:

  • information_schema.tables
  • information_schema.columns

Por ejemplo, y de manera manual. Sabiendo que existe una tabla “users”, y habiendo sacado el nombre de las tablas podemos ir obteniendo el nombre de los usuarios, en la captura de abajo probamos con la “B”:


Si no existiera un usuario en la tabla users, que empiece por B, nos devolvería un solo resultado, con Surname: 0. Una inyección que nos permite sacar usuario y contraseña:


El Hash MD5 que vemos en la captura anterior corresponde a 'password'. En este caso, en DVWA, buscamos llegar al siguiente resultado:


Este Script se podría ir perfilando y conseguir uno más genérico y que valga para probar más páginas (recordando hacerlo en entornos controlados y con permisos).

Aquí termina el post, espero que os sirva. ¡Nos vemos en la próxima entrada!

1 comentario:

  1. Se ve complicado este tema. mil gracias por compartir

    ResponderEliminar