This post is focused on the walkthrough of Hard Linux Machine Carpediem from HackTheBox.
Summary
Carpediem from HackTheBox, a hard linux machine. Here we get an interesting subdomain from fuzzing in which we can make an account and due to some misconfigurations elevate our low privilege user to admin user. From there we get access to the admin panel, where we abuse the upload functionality to get the initial foothold on the machine. In the docker container we’re in, we scan the internal network and discover a bunch of services. From these services, we expose a trudesk login panel and mongodb. Having full control of the mongodb, we update a user entry to add a custom password and login as that user. Looking through different tickets and mails, we discover the method to get credentials from Zoiper client and login as hflaccus to get the user.txt. For privesc, we first utilize tcpdump to capture the https traffic and analyze it using the ssl cert key in wireshark. Here we get credentials for the backdrop CMS where we exploit a vulnerability to get shell as www-data in a container which we elevate to root and finally escape the docker to get root on the actual machine.
Enumeration
Starting out with the initial nmap scan.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌──(kali㉿kali)-[~/…/hackthebox/hackthebox/machines/carpediem]└─$ nmap -A -vv 10.10.11.167 -oN nmapN
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack 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 nginx 1.18.0 (Ubuntu)| http-methods:
|_ Supported Methods: GET HEAD
|_http-title: Comming Soon
|_http-server-header: nginx/1.18.0 (Ubuntu)Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
With only 2 ports open, let’s go for port 80.
There’s nothing much to do in this website. Directory brute-forcing also didn’t give much.
So checking for virtual hosts for carpediem.htb we got a subdomain portal.carpediem.htb.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──(kali㉿kali)-[~/…/hackthebox/hackthebox/machines/carpediem]└─$ wfuzz -c -f sub-fighter -w /home/kali/Documents/wordlists/SecLists/Discovery/DNS/subdomains-top1million-5000.txt -u 'http://carpediem.htb' -H "Host: FUZZ.carpediem.htb" --hh 2875Target: http://carpediem.htb/
Total requests: 4989=====================================================================ID Response Lines Word Chars Payload=====================================================================000000048: 200462 L 2174 W 31090 Ch "portal"Total time: 0Processed Requests: 4989Filtered Requests: 4988Requests/sec.: 0
portal.carpediem.htb
I tried directory brute-forcing here as well and got the following results.
Nmap scan report for 172.17.0.1
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack
80/tcp open http syn-ack
Nmap scan report for 172.17.0.2
PORT STATE SERVICE REASON
21/tcp open ftp syn-ack
80/tcp open http syn-ack
443/tcp open https syn-ack
Nmap scan report for 172.17.0.3
PORT STATE SERVICE REASON
3306/tcp open mysql syn-ack
Nmap scan report for 172.17.0.4
Host is up (0.00016s latency).
Not shown: 65534 closed ports
PORT STATE SERVICE
27017/tcp open unknown
Nmap scan report for 172.17.0.6
Host is up (0.00016s latency).
Not shown: 65534 closed ports
PORT STATE SERVICE
8118/tcp open unknown
Enumerating the discovered services
Forwarding the ports with chisel, we can analyze each service.
Sadly, they are not crackable. But since we have full read/write access to the db, we can update entries here as well.
Looking here we can see that trudesk uses bcrypt to hash the passwords. We can write a simple python program (reference) to generate a similar hash and replace the original one.
We can now login to trudesk with credentials admin:supersecretpassword.
Logging in we get the dashboard.
Listening to the credentials
Looking around on the Active Tickets page, we have a ticket regarding new employee on-boarding.
In the mail it is mentioned that the new employee Horace Flaccus will get his credentials on a voicemail. It is also mentioned that they’ve been using zoiper client to do the process.
We can download the zoiper client and perform the mentioned process to get the credentials.
After downloading the zoiper client, we can use above mentioned credentials 9650:2022 and carpediem.htb as domain.
Simply dialing a call to *62 we can listen to the credentials.
From the obtained credentials, we can login as hflaccus to ssh.
Add a new module at https://127.0.0.1/?q=admin/modules/install. Then select manual installation.
Upload the malicious reference.tar file.
Enable the newly added module.
Shell as www-data on 172.17.0.2
Now start a netcat listener on port 4444 & visit the shell.php file.
And we got the shell as www-data.
www-data -> root in the same container
Looking around in the /opt directory, we have a file heartbeat.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
www-data@90c7f522b842:/opt$ cat heartbeat.sh
cat heartbeat.sh
#!/bin/bash#Run a site availability check every 10 seconds via cronchecksum=($(/usr/bin/md5sum /var/www/html/backdrop/core/scripts/backdrop.sh))if[[$checksum !="70a121c0202a33567101e2330c069b34"]];thenexitfistatus=$(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
fiwww-data@90c7f522b842:/opt$
In this script, it is calling the backdrop.sh file.
www-data@90c7f522b842:/var/www/html/backdrop$ cat core/scripts/backdrop.sh
cat core/scripts/backdrop.sh
#!/usr/bin/env php<?php
/**
* Backdrop shell execution script
*
* Check for your PHP interpreter - on Windows you'll probably have to
* replace line 1 with
* #!c:/program files/php/php.exe
*
* @param path Backdrop's absolute root directory in local file system (optional).
* @param URI A URI to execute, including HTTP protocol prefix.
*/
$script= basename(array_shift($_SERVER['argv']));if(in_array('--help', $_SERVER['argv'])|| empty($_SERVER['argv'])){echo<<<EOF
Execute a Backdrop page from the shell.
Usage: {$script}[OPTIONS]"<URI>"Example: {$script}"http://mysite.org/node"All arguments are long options.
--help This page.
--root Set the working directory for the script to the specified path.
To execute Backdrop this has to be the root directory of your
Backdrop installation, f.e. /home/www/foo/backdrop (assuming
Backdrop is running on Unix). Current directory is not required.
Use surrounding quotation marks on Windows.
--verbose This option displays the options as they are set, but will
produce errors from setting the session.
URI The URI to execute, i.e. http://default/foo/bar for executing
the path '/foo/bar' in your site 'default'. URI has to be
enclosed by quotation marks if there are ampersands in it
(f.e. index.php?q=node&foo=bar). Prefix 'http://' is required,
and the domain must exist in Backdrop's sites-directory.
If the given path and file exists it will be executed directly,
i.e. if URI is set to http://default/bar/foo.php
and bar/foo.php exists, this script will be executed without
bootstrapping Backdrop. To execute Backdrop's cron.php, specify
http://default/core/cron.php as the URI.
To run this script without --root argument invoke it from the root directory
of your Backdrop installation with
./scripts/{$script}\nEOF; exit;}// define default settings
$cmd='index.php';$_SERVER['HTTP_HOST']='default';$_SERVER['PHP_SELF']='/index.php';$_SERVER['REMOTE_ADDR']='127.0.0.1';$_SERVER['SERVER_SOFTWARE']= NULL;$_SERVER['REQUEST_METHOD']='GET';$_SERVER['QUERY_STRING']='';$_SERVER['PHP_SELF']=$_SERVER['REQUEST_URI']='/';$_SERVER['HTTP_USER_AGENT']='console';// toggle verbose mode
if(in_array('--verbose', $_SERVER['argv'])){$_verbose_mode= true;}else{$_verbose_mode= false;}// parse invocation arguments
while($param= array_shift($_SERVER['argv'])){ switch ($param){case'--root':
// change working directory
$path= array_shift($_SERVER['argv']);if(is_dir($path)){ chdir($path);if($_verbose_mode){echo"cwd changed to: {$path}\n";}}else{echo"\nERROR: {$path} not found.\n\n";} break; default:
if(substr($param, 0, 2)=='--'){ // ignore unknown options
break;}else{ // parse the URI
$path= parse_url($param); // set site name
if(isset($path['host'])){$_SERVER['HTTP_HOST']=$path['host'];} // set query string
if(isset($path['query'])){$_SERVER['QUERY_STRING']=$path['query']; parse_str($path['query'], $_GET);$_REQUEST=$_GET;} // set file to execute or Backdrop path (clean URLs enabled)if(isset($path['path'])&& file_exists(substr($path['path'], 1))){$_SERVER['PHP_SELF']=$_SERVER['REQUEST_URI']=$path['path'];$cmd= substr($path['path'], 1);} elseif (isset($path['path'])){if(!isset($_GET['q'])){$_REQUEST['q']=$_GET['q']=$path['path'];}} // display setup in verbose mode
if($_verbose_mode){echo"Hostname set to: {$_SERVER['HTTP_HOST']}\n";echo"Script name set to: {$cmd}\n";echo"Path set to: {$_GET['q']}\n";}} break;}}if(file_exists($cmd)){ include $cmd;}else{echo"\nERROR: {$cmd} not found.\n\n";}exit();www-data@90c7f522b842:/var/www/html/backdrop$
It’s a php file which runs with argument --root which sets the working directory for the script to the specified path which in our case is /var/www/html/backdrop and then finally calls the site at https://localhost to check if the site is alive or not.
How we can use this as our advantage is, we can replace the /var/www/html/backdrop/index.php file with our crafted malicious php file and everytime this heartbeat check is called, our malicious script will be executed instead.
Now again start a nc listener on port 9001 and execute the script.
But before executing the script,
unshare()
Mounting a cgroupfs requires the CAP_SYS_ADMIN capability in the user namespace hosting the current cgroup namespace. By default, containers run without CAP_SYS_ADMIM, and thus cannot mount cgroupfs in the initial user namespace. But through the unshare() syscall, containers can create new user and cgroup namespaces where they possess the CAP_SYS_ADMIN capability and can mount a cgroupfs.
┌──(kali㉿kali)-[~/…/hackthebox/hackthebox/machines/carpediem]└─$ nc -lvnp 9001listening on [any]9001 ...
connect to [10.10.14.46] from (UNKNOWN)[10.10.11.167]35810root@carpediem:/# whoami
root