Land #3370, cleanup for sap_icm_urlscan
commit
bc64e47698
|
@ -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:(.*)</i.match(res.body) )
|
||||
if (res.body && /class="note">(.*)code:(.*)</i.match(res.body) )
|
||||
print_error("#{rhost}:#{rport} SAP ICM error message: #{$2}")
|
||||
end
|
||||
|
||||
# Load URLs
|
||||
urls_to_check = []
|
||||
File.open(url_file) do |f|
|
||||
urls_to_check = check_urlprefixes
|
||||
File.open(datastore['URLFILE']) do |f|
|
||||
f.each_line do |line|
|
||||
urls_to_check.push line
|
||||
end
|
||||
end
|
||||
|
||||
print_status("#{rhost}:#{rport} Beginning URL check")
|
||||
@valid_urls = ''
|
||||
urls_to_check.each do |url|
|
||||
check_url(url.strip)
|
||||
end
|
||||
# check custom URLs
|
||||
check_urlprefixes
|
||||
else
|
||||
print_error("#{rhost}:#{rport} No response received")
|
||||
end
|
||||
|
||||
if @valid_urls.length > 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
|
||||
|
|
Loading…
Reference in New Issue