diff --git a/modules/auxiliary/scanner/sap/sap_icm_urlscan.rb b/modules/auxiliary/scanner/sap/sap_icm_urlscan.rb index 870936569c..7c26bbd1dc 100644 --- a/modules/auxiliary/scanner/sap/sap_icm_urlscan.rb +++ b/modules/auxiliary/scanner/sap/sap_icm_urlscan.rb @@ -3,7 +3,6 @@ # Current source: https://github.com/rapid7/metasploit-framework ## -require 'rex/proto/http' require 'msf/core' class Metasploit3 < Msf::Auxiliary @@ -30,143 +29,160 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptString.new('VERB', [true, "Verb for auth bypass testing", "HEAD"]), - OptString.new('URLFILE', [true, "SAP ICM Paths File", "sap_icm_paths.txt"]) + OptPath.new('URLFILE', [true, "SAP ICM Paths File", + File.join(Msf::Config.data_directory, 'wordlists', 'sap_icm_paths.txt')]) ], self.class) end # Base Structure of module borrowed from jboss_vulnscan def run_host(ip) - # If URLFILE is set empty, obviously the user made a silly mistake - if datastore['URLFILE'].empty? - print_error("Please specify a URLFILE") - return - end - - # Initialize the actual URLFILE path - if datastore['URLFILE'] == "sap_icm_paths.txt" - url_file = "#{Msf::Config.data_directory}/wordlists/#{datastore['URLFILE']}" - else - # Not the default sap_icm_paths file - url_file = datastore['URLFILE'] - end - - # If URLFILE path doesn't exist, no point to continue the rest of the script - if not File.exists?(url_file) - print_error("Required URL list #{url_file} was not found") - return - end - - res = send_request_cgi( + res = send_request_cgi( { 'uri' => "/" + Rex::Text.rand_text_alpha(12), 'method' => 'GET', - 'ctype' => 'text/plain', - }, 20) + }) if res print_status("Note: Please note these URLs may or may not be of interest based on server configuration") @info = [] - if not res.headers['Server'].nil? + if res.headers['Server'] @info << res.headers['Server'] print_status("#{rhost}:#{rport} Server responded with the following Server Header: #{@info[0]}") else print_status("#{rhost}:#{rport} Server responded with a blank or missing Server Header") end - if (res.body and /class="note">(.*)code:(.*)(.*)code:(.*) 0 + l = store_loot( + 'sap.icm.urls', + "text/plain", + datastore['RHOST'], + @valid_urls, + "icm_urls.txt", "SAP ICM Urls" + ) + print_line + print_good("Stored urls as loot: #{l}") if l + end end def check_url(url) + full_url = write_url(url) res = send_request_cgi({ - 'uri' => url, + 'uri' => normalize_uri(url), 'method' => 'GET', - 'ctype' => 'text/plain', - }, 20) + }) if (res) - if not @info.include?(res.headers['Server']) and not res.headers['Server'].nil? - print_good("New server header seen [#{res.headers['Server']}]") - @info << res.headers['Server'] #Add To seen server headers + if res.headers['Server'] + unless @info.include?(res.headers['Server']) + print_good("New server header seen [#{res.headers['Server']}]") + @info << res.headers['Server'] #Add To seen server headers + end end - case - when res.code == 200 - print_good("#{rhost}:#{rport} #{url} - does not require authentication (200) (length: #{res.headers['Content-Length']})") - when res.code == 403 - print_good("#{rhost}:#{rport} #{url} - restricted (403)") - when res.code == 401 - print_good("#{rhost}:#{rport} #{url} - requires authentication (401): #{res.headers['WWW-Authenticate']}") + case res.code + when 200 + print_good("#{full_url} - does not require authentication (#{res.code}) (length: #{res.headers['Content-Length']})") + @valid_urls << full_url << "\n" + when 403 + print_status("#{full_url} - restricted (#{res.code})") + when 401 + print_status("#{full_url} - requires authentication (#{res.code}): #{res.headers['WWW-Authenticate']}") + @valid_urls << full_url << "\n" # Attempt verb tampering bypass bypass_auth(url) - when res.code == 404 + when 404 # Do not return by default, only display in verbose mode - vprint_status("#{rhost}:#{rport} #{url.strip} - not found (404)") - when res.code == 500 - print_good("#{rhost}:#{rport} #{url} - produced a server error (500)") - when res.code == 301, res.code == 302 - print_good("#{rhost}:#{rport} #{url} - redirected (#{res.code}) to #{res.headers['Location']} (not following)") + vprint_status("#{full_url} - not found (#{res.code})") + when 400,500 + print_status("#{full_url} - produced a server error (#{res.code})") + when 301, 302, + print_good("#{full_url} - redirected (#{res.code}) to #{res.redirection} (not following)") + @valid_urls << full_url << "\n" + when 307 + print_status("#{full_url} - redirected (#{res.code}) to #{res.redirection} (not following)") else - vprint_status("#{rhost}:#{rport} - unhandle response code #{res.code}") + print_error("#{full_url} - unhandled response code #{res.code}") + @valid_urls << full_url << "\n" end else - print_status("#{rhost}:#{rport} #{url} - not found (No Response code Received)") + vprint_status("#{full_url} - not found (No Repsonse code Received)") end end + def write_url(path) + if datastore['SSL'] + protocol = 'https://' + else + protocol = 'http://' + end + + "#{protocol}#{rhost}:#{rport}#{path}" + end + def bypass_auth(url) - print_status("#{rhost}:#{rport} Check for verb tampering (#{datastore['VERB']})") + full_url = write_url(url) + vprint_status("#{full_url} Check for verb tampering (#{datastore['VERB']})") res = send_request_raw({ - 'uri' => url, + 'uri' => normalize_uri(url), 'method' => datastore['VERB'], 'version' => '1.0' # 1.1 makes the head request wait on timeout for some reason - }, 20) + }) - if (res and res.code == 200) - print_good("#{rhost}:#{rport} Got authentication bypass via HTTP verb tampering (length: #{res.headers['Content-Length']})") + if (res && res.code == 200) + print_good("#{full_url} Got authentication bypass via HTTP verb tampering") else - print_status("#{rhost}:#{rport} Could not get authentication bypass via HTTP verb tampering") + vprint_status("#{rhost}:#{rport} Could not get authentication bypass via HTTP verb tampering") end end + # "/urlprefix outputs the list of URL prefixes that are handled in the ABAP part of the SAP Web AS. + # This is how the message server finds out which URLs must be forwarded where. + # (SAP help) -> this disclose custom URLs that are also checked for authentication def check_urlprefixes - # "/urlprefix outputs the list of URL prefixes that are handled in the ABAP part of the SAP Web AS. This is how the message server finds out which URLs must be forwarded where." (SAP help) - # -> this disclose custom URLs that are also checked for authentication + urls = [] res = send_request_cgi({ 'uri' => "/sap/public/icf_info/urlprefix", 'method' => 'GET', - 'ctype' => 'text/plain', - }, 20) - if (res and res.code == 200) + }) + + if (res && res.code == 200) res.body.each_line do |line| if line =~ /PREFIX=/ url_enc = line.sub(/^PREFIX=/, '') + # Remove CASE and VHOST + url_enc = url_enc.sub(/&CASE=.*/, '') url_dec = URI.unescape(url_enc).sub(/;/, '') - check_url(url_dec.strip) + urls << url_dec.strip end end + else + print_error("#{rhost}:#{rport} Could not retrieve urlprefixes") end + + urls end end