// 01
Resumen
Mirame es una máquina de dificultad fácil de la plataforma DockerLabs, diseñada para practicar bypass de autenticación mediante inyección SQL, extracción de datos con sqlmap, técnicas de esteganografía y escalada de privilegios mediante un binario con el bit SUID mal configurado. Corre sobre un contenedor Docker con Debian Linux y expone dos servicios: SSH (puerto 22) con OpenSSH 9.2p1 y un servidor web HTTP (puerto 80) con Apache 2.4.61.
La superficie de ataque se inicia en un formulario de login web ( index.php / auth.php) que no sanitiza correctamente las entradas del usuario, permitiendo inyecciones SQL básicas. A través de sqlmap se extrae la base de datos completa con credenciales en texto plano. Estas contraseñas se reutilizan como wordlist en un segundo escaneo con Gobuster, descubriendo un directorio oculto que contiene una imagen con datos esteganográficos — un fichero ZIP cifrado que, al ser crackeado con John the Ripper, revela credenciales SSH.
Una vez dentro del sistema como el usuario carlos, la escalada de privilegios es directa: el binario /usr/bin/find tiene el bit SUID activado, lo que permite ejecutarlo con permisos de root y lanzar una shell privilegiada con una sola instrucción extraída de GTFOBins.
| Fase | Técnica / Herramienta | Resultado |
|---|---|---|
| Despliegue | sudo bash auto_deploy.sh mirame.tar | Contenedor activo · IP 172.17.0.2 |
| Escaneo | nmap -p- -sSCV -n -Pn --min-rate=5000 -oX | Puertos 22 (SSH) y 80 (HTTP) abiertos |
| Reporte | xsltproc mirame.xml → mirame.html | Reporte HTML visual del scan Nmap |
| Enum. Web | gobuster dir -x txt,php,bak,old | Detectados auth.php, index.php, page.php |
| SQLi manual | 'admin' --' en campo usuario | Error SQL visible → no sanitizado |
| Captura HTTP | Burp Suite Intercept → request.txt | POST /auth.php capturado |
| SQLi automático | sqlmap -r request.txt --dbs / --tables / --dump | 4 usuarios con contraseñas en texto plano |
| Dir. oculto | gobuster con passwords como wordlist | Directorio /directoriotravieso descubierto |
| Esteganografía | stegseek miramebien.jpg | ocultito.zip embebido en imagen |
| Crack ZIP | zip2john + john --wordlist=rockyou.txt | Contraseña: stupid1 |
| Extracción | unzip miramebien.jpg.out → secret.txt | Credenciales carlos:carlitos |
| Acceso inicial | ssh carlos@172.17.0.2 | Shell como carlos |
| Recon local | find / -perm -4000 2>/dev/null | /usr/bin/find con bit SUID |
| Privesc | /usr/bin/find . -exec /bin/sh -p \; -quit | Shell como root |
// 02
Despliegue de la Máquina
A diferencia de plataformas como HackTheBox donde las máquinas ya están corriendo en servidores remotos, en DockerLabs las máquinas son contenedores Docker que debemos levantar nosotros mismos en local. El proceso es simple: se descarga el archivo .tar de la máquina y se ejecuta el script auto_deploy.sh que viene incluido con la plataforma.
El script necesita permisos de superusuario (sudo) porque Docker requiere privilegios elevados para crear y gestionar redes y contenedores. Internamente, el script carga la imagen del contenedor desde el archivo .tar, crea una red Docker interna, asigna automáticamente la IP 172.17.0.2 al contenedor y lo inicia. Mientras el terminal muestre el mensaje de espera, la máquina está activa. Al terminar, se presiona Ctrl+C para detener y eliminar el contenedor.
// ¿POR QUÉ 172.17.0.2?
Docker crea por defecto una red bridge interna con el rango 172.17.0.0/16. El host (tu Kali) recibe la IP 172.17.0.1 (el gateway) y el primer contenedor que se levanta recibe 172.17.0.2. A diferencia de HackTheBox donde necesitas VPN, en DockerLabs la comunicación es directa a través de esta red virtual local.
// 03
Reconocimiento
Con la máquina desplegada y su IP confirmada, el primer paso es descubrir qué servicios están expuestos. Se realiza un escaneo completo de todos los 65,535 puertos TCP con Nmap, usando el siguiente conjunto de flags optimizado para CTF: -p- escanea la totalidad de puertos (sin esto, Nmap solo comprueba los 1000 más comunes por defecto), -sSCV combina tres modos en uno: SYN scan sigiloso que no completa el handshake TCP, detección de versión de los servicios detectados, y ejecución de los scripts de reconocimiento del engine NSE de Nmap. -n desactiva la resolución de nombres DNS para no añadir latencia innecesaria, -Pn omite el ping de host-discovery (asume que el host está activo — útil porque algunos hosts bloquean ICMP), --open muestra únicamente los puertos que están abiertos, y --min-rate=5000 fuerza un mínimo de 5000 paquetes por segundo para que el scan sea rápido. El resultado se exporta a XML con -oX mirame.xml para procesarlo visualmente después.
// ANÁLISIS DEL SCAN
Nmap detectó dos puertos: SSH en el 22 (sin credenciales aún, no podemos entrar) y HTTP en el 80 con título "Login Page". Esto apunta directamente a un formulario de autenticación web como vector de ataque. El siguiente paso es enumerar el contenido web para entender qué archivos y directorios existen.
El archivo mirame.xml generado por Nmap está en formato XML crudo, difícil de leer. La herramienta xsltproc transforma ese XML en un reporte HTML visualmente organizado usando la hoja de estilos oficial que trae Nmap en /usr/share/nmap/nmap.xsl. El operador > redirige la salida estándar al archivo mirame.html, que se puede abrir en el navegador y muestra los puertos, versiones y scripts en un formato con tablas y colores. Es una buena práctica de documentación, especialmente en scans extensos.
// 04
Enumeración Web con Gobuster
Con el puerto 80 activo y sabiendo que hay una página de Login, se usa Gobuster en modo dir para descubrir archivos y directorios ocultos mediante fuerza bruta de diccionario. El flag -u http://172.17.0.2 especifica la URL objetivo, -w señala el diccionario a usar (common.txt de dirb, con las rutas más comunes en servidores web), y -x txt,php,bak,old indica las extensiones de archivo que Gobuster añadirá a cada palabra del diccionario. Esto es crucial: un servidor puede no tener un directorio /admin pero sí un archivo /admin.php — sin la flag -x ese archivo quedaría sin descubrir.
El resultado muestra que los archivos con código 200 (accesibles) son: index.php (la página de login principal), auth.php (el backend que procesa las credenciales) y page.php (una página a la que redirige el login tras autenticarse). Los archivos con código 403 existen pero el servidor deniega el acceso directo (archivos de configuración de Apache como .htaccess).
// HALLAZGO CLAVE: auth.php
El archivo auth.php es el procesador del formulario de login. Al existir como archivo accesible y separado del index, podemos intuir que las credenciales se envían por POST directamente a este endpoint. Esto será fundamental para capturar el request con Burp Suite y pasárselo a sqlmap.
// 05
SQL Injection — Detección Manual
Antes de lanzar herramientas automáticas, se realiza una prueba manual para confirmar que el formulario de login es vulnerable a inyección SQL. Esto es una buena práctica: entender primero qué ocurre manualmente antes de automatizar.
Se navega a http://172.17.0.2 y se observa un formulario de login con dos campos: Usuario y Contraseña. La hipótesis es que el backend construye una query SQL similar a esta para validar el login:
SELECT * FROM usuarios WHERE username='[INPUT]' AND password='[INPUT]'
Si el input no se sanitiza, podemos romper la estructura de la query. Se introduce 'admin' --' en el campo usuario (cualquier texto en contraseña). El apóstrofe inicial cierra el string de la query prematuramente, y el doble guion -- actúa como comentario en SQL, ignorando todo lo que sigue (incluyendo la validación de contraseña).
La respuesta del servidor es un error fatal de PHP/MySQL visible en pantalla: revela que el código de auth.php está en la línea 22, que usa MariaDB, y que la query falló porque recibió nuestra inyección sin escapar. Este tipo de error no debería nunca mostrarse en producción — es una grave falla de configuración que nos confirma que el campo es inyectable con Single Quoted String.
// ¿POR QUÉ ESTO CONFIRMA LA VULNERABILIDAD?
Un formulario seguro usaría prepared statements o escapado de caracteres especiales. En ese caso, el apóstrofe sería tratado como texto literal y no rompería la query. Aquí, el servidor ejecutó el input directamente dentro de la query SQL sin ningún filtro, y encima devolvió el error completo con rutas del servidor — dos fallos críticos en uno: SQL Injection + error verbose.
// 06
sqlmap — Extracción de la Base de Datos
Confirmada la vulnerabilidad SQL Injection, se automatiza la extracción usando sqlmap — la herramienta estándar del sector para pruebas de inyección SQL. Antes de ejecutarla, necesitamos capturar el request HTTP completo del formulario de login para que sqlmap sepa exactamente qué enviar al servidor.
Burp Suite es un proxy HTTP de interceptación. Al configurar el navegador para que pase todo su tráfico a través de Burp (generalmente 127.0.0.1:8080), podemos ver, modificar y guardar cada request y response. En este caso, se activa Intercept On, se hace un intento de login normal con credenciales de prueba (admin/admin), y Burp captura el request POST antes de que llegue al servidor.
Se guarda ese request completo en un archivo de texto llamado request.txt. Este archivo contiene todos los headers HTTP y el body del POST con los parámetros username y password. sqlmap usará este archivo como plantilla para saber exactamente cómo y dónde inyectar sus payloads.
Con el flag -r request.txt se le pasa a sqlmap el request guardado. sqlmap prueba automáticamente múltiples técnicas de inyección en todos los parámetros del body del POST. Detecta que tanto username como password son inyectables mediante Boolean-based blind (respuestas distintas según si la condición es verdadera o falsa), Error-based (extrae datos a través de los mensajes de error de MySQL) y Time-based blind (la respuesta tarda más si la condición es verdadera). También confirma que el DBMS es MySQL / MariaDB y que el servidor web es Apache 2.4.61 en Linux Debian.
El flag --dbs (databases) instruye a sqlmap para que enumere todas las bases de datos disponibles en el servidor MySQL. Esto equivale a ejecutar un SHOW DATABASES; pero de forma ciega, a través de los parámetros inyectables. Cada carácter del nombre de cada base de datos se extrae bit a bit mediante las técnicas de inyección detectadas anteriormente. Se descubren dos bases de datos: information_schema (la base de datos interna del sistema MySQL con metadatos) y users (la base de datos de la aplicación, que es la que nos interesa).
Con -D users se especifica que queremos operar sobre la base de datos users, y --tables enumera todas las tablas que contiene. sqlmap descubre una única tabla llamada usuarios. Este nombre es muy descriptivo — casi seguro contiene nombres de usuario y contraseñas.
El flag -T usuarios selecciona la tabla específica y --dump extrae todo su contenido. sqlmap primero enumera las columnas de la tabla (descubre id, username, password) y luego vuelca fila por fila cada registro. El resultado revela 4 usuarios con sus contraseñas en texto plano (no hasheadas): este es un error de seguridad grave — las contraseñas nunca deben almacenarse en texto claro.
// PISTA OCULTA EN LAS CONTRASEÑAS
Al revisar las contraseñas con atención, la del usuario "directorio" es "directoriotravieso". Esta combinación es una pista directa: sugiere que existe un directorio web oculto cuyo nombre podría estar relacionado con estas palabras. En CTFs, los nombres de cuentas y contraseñas suelen contener pistas del siguiente paso.
// 07
Esteganografía — Datos Ocultos en la Imagen
Teniendo las contraseñas de la base de datos, se construye una wordlist artesanal combinando los nombres de usuario y contraseñas obtenidos. Se ejecuta un segundo Gobuster pero ahora con ese archivo como diccionario en lugar del common.txt genérico. La idea es que si algún directorio web tiene el mismo nombre que una de estas palabras, lo encontraremos.
El resultado confirma la hipótesis: Gobuster encuentra el directorio /directoriotravieso con código 301 (Redirect) apuntando a http://172.17.0.2/directoriotravieso/. Este directorio no aparecía en el scan anterior con common.txt porque su nombre no es una palabra común — solo era descubrible con las pistas extraídas de la base de datos.
Al navegar a http://172.17.0.2/directoriotravieso/ se accede a un directory listing de Apache (el servidor está configurado para mostrar el contenido del directorio cuando no hay un index.html). Dentro hay un único archivo: miramebien.jpg — una imagen de 6.2K. El nombre es una pista obvia: "mírame bien" implica que hay algo más de lo que parece a simple vista. La imagen muestra un par de ojos, reforzando el juego de palabras con el nombre de la máquina.
Se descarga la imagen con wget para analizarla localmente. Cuando algo "se ve normal" pero tiene nombre sospechoso en un CTF, lo primero es comprobar si contiene datos ocultos mediante esteganografía.
stegseek es una herramienta especializada en detectar y extraer datos ocultos en imágenes JPEG usando el algoritmo de esteganografía steghide. A diferencia de steghide, que necesita que conozcas la passphrase de antemano, stegseek hace un ataque de diccionario automático: prueba miles de passphrases por segundo hasta encontrar la correcta.
Por defecto, stegseek usa su lista de palabras interna basada en las más comunes. El resultado es inmediato: encuentra la passphrase "chocolate" (que aparecía también como parte de la contraseña del admin en la base de datos — otro guiño del autor), extrae el archivo oculto original cuyo nombre era ocultito.zip y lo guarda como miramebien.jpg.out.
// ¿QUÉ ES ESTEGANOGRAFÍA?
La esteganografía es la práctica de ocultar información dentro de otros archivos de forma que la presencia del mensaje sea imperceptible. A diferencia de la criptografía (que oculta el contenido), la esteganografía oculta la existencia del mensaje. En imágenes JPEG, herramientas como steghide modifican los bits menos significativos de los píxeles de forma que el cambio visual es invisible para el ojo humano, pero el archivo contiene datos adicionales que pueden extraerse con la passphrase correcta.
El archivo extraído miramebien.jpg.out es en realidad un ZIP (ocultito.zip) protegido por contraseña. No se puede descomprimir directamente. La herramienta zip2john (parte del suite John the Ripper) analiza el archivo ZIP y extrae la información de la contraseña en un formato de hash que John the Ripper puede intentar crackear. El resultado se guarda en hash_mirabien.txt.
Técnicamente, lo que extrae zip2john es el bloque de verificación PKZIP: un fragmento cifrado del archivo que John puede usar para comprobar si una contraseña es correcta sin necesidad de descomprimir el archivo completo. Esto hace el proceso muy rápido.
John the Ripper es una herramienta de crackeo de contraseñas que prueba posibles contraseñas contra un hash hasta encontrar la que genera el mismo resultado. Con el flag --wordlist=/usr/share/wordlists/rockyou.txt se especifica el diccionario a usar: rockyou.txt es la wordlist más usada en CTFs, derivada de una filtración real de 14 millones de contraseñas del servicio RockYou en 2009. John prueba cada contraseña del diccionario contra el hash extraído del ZIP.
Una vez que John ha crackeado el hash (o si ya lo había crackeado en una sesión anterior y lo tiene en caché), el flag --show muestra todas las contraseñas encontradas para los hashes del archivo. El formato de salida es archivo:contraseña:.... Se revela que la contraseña del ZIP es stupid1.
Con la contraseña obtenida se descomprime el archivo ZIP. Al ejecutar unzip miramebien.jpg.out, el sistema solicita la contraseña del archivo interno secret.txt. Se introduce stupid1 y se extrae el fichero de texto plano.
El archivo secret.txt contiene una sola línea en formato usuario:contraseña. Esta es exactamente la estructura que se usa para credenciales SSH. El usuario es carlos con la contraseña carlitos. Ya tenemos todo lo que necesitamos para entrar a la máquina por SSH.
// 08
Acceso Inicial — SSH como carlos
Con las credenciales extraídas de secret.txt, se establece una conexión SSH al servidor. El comando ssh carlos@172.17.0.2 inicia el cliente SSH indicando que queremos conectarnos con el usuario carlos a la IP del contenedor. La primera vez que nos conectamos a un host desconocido, SSH pide confirmación para guardar la fingerprint del servidor en ~/.ssh/known_hosts — respondemos yes. Después solicitará la contraseña.
La conexión es exitosa. La shell muestra el banner de Debian y el prompt de carlos. Estamos dentro del sistema con una terminal interactiva completa — mucho más cómoda que la webshell con la que trabajamos en la máquina Overlay. Desde aquí comenzamos la fase de escalada de privilegios.
// 09
Escalada de Privilegios — SUID en /usr/bin/find
Dentro del sistema como carlos, necesitamos encontrar una vía para escalar a root. La metodología estándar de reconocimiento local comienza buscando binarios con el bit SUID activado, ya que son ejecutables que corren con los permisos de su propietario (normalmente root) independientemente de quién los ejecute.
El comando find / -perm -4000 2>/dev/null busca recursivamente desde la raíz del sistema de archivos (/) todos los archivos que tengan el bit SUID (4000) activado en sus permisos. El guion antes del número (-4000) significa "que tenga al menos este bit activado". El 2>/dev/null descarta todos los mensajes de error (como "Permission denied" al intentar acceder a directorios de root) para que la salida sea limpia y solo muestre los binarios encontrados.
Entre los resultados aparece /usr/bin/find — la herramienta de búsqueda de archivos de Unix. El hecho de que find tenga SUID es un error de configuración grave: find tiene la capacidad de ejecutar comandos arbitrarios mediante su flag -exec, por lo que si corre como root, puede ejecutar cualquier cosa como root.
// ¿POR QUÉ /usr/bin/find CON SUID ES PELIGROSO?
El binario find tiene la capacidad -exec que permite ejecutar cualquier comando arbitrario por cada archivo encontrado. Si find tiene el bit SUID activado y su propietario es root, todos esos comandos se ejecutan con permisos de superusuario. Esto es exactamente lo que explotaremos. Esta configuración nunca debería existir en un sistema real — find solo necesita SUID en circunstancias muy excepcionales.
GTFOBins (gtfobins.github.io) es un repositorio curado de técnicas para abusar binarios Unix con privilegios elevados (SUID, sudo, capabilities). Al buscar "find" en GTFOBins bajo la categoría SUID, encontramos exactamente el payload que necesitamos: el binario puede usarse para obtener una shell interactiva (/bin/sh) a través de su flag -exec, y el flag -p de sh es clave — sin él, algunos sistemas modernos eliminarían los privilegios SUID al iniciar la shell (mecanismo de seguridad). Con -p, la shell mantiene el UID efectivo del proceso (root).
El comando final es elegante en su simplicidad. Se ejecuta /usr/bin/find . -exec /bin/sh -p \; -quit. Desglosado: find . busca en el directorio actual, -exec /bin/sh -p \; ejecuta /bin/sh con el flag -p por cada archivo encontrado (en este caso, el primer archivo del directorio actual), y -quit hace que find termine inmediatamente después del primer resultado, evitando que se ejecute múltiples veces. Como find corre con SUID de root, la shell que lanza también hereda esos permisos. El comando whoami confirma: root.
// MÁQUINA COMPROMETIDA COMPLETAMENTE
Hemos obtenido una shell como root en el contenedor. El flujo completo desde acceso inicial hasta privesc fue: SQLi → DB dump → wordlist → directorio oculto → esteganografía → crack ZIP → SSH → SUID find → root. Cada paso usó la información obtenida en el anterior, formando una cadena de ataque coherente.
// 10
Referencia de Comandos
- sudo — Ejecuta con privilegios de superusuario (Docker lo requiere)
- bash — Interprete shell que ejecuta el script
- mirame.tar — Imagen Docker comprimida de la máquina víctima
- -p- — Escanea los 65,535 puertos TCP (no solo los 1000 por defecto)
- -sS — SYN scan: no completa el handshake TCP (más sigiloso)
- -sC — Ejecuta scripts NSE por defecto (reconocimiento automático)
- -sV — Detecta versiones de servicios en cada puerto
- -n — Sin resolución DNS (más rápido)
- -Pn — Sin ping previo (asume que el host está activo)
- --min-rate=5000 — Mínimo 5000 paquetes/segundo (scan agresivo)
- --open — Muestra solo puertos con estado OPEN
- -oX file.xml — Exporta resultados en formato XML
- dir — Modo directory/file enumeration
- -u URL — URL objetivo del servidor web
- -w wordlist — Ruta al diccionario de palabras a probar
- -x ext — Extensiones a añadir a cada palabra (php, txt, bak, old)
- -t N — Número de hilos concurrentes (default: 10)
- -r request.txt — Lee el request HTTP desde un archivo (capturado con Burp)
- --dbs — Enumera todas las bases de datos disponibles
- -D nombre — Selecciona una base de datos específica para operar
- --tables — Lista todas las tablas de la base de datos seleccionada
- -T nombre — Selecciona una tabla específica
- --dump — Extrae y muestra todos los datos de la tabla seleccionada
- --batch — Responde automáticamente a todas las preguntas interactivas
- imagen.jpg — Imagen JPEG a analizar en busca de datos esteganográficos
- wordlist — Diccionario de passphrases (opcional; usa lista interna por defecto)
- -o archivo — Especifica el nombre del archivo de salida extraído
- zip2john archivo — Convierte el ZIP protegido a formato de hash crackeable
- --wordlist= — Especifica el diccionario para el ataque (rockyou.txt)
- --show — Muestra las contraseñas ya crackeadas para los hashes del archivo
- --format= — Fuerza un formato de hash específico (ej: pkzip)
- usuario@IP — Usuario del sistema remoto y su dirección IP
- -p puerto — Puerto SSH si no es el estándar 22
- -i clave — Usa una clave privada para autenticarse en lugar de contraseña
- -L — Port forwarding local (túnel SSH)
- . — Directorio de búsqueda (el directorio actual)
- -exec cmd \; — Ejecuta el comando por cada archivo encontrado
- /bin/sh -p — Lanza una shell manteniendo los privilegios SUID (UID efectivo)
- -quit — Termina find después del primer resultado (evita múltiples shells)
// 11
Glosario
', --, ; o OR 1=1 que cambian la lógica de la query original. Las consecuencias van desde bypass de autenticación hasta extracción completa de la base de datos o ejecución de comandos en el sistema operativo.? o :nombre) y se envían los valores por separado. La base de datos los trata siempre como datos literales, nunca como código SQL, haciendo imposible que un apóstrofe en el input rompa la query. En PHP se usan con mysqli_prepare() o PDO.SLEEP(N) para comunicar datos mediante retardos medibles; UNION-based: añade una query secundaria con UNION SELECT para inyectar datos en la respuesta. La elección de técnica depende de lo que el servidor devuelva al atacante.-x) permite descubrir archivos y directorios que no están enlazados en la web visible.zip2john extrae el hash en formato compatible con John./usr/bin/passwd (para que cualquier usuario pueda cambiar su propia contraseña) o /usr/bin/sudo. El problema surge cuando binarios que permiten ejecutar código arbitrario (como find, python, vim) tienen SUID — se convierten en vectores de escalada de privilegios.172.17.0.0/16: el host recibe 172.17.0.1 y los contenedores se asignan secuencialmente (172.17.0.2, etc.). Esta arquitectura es la que usa DockerLabs para sus máquinas vulnerables.index.html o index.php y la opción Options +Indexes está habilitada (o -Indexes no está configurada). Esto expone todos los archivos del directorio al visitante. Es una mala configuración frecuente: en un entorno de producción nunca debería estar habilitado, ya que revela la estructura de archivos del servidor. En CTFs, a menudo es intencional como pista.find).