CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/lib/metasploit/framework/login_scanner/snmp.rb
Views: 1904
# -*- coding: binary -*-12require 'snmp'3require 'metasploit/framework/login_scanner/base'45module Metasploit6module Framework7module LoginScanner8# This is the LoginScanner class for dealing with SNMP.9# It is responsible for taking a single target, and a list of credentials10# and attempting them. It then saves the results.11class SNMP12include Metasploit::Framework::LoginScanner::Base1314DEFAULT_TIMEOUT = 215DEFAULT_PORT = 16116DEFAULT_PROTOCOL = 'udp'.freeze17DEFAULT_VERSION = '1'.freeze18DEFAULT_QUEUE_SIZE = 10019LIKELY_PORTS = [ 161, 162 ].freeze20LIKELY_SERVICE_NAMES = [ 'snmp' ].freeze21PRIVATE_TYPES = [ :password ].freeze22REALM_KEY = nil2324attr_accessor :queued_credentials, :queued_results, :sock # :nodoc: # :nodoc: # :nodoc:2526# The SNMP version to scan27# @return [String]28attr_accessor :version2930# The SNMP protocol to use31# @return [String]32attr_accessor :protocol3334# The number of logins to try in each batch35# @return [Integer]36attr_accessor :queue_size3738validates :version,39presence: true,40inclusion: {41in: ['1', '2c', 'all']42}4344validates :protocol,45presence: true,46inclusion: {47in: ['udp', 'tcp']48}4950validates :queue_size,51presence: true,52numericality: {53only_integer: true,54greater_than_or_equal_to: 055}5657# This method returns an array of versions to scan58# @return [Array] An array of versions59def versions60case version61when '1'62[:SNMPv1]63when '2c'64[:SNMPv2c]65when 'all'66%i[SNMPv1 SNMPv2c]67end68end6970# Attempt to login with every {Credential credential} in # #cred_details.71#72# @yieldparam result [Result] The {Result} object for each attempt73# @yieldreturn [void]74# @return [void]75def scan!76valid!7778# Keep track of connection errors.79# If we encounter too many, we will stop.80consecutive_error_count = 081total_error_count = 08283successful_users = Set.new84first_attempt = true8586# Create a socket for the initial login tests (read-only)87configure_socket8889# Create a map of community name to credential object90credential_map = {}9192begin93each_credential do |credential|94# Track the credentials by community string95credential_map[credential.public] = credential9697# Skip users for whom we've have already found a password98if successful_users.include?(credential.public)99# For Pro bruteforce Reuse and Guess we need to note that we100# skipped an attempt.101if credential.parent.respond_to?(:skipped)102credential.parent.skipped = true103credential.parent.save!104end105next106end107# Queue and trigger authentication if queue size is reached108versions.each do |version|109process_logins(community: credential.public, type: 'read', version: version)110end111112# Exit early if we already have a positive result113if stop_on_success && !queued_results.empty?114break115end116end117rescue Errno::ECONNREFUSED118# Exit early if we get an ICMP port unreachable119return120end121122# Handle any unprocessed responses123process_logins(final: true)124125# Create a non-duplicated set of credentials126found_credentials = queued_results.uniq127128# Reset the queued results for our write test129self.queued_results = []130131# Grab a new socket to avoid stale replies132configure_socket133134# Try to write back the originally received values135found_credentials.each do |result|136process_logins(137version: result[:snmp_version],138community: result[:community],139type: 'write',140data: result[:proof]141)142end143144# Catch any stragglers145process_logins(final: true)146147# Mark any results from our write scan as read-write in our found credentials148queued_results.select { |r| [0, 17].include? r[:snmp_error] }.map { |r| r[:community] }.uniq.each do |c|149found_credentials.select { |r| r[:community] == c }.each do |result|150result[:access_level] = 'read-write'151end152end153154# Iterate the results155found_credentials.each do |result_options|156# Scrub the SNMP version & error code from the tracked result157result_options.delete(:snmp_version)158result_options.delete(:snmp_error)159160# Associate the community with the original credential161result_options[:credential] = credential_map[result_options.delete(:community)]162163# In the rare chance that we got a result for a community we didn't scan...164next unless result_options[:credential]165166# Create, freeze, and yield the result167result = ::Metasploit::Framework::LoginScanner::Result.new(result_options)168result.freeze169yield result if block_given?170end171172nil173ensure174shutdown_socket175end176177# Queue up and possibly send any requests, based on the queue limit and final flag178def process_logins(opts = {})179self.queued_results ||= []180self.queued_credentials ||= []181182unless opts[:final] || self.queued_credentials.length > queue_size183self.queued_credentials.push [ opts[:type], opts[:community], opts[:version], opts[:data] ]184return185end186187return if self.queued_credentials.empty?188189process_responses(0.01)190191until self.queued_credentials.empty?192action, community, version, data = self.queued_credentials.pop193case action194when 'read'195send_snmp_read_request(version, community)196when 'write'197send_snmp_write_request(version, community, data)198end199sleep_between_attempts200end201process_responses(1.0)202end203204def recv_wrapper(sock, max_size, timeout)205res = nil206if protocol == 'udp'207res = sock.recvfrom(max_size, timeout)208elsif protocol == 'tcp'209ready = ::IO.select([sock], nil, nil, timeout)210if ready211res = sock.recv_nonblock(max_size)212# Put into an array to mimic recvfrom213res = [res, host, port]214end215end216217res218end219220# Process any responses on the UDP socket and queue the results221def process_responses(timeout = 1.0)222queue = []223while (res = recv_wrapper(sock, 65535, timeout))224225# Ignore invalid responses226break if !(res[1])227228# Ignore empty responses229next if !(res[0] && !res[0].empty?)230231# Trim the IPv6-compat prefix off if needed232shost = res[1].sub(/^::ffff:/, '')233234response = parse_snmp_response(res[0])235next unless response236237self.queued_results << {238community: response[:community],239host: host,240port: port,241protocol: protocol,242service_name: 'snmp',243proof: response[:proof],244status: Metasploit::Model::Login::Status::SUCCESSFUL,245access_level: 'read-only',246snmp_version: response[:version],247snmp_error: response[:error]248}249end250end251252# Create and send a SNMP read request for sys.sysDescr.0253def send_snmp_read_request(version, community)254send_snmp_request(255create_snmp_read_sys_descr_request(version, community)256)257end258259# Create and send a SNMP write request for sys.sysDescr.0260def send_snmp_write_request(version, community, data)261send_snmp_request(262create_snmp_write_sys_descr_request(version, community, data)263)264end265266def send_wrapper(sock, pkt, host, port, flags)267if protocol == 'tcp'268return sock.send(pkt, flags)269end270271if protocol == 'udp'272return sock.sendto(pkt, host, port, 0)273end274end275276# Send a SNMP request on the existing socket277def send_snmp_request(pkt)278resend_count = 0279280begin281send_wrapper(sock, pkt, host, port, 0)282rescue ::Errno::ENOBUFS283resend_count += 1284if resend_count > MAX_RESEND_COUNT285return false286end287288::IO.select(nil, nil, nil, 0.25)289retry290rescue ::Rex::ConnectionError291# This fires for host unreachable, net unreachable, and broadcast sends292# We can safely ignore all of these for UDP sends293end294end295296# Create a SNMP request that tries to read from sys.sysDescr.0297def create_snmp_read_sys_descr_request(version_str, community)298version = version_str == :SNMPv1 ? 1 : 2299OpenSSL::ASN1::Sequence([300OpenSSL::ASN1::Integer(version - 1),301OpenSSL::ASN1::OctetString(community),302OpenSSL::ASN1::Set.new([303OpenSSL::ASN1::Integer(rand(0x80000000)),304OpenSSL::ASN1::Integer(0),305OpenSSL::ASN1::Integer(0),306OpenSSL::ASN1::Sequence([307OpenSSL::ASN1::Sequence([308OpenSSL::ASN1.ObjectId('1.3.6.1.2.1.1.1.0'),309OpenSSL::ASN1.Null(nil)310])311]),312], 0, :IMPLICIT)313]).to_der314end315316# Create a SNMP request that tries to write to sys.sysDescr.0317def create_snmp_write_sys_descr_request(version_str, community, data)318version = version_str == :SNMPv1 ? 1 : 2319snmp_write = OpenSSL::ASN1::Sequence([320OpenSSL::ASN1::Integer(version - 1),321OpenSSL::ASN1::OctetString(community),322OpenSSL::ASN1::Set.new([323OpenSSL::ASN1::Integer(rand(0x80000000)),324OpenSSL::ASN1::Integer(0),325OpenSSL::ASN1::Integer(0),326OpenSSL::ASN1::Sequence([327OpenSSL::ASN1::Sequence([328OpenSSL::ASN1.ObjectId('1.3.6.1.2.1.1.1.0'),329OpenSSL::ASN1::OctetString(data)330])331]),332], 3, :IMPLICIT)333]).to_der334end335336# Parse a SNMP reply from a packet and return a response hash or nil337def parse_snmp_response(pkt)338asn = begin339OpenSSL::ASN1.decode(pkt)340rescue StandardError341nil342end343return if !asn344345snmp_vers = begin346asn.value[0].value.to_i347rescue StandardError348nil349end350snmp_comm = begin351asn.value[1].value352rescue StandardError353nil354end355snmp_error = begin356asn.value[2].value[1].value.to_i357rescue StandardError358nil359end360snmp_data = begin361asn.value[2].value[3].value[0]362rescue StandardError363nil364end365snmp_oid = begin366snmp_data.value[0].value367rescue StandardError368nil369end370snmp_info = begin371snmp_data.value[1].value.to_s372rescue StandardError373nil374end375376return if !(snmp_error && snmp_comm && snmp_data && snmp_oid && snmp_info)377378snmp_vers = snmp_vers == 0 ? '1' : '2c'379380{ error: snmp_error, community: snmp_comm, proof: snmp_info, version: snmp_vers }381end382383# Create a new socket for this scanner384def configure_socket385shutdown_socket if sock386387self.sock = ::Rex::Socket.create({388'PeerHost' => host,389'PeerPort' => port,390'Proto' => protocol,391'Timeout' => connection_timeout,392'Context' =>393{ 'Msf' => framework, 'MsfExploit' => framework_module }394})395end396397# Close any open socket if it exists398def shutdown_socket399sock.close if sock400self.sock = nil401end402403# Sets the SNMP parameters if not specified404def set_sane_defaults405self.connection_timeout = DEFAULT_TIMEOUT if connection_timeout.nil?406self.protocol = DEFAULT_PROTOCOL if protocol.nil?407self.port = DEFAULT_PORT if port.nil?408self.version = DEFAULT_VERSION if version.nil?409self.queue_size = DEFAULT_QUEUE_SIZE if queue_size.nil?410end411412end413end414end415end416417418