# -*- coding: binary -*-
require 'rex/exploitation/obfuscatejs'
require 'rex/exploitation/encryptjs'
require 'rex/exploitation/heaplib'
require 'rex/exploitation/js'

module Msf

###
#
# This module provides methods for exploiting an HTTP client by acting
# as an HTTP server.
#
###
module Exploit::Remote::HttpServer


  include Msf::Exploit::Remote::TcpServer
  include Msf::Auxiliary::Report

  def initialize(info = {})
    super

    register_options(
      [
        OptString.new('URIPATH', [ false,  "The URI to use for this exploit (default is random)"]),
      ], Exploit::Remote::HttpServer
    )

    register_evasion_options(
      [
        OptBool.new('HTTP::no_cache', [false, 'Disallow the browser to cache HTTP content', false]),
        OptBool.new('HTTP::chunked', [false, 'Enable chunking of HTTP responses via "Transfer-Encoding: chunked"', false]),
        OptBool.new('HTTP::header_folding', [false, 'Enable folding of HTTP headers', false]),
        OptBool.new('HTTP::junk_headers', [false, 'Enable insertion of random junk HTTP headers', false]),
        OptEnum.new('HTTP::compression', [false, 'Enable compression of HTTP responses via content encoding', 'none', ['none','gzip','deflate']]),
        OptString.new('HTTP::server_name', [true, 'Configures the Server header of all outgoing replies', 'Apache'])
      ], Exploit::Remote::HttpServer
    )

    register_advanced_options([
      OptAddress.new('URIHOST', [false, 'Host to use in URI (useful for tunnels)']),
      OptPort.new('URIPORT', [false, 'Port to use in URI (useful for tunnels)']),
      OptBool.new('SendRobots', [false, 'Return a robots.txt file if asked for one', false])
    ])

    # Used to keep track of resources added to the service manager by
    # this module. see #add_resource and #cleanup
    @my_resources = []
    @service_path = nil
  end

  #
  # By default, all HTTP servers are not subject to automatic exploitation
  #
  def autofilter
    false
  end

  #
  # Thread-local client accessor
  #
  def cli
    Thread.current[:cli]
  end

  #
  # Thread-local client accessor
  #
  def cli=(cli)
    Thread.current[:cli] = cli
  end

  def print_prefix
    if cli && self.respond_to?(:stance) &&
        !(stance == Msf::Exploit::Stance::Aggressive || stance.include?(Msf::Exploit::Stance::Aggressive))
      super + "#{cli.peerhost.ljust(16)} #{self.shortname} - "
    else
      super
    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']
      raise RuntimeError, "zlib support was not detected, yet the HTTP::compression option was set.  Don't do that!"
    end
  end

  #
  # This method gives a derived class the opportunity to ensure that all
  # dependencies are present before initializing the service.
  #
  # By default, all HTTP server mixins will try to use zlib.
  #
  def check_dependencies
    use_zlib
  end

  ##
  # :category: Exploit::Remote::TcpServer overrides
  #
  # This mixin starts the HTTP server listener.  This routine takes a few
  # different hash parameters:
  #
  #   ServerHost => Override the server host to listen on (default to SRVHOST).
  #   ServerPort => Override the server port to listen on (default to SRVPORT).
  #   Uri        => The URI to handle and the associated procedure to call.
  #
  #
  # TODO: This must be able to take an SSL parameter and not rely
  # completely on the datastore. (See dlink_upnp_exec_noauth)
  def start_service(opts = {})

    check_dependencies

    # Start a new HTTP server service.
    self.service = Rex::ServiceManager.start(
      Rex::Proto::Http::Server,
      (opts['ServerPort'] || bindport).to_i,
      opts['ServerHost'] || bindhost,
      datastore['SSL'], # XXX: Should be in opts, need to test this
      {
        'Msf'        => framework,
        'MsfExploit' => self,
      },
      opts['Comm'] || _determine_server_comm(opts['ServerHost'] || bindhost),
      datastore['SSLCert'],
      datastore['SSLCompression'],
      datastore['SSLCipher'],
      datastore['SSLVersion']
    )

    self.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|
          self.cli = cli
          ( self.respond_to?(:filter_request_uri) &&
                      filter_request_uri(cli, req)
                    ) ? nil : on_request_uri(cli, req)
        },
      'Path' => opts['Path'] || resource_uri
    }.update(opts['Uri'] || {})

    proto = (datastore["SSL"] ? "https" : "http")

    # SSLCompression may or may not actually be available. For example, on
    # Ubuntu, it's disabled by default, unless the correct environment
    # variable is set. See https://github.com/rapid7/metasploit-framework/pull/2666
    if proto == "https" and datastore['SSLCompression']
      print_status("Intentionally using insecure SSL compression. Your operating system might not respect this!")
    end

    netloc = srvhost_addr
    if (proto == 'http' && srvport  != 80) || (proto == 'https' && srvport != 443)
      if Rex::Socket.is_ipv6?(netloc)
        netloc = "[#{netloc}]:#{srvport}"
      else
        netloc = "#{netloc}:#{srvport}"
      end
    end
    print_status("Using URL: #{proto}://#{netloc}#{uopts['Path']}")

    add_robots_resource if datastore['SendRobots']
    add_resource(uopts)
  end

  def add_robots_resource
    proc = Proc.new do |cli, req|
      self.cli = cli
      send_robots(cli, req)
    end

    vprint_status('Adding hardcoded URI /robots.txt')
    begin
      add_resource('Path' => '/robots.txt', 'Proc' => proc)
    rescue RuntimeError => e
      print_warning(e.message)
    end
  end

  # Set {#on_request_uri} to handle the given +uri+ in addition to the one
  # specified by the developer in opts['Path'] or by the user in URIPATH.
  #
  # @note This MUST be called from {#primer} so that the service has been set
  # up but we have not yet entered the listen/accept loop.
  #
  # @param uri [String] The resource URI that should be handled by
  #   {#on_request_uri}.
  # @return [void]
  def hardcoded_uripath(uri)
    proc = Proc.new do |cli, req|
      on_request_uri(cli, req)
    end

    vprint_status("Adding hardcoded uri #{uri}")
    begin
      add_resource({'Path' => uri, 'Proc' => proc})
    rescue RuntimeError => e
      print_error("This module requires a hardcoded uri at #{uri}. Can't run while other modules are using it.")
      raise e
    end
  end

  # Take care of removing any resources that we created
  def cleanup
    # Must dup here because remove_resource modifies @my_resources
    @my_resources.dup.each do |resource|
      remove_resource(resource)
    end

    super
  end

  #
  # Return a Hash containing a best guess at the actual browser and operating
  # system versions, based on the User-Agent header.
  #
  # Keys in the returned hash are similar to those expected of
  # Report#report_client, and Msf::DBManager#report_host namely:
  # +:ua_name+::     a brief identifier for the client, e.g. "Firefox"
  # +:ua_ver+::      the version number of the client, e.g. "3.0.11"
  # +:os_name+::     something like "Windows XP", "Windows 7", or "Linux"
  # +:os_flavor+::   something like "Enterprise", "Pro", or "Home"
  # +:os_lang+::     something like "English", "French", or "en-US"
  # +:arch+::        one of the ARCH_* constants
  #
  # Unknown values may be nil.
  #
  def fingerprint_user_agent(ua_str)

    fp = { :ua_string => ua_str }

    # Guess the browser type based on the user agent
    # Check for IE last since its often impersonated
    case (ua_str.downcase)
      # Chrome tries to look like Safari, so check it first
      when /chrome\/(\d+(:?\.\d+)*)/
        # Matches, e.g.:
        # Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3
        fp[:ua_name] = HttpClients::CHROME
        fp[:ua_ver] = $1
      when /version\/(\d+(:?\.\d+)*)\s*safari/
        fp[:ua_name] = HttpClients::SAFARI
        fp[:ua_ver] = $1
      when /firefox\/((:?[0-9]+\.)+[0-9]+)/
        fp[:ua_name] = HttpClients::FF
        fp[:ua_ver] = $1
      when /opera\/(\d+(:?\.\d+)*)/
        fp[:ua_name] = HttpClients::OPERA
        fp[:ua_ver] = $1
      when /mozilla\/[0-9]+\.[0-9] \(compatible; msie ([0-9]+\.[0-9]+)/i, /mozilla\/[0-9]+\.[0-9] \(.+ rv:([0-9]+\.[0-9])\)/i
        fp[:ua_name] = HttpClients::IE
        fp[:ua_ver] = $1
      when /git\/([0-9]+(\.[0-9]+)+)/
        fp[:ua_name] = HttpClients::GIT
        fp[:ua_ver] = $1
      when /git-lfs\/([0-9]+(\.[0-9]+)+)/
        fp[:ua_name] = HttpClients::GIT_LFS
        fp[:ua_ver] = $1
      else
        fp[:ua_name] = HttpClients::UNKNOWN
    end

    # Guess the language
    case (ua_str.downcase)
      when /(en-us|en-gb)/
        fp[:os_lang] = $1
    end

    # Guess the general OS type
    case (ua_str.downcase)
      when /windows|win32/
        fp[:os_name] = OperatingSystems::WINDOWS
        fp[:arch] = ARCH_X86
      when /linux/
        fp[:os_name] = OperatingSystems::LINUX
      when /iphone|ipad/
        fp[:os_name] = OperatingSystems::APPLE_IOS
        fp[:arch] = 'armle'
      when /mac os x/
        fp[:os_name] = OperatingSystems::MAC_OSX
      else
        fp[:os_name] = OperatingSystems::UNKNOWN
    end

    # Determine the specific OS variant

    # Note that we assume windows variants are the
    # client version and mismatch server editions.

    case (ua_str.downcase)
      when /windows 95/
        fp[:os_name] = 'Windows 95'
      when /windows 98/
        fp[:os_name] = 'Windows 98'
      when /windows nt 4/
        fp[:os_name] = 'Windows NT'
      when /windows nt 5.0/
        fp[:os_name] = 'Windows 2000'
      when /windows nt 5.1/
        fp[:os_name] = 'Windows XP'
      when /windows nt 5.2/
        fp[:os_name] = 'Windows 2003'
      when /windows nt 6.0/
        fp[:os_name] = 'Windows Vista'
      when /windows nt 6.1/
        fp[:os_name] = 'Windows 7'
      when /windows nt 6.2/
        fp[:os_name] = 'Windows 8'
      when /windows nt 6.3/
        fp[:os_name] = 'Windows 8.1'
      when /gentoo/
        fp[:os_vendor] = 'Gentoo'
      when /debian/
        fp[:os_vendor] = 'Debian'
      when /ubuntu/
        fp[:os_vendor] = 'Ubuntu'
      when /fedora/
        fp[:os_vendor] = 'Fedora'
      when /red hat|rhel/
        fp[:os_vendor] = 'RHEL'
      when /android/
        fp[:os_name] = OperatingSystems::ANDROID
    end

    # Guess the architecture
    case (ua_str.downcase)
      when /ppc/
        fp[:arch] = ARCH_PPC
      when /x64|x86_64/
        fp[:arch] = ARCH_X64
      when /i.86|wow64/
        # WOW64 means "Windows on Windows64" and is present
        # in the useragent of 32-bit IE running on 64-bit
        # Windows
        fp[:arch] = ARCH_X86
      when /android|iphone|ipod|ipad/
        fp[:arch] = ARCH_ARMLE
      else
        fp[:arch] = ARCH_X86
    end

    fp
  end

  #
  # Store the results of server-side User-Agent fingerprinting in the DB.
  #
  # Returns a Hash containing host and client information.
  #
  def report_user_agent(address, request, client_opts={})
    fp = fingerprint_user_agent(request["User-Agent"])
    host = {
      :address   => address,
      :host      => address,
    }
    host[:os_name]   = fp[:os_name]   if fp[:os_name]
    host[:os_flavor] = fp[:os_flavor] if fp[:os_flavor]
    host[:arch]      = fp[:arch]      if fp[:arch]
    host[:os_lang]   = fp[:os_lang]   if fp[:os_lang]
    report_host(host)
    client = {
      :host      => address,
      :ua_string => request['User-Agent'],
    }
    client[:ua_name] = fp[:ua_name] if fp[:ua_name]
    client[:ua_ver]  = fp[:ua_ver]  if fp[:ua_ver]
    client.merge!(client_opts) if client_opts
    report_client(client)
    report_note(
      :host => address,
      :type => 'http.request',
      :data => "#{address}: #{request.method} #{request.resource} #{client[:os_name]} #{client[:ua_name]} #{client[:ua_ver]}",
      :update => :unique_data
    )
    return host.merge(client)
  end

  #
  # Adds a URI resource using the supplied hash parameters.
  #
  #   Path     => The path to associate the procedure with.
  #   Proc     => The procedure to call when the URI is requested.
  #   LongCall => Indicates that the request is a long call.
  #
  # NOTE: Calling #add_resource will change the results of subsequent calls
  # to #get_resource!
  #
  # @return (see Rex::Service#add_resource)
  def add_resource(opts)
    @service_path = opts['Path']
    res = service.add_resource(opts['Path'], opts)

    # This has to go *after* the call to service.add_resource in case
    # the service manager doesn't like it for some reason and raises.
    @my_resources.push(opts['Path'])

    res
  end

  #
  # Returns the last-used resource path
  #
  def get_resource
    # We don't want modules modifying their service_path inadvertently, so
    # give them a dup.  Can be nil during module setup.
    @service_path ? @service_path.dup : nil
  end

  #
  # Return a full url of the form <tt>http://1.1.1.1:8080/resource/</tt>
  #
  # The address portion should be something a client would be able to route,
  # but see {#srvhost_addr} for caveats.
  #
  def get_uri(cli=self.cli)
    resource = get_resource

    # The resource won't exist until the server is started
    return unless resource

    ssl = !!(datastore["SSL"])
    proto = (ssl ? "https://" : "http://")
    if datastore['URIHOST']
      host = datastore['URIHOST']
    elsif (cli and cli.peerhost)
      host = Rex::Socket.source_address(cli.peerhost)
    else
      host = srvhost_addr
    end

    if Rex::Socket.is_ipv6?(host)
      host = "[#{host}]"
    end

    if datastore['URIPORT'] && datastore['URIPORT'] != 0
      port = ':' + datastore['URIPORT'].to_s
    elsif (ssl and datastore["SRVPORT"] == 443)
      port = ''
    elsif (!ssl and datastore["SRVPORT"] == 80)
      port = ''
    else
      port = ":" + datastore["SRVPORT"].to_s
    end

    uri = proto + host + port + resource

    uri
  end

  #
  # An address to which the client can route.
  #
  # If available, return LHOST which should be the right thing since it
  # already has to be an address the client can route to for the payload to
  # work.  However, LHOST will only be available if we're using a reverse_*
  # payload, so if we don't have it, try to use the client's peerhost
  # address.  Failing that, fall back to the addr with the default gateway.
  # All of this will be for naught in the case of a user behind NAT using a
  # bind payload but there's nothing we can do about it.
  #
  # NOTE: The address will be *incorrect* in the following two situations:
  # 1. LHOST is pointed at a exploit/multi/handler on some other box.
  # 2. SRVHOST has a value of '0.0.0.0', the user is behind NAT, and we're
  #    using a bind payload.  In that case, we don't have an LHOST and
  #    the source address will be internal.
  #
  # This can potentially be dealt with in a module by using the Host header
  # from a request if such a header exists.
  #
  # @return [String]
  def srvhost_addr
    if datastore['URIHOST']
      host = datastore['URIHOST']
    elsif (datastore['LHOST'] and (!datastore['LHOST'].strip.empty?))
      host = datastore["LHOST"]
    else
      if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::")
        if (respond_to?(:sock) and sock and sock.peerhost)
          # Then this is a Passive-Aggressive module. It has a socket
          # connected to the remote server from which we can deduce the
          # appropriate source address.
          host = Rex::Socket.source_address(sock.peerhost)
        else
          # Otherwise, this module is only a server, not a client, *and*
          # the payload does not have an LHOST option. This can happen,
          # for example, with a browser exploit using a download-exec
          # payload. In that case, just use the address of the interface
          # with the default gateway and hope for the best.
          host = Rex::Socket.source_address
        end
      else
        host = datastore['SRVHOST']
      end
    end

    host
  end

  #
  # Returns the local port that is being listened on.
  #
  def srvport
    if datastore['URIPORT']
      port = datastore['URIPORT']
    else
      port = datastore['SRVPORT']
    end

    port
  end

  #
  # Removes a URI resource.
  #
  def remove_resource(name)
    # Guard against removing resources added by other modules
    if @my_resources.include?(name)
      @my_resources.delete(name)
      service.remove_resource(name) if service
    end
  end

  #
  # Closes a client connection.
  #
  def close_client(cli)
    service.close_client(cli)
  end

  #
  # Creates an HTTP response packet.
  #
  def create_response(code = 200, message = "OK", proto = Rex::Proto::Http::DefaultProtocol)
    res = Rex::Proto::Http::Response.new(code, message, proto);
    res['Content-Type'] = 'text/html'
    res
  end

  #
  # Transmits a response to the supplied client, default content-type is text/html
  #
  # Payload evasions are implemented here!
  #
  def send_response(cli, body, headers = {})
    response = create_response
    response['Content-Type'] = 'text/html'
    response.body = body.to_s.unpack("C*").pack("C*")

    if (datastore['HTTP::compression'])
      self.use_zlib # make sure...
      response.compress = datastore['HTTP::compression']
    end

    if datastore['HTTP::chunked']
      response.auto_cl = false
      response.transfer_chunked = true
    end

    if datastore['HTTP::header_folding']
      response.headers.fold = 1
    end

    if datastore['HTTP::junk_headers']
      response.headers.junk_headers = 1
    end

    if datastore['HTTP::no_cache']
      response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate'
    end

    headers.each_pair { |k,v| response[k] = v }

    cli.send_response(response)
  end

  #
  # Sends a 302 redirect to the client
  #
  def send_redirect(cli, location='/', body='', headers = {})
    response = create_response(302, 'Moved')
    response['Content-Type'] = 'text/html'
    response['Location'] = location
    response.body = body.to_s.unpack("C*").pack("C*")
    headers.each_pair { |k,v| response[k] = v }

    cli.send_response(response)
  end


  #
  # Sends a 302 redirect relative to our base path
  #
  def send_local_redirect(cli, location)
    send_redirect(cli, get_resource + location)
  end


  #
  # Sends a 404
  #
  def send_not_found(cli)
    resp_404 = create_response(404, 'Not Found')
    resp_404.body = %Q{\
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
<hr>
<address>Apache/2.2.9 (Unix) Server at #{datastore['LHOST']} Port #{datastore['SRVPORT']}</address>
</body></html>
}

    cli.send_response(resp_404)
  end

  #
  # Sends a canned robots.txt file
  #
  def send_robots(cli, request)
    print_status('Sending robots.txt')
    robots = create_response(200, 'Success')
    robots['Content-Type'] = 'text/plain'

    robots.body = %Q{\
User-agent: *
Disallow: /
}

    cli.send_response(robots)
  end


  #
  # Returns the configured (or random, if not configured) URI path
  #
  def resource_uri
    path = datastore['URIPATH'] || random_uri
    path = '/' + path if path !~ /^\//
    return path
  end


  #
  # Generates a random URI for use with making finger printing more
  # challenging.
  #
  def random_uri
    "/" + Rex::Text.rand_text_alphanumeric(rand(10) + 6)
  end

  #
  # Re-generates the payload, substituting the current RHOST and RPORT with
  # the supplied client host and port.
  #
  def regenerate_payload(cli, arch = nil, platform = nil, target = nil)
    pcode = nil

    # If the payload fails to generate for some reason, send a 403.
    if ((pcode = super(cli, arch, platform, target)) == nil)
      print_error("Failed to generate payload, sending 403.")

      cli.send_response(
        create_response(403, 'Forbidden'))

      return nil
    end
    pcode
  end

  ##
  #
  # Override methods
  #
  ##

  #
  # Called when a request is made to a single URI registered during the
  # start_service.  Subsequent registrations will not result in a call to
  # on_request_uri.
  #
  # Modules should override this method.
  #
  def on_request_uri(cli, request)
  end

  # allow this module to be patched at initialization-time
  Metasploit::Concern.run(self)
end

end
