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/base.rb
Views: 1904
require 'metasploit/framework/login_scanner'12module Metasploit3module Framework4module LoginScanner56# This module provides the base behaviour for all of7# the LoginScanner classes. All of the LoginScanners8# should include this module to establish base behaviour9module Base10extend ActiveSupport::Concern11include ActiveModel::Validations1213included do14# @!attribute framework15# @return [Object] The framework instance object16attr_accessor :framework17# @!attribute framework_module18# @return [Object] The framework module caller, if available19attr_accessor :framework_module20# @!attribute connection_timeout21# @return [Integer] The timeout in seconds for a single SSH connection22attr_accessor :connection_timeout23# @!attribute cred_details24# @return [CredentialCollection] Collection of Credential objects25attr_accessor :cred_details26# @!attribute host27# @return [String] The IP address or hostname to connect to28attr_accessor :host29# @!attribute port30# @return [Integer] The port to connect to31attr_accessor :port32# @!attribute host33# @return [String] The local host for outgoing connections34attr_accessor :local_host35# @!attribute port36# @return [Integer] The local port for outgoing connections37attr_accessor :local_port38# @!attribute proxies39# @return [String] The proxy directive to use for the socket40attr_accessor :proxies41# @!attribute stop_on_success42# @return [Boolean] Whether the scanner should stop when it has found one working Credential43attr_accessor :stop_on_success44# @!attribute bruteforce_speed45# @return [Integer] The desired speed, with 5 being 'fast' and 0 being 'slow.'46attr_accessor :bruteforce_speed4748validates :connection_timeout,49presence: true,50numericality: {51only_integer: true,52greater_than_or_equal_to: 153}5455validates :cred_details, presence: true5657validates :host, presence: true5859validates :port,60presence: true,61numericality: {62only_integer: true,63greater_than_or_equal_to: 1,64less_than_or_equal_to: 6553565}6667validates :stop_on_success,68inclusion: { in: [true, false] }6970validates :bruteforce_speed,71numericality: {72allow_nil: true,73only_integer: true,74greater_than_or_equal_to: 0,75less_than_or_equal_to: 576}7778validate :host_address_must_be_valid7980validate :validate_cred_details8182# @param attributes [Hash{Symbol => String,nil}]83def initialize(attributes={})84attributes.each do |attribute, value|85public_send("#{attribute}=", value)86end87set_sane_defaults88end8990# Attempt a single login against the service with the given91# {Credential credential}.92#93# @param credential [Credential] The credential object to attempt to94# login with95# @return [Result] A Result object indicating success or failure96# @abstract Protocol-specific scanners must implement this for their97# respective protocols98def attempt_login(credential)99raise NotImplementedError100end101102# @note Override this to detect that the service is up, is the right103# version, etc.104# @return [false] Indicates there were no errors105# @return [String] a human-readable error message describing why106# this scanner can't run107def check_setup108false109end110111# @note Override this to set a timeout that makes more sense for112# your particular protocol. Telnet already usually takes a really113# long time, while MSSQL is often lickety-split quick. If114# overridden, the override should probably do something sensible115# with {#bruteforce_speed}116#117# @return [Integer] a number of seconds to sleep between attempts118def sleep_time119case bruteforce_speed120when 0; 60 * 5121when 1; 15122when 2; 1123when 3; 0.5124when 4; 0.1125else; 0126end127end128129# A threadsafe sleep method130#131# @param time [Integer] number of seconds (can be a Float), defaults132# to {#sleep_time}133#134# @return [void]135def sleep_between_attempts(time=self.sleep_time)136::IO.select(nil,nil,nil,time) unless sleep_time.zero?137end138139def each_credential140cred_details.each do |raw_cred|141142# This could be a Credential object, or a Credential Core, or an Attempt object143# so make sure that whatever it is, we end up with a Credential.144credential = raw_cred.to_credential145146if credential.realm.present? && self.class::REALM_KEY.present?147# The class's realm_key will always be the right thing for the148# service it knows how to login to. Override the credential's149# realm_key if one exists for the class. This can happen for150# example when we have creds for DB2 and want to try them151# against Postgres.152credential.realm_key = self.class::REALM_KEY153yield credential154elsif credential.realm.blank? && self.class::REALM_KEY.present? && self.class::DEFAULT_REALM.present?155# XXX: This is messing up the display for mssql when not using156# Windows authentication, e.g.:157# [+] 10.0.0.53:1433 - LOGIN SUCCESSFUL: WORKSTATION\sa:msfadmin158# Realm gets ignored in that case, so it still functions, it159# just gives the user bogus info160credential.realm_key = self.class::REALM_KEY161credential.realm = self.class::DEFAULT_REALM162yield credential163elsif credential.realm.present? && self.class::REALM_KEY.blank?164second_cred = credential.dup165# This service has no realm key, so the realm will be166# meaningless. Strip it off.167credential.realm = nil168credential.realm_key = nil169yield credential170# Some services can take a domain in the username like this even though171# they do not explicitly take a domain as part of the protocol.172# e.g., telnet173second_cred.public = "#{second_cred.realm}\\#{second_cred.public}"174second_cred.realm = nil175second_cred.realm_key = nil176yield second_cred177else178yield credential179end180end181end182183# Attempt to login with every {Credential credential} in184# {#cred_details}, by calling {#attempt_login} once for each.185#186# If a successful login is found for a user, no more attempts187# will be made for that user.188#189# @yieldparam result [Result] The {Result} object for each attempt190# @yieldreturn [void]191# @return [void]192def scan!193valid!194195# Keep track of connection errors.196# If we encounter too many, we will stop.197consecutive_error_count = 0198total_error_count = 0199200successful_users = Set.new201ignored_users = Set.new202first_attempt = true203204each_credential do |credential|205# Skip users for whom we've have already found a password206if successful_users.include?(credential.public)207# For Pro bruteforce Reuse and Guess we need to note that we208# skipped an attempt.209if credential.parent.respond_to?(:skipped)210credential.parent.skipped = true211credential.parent.save!212end213next214end215216# Users that went into the lock-out list217if ignored_users.include?(credential.public)218if credential.parent.respond_to?(:skipped)219credential.parent.skipped = true220end221next222end223224if first_attempt225first_attempt = false226else227sleep_between_attempts228end229230result = attempt_login(credential)231result.freeze232233yield result if block_given?234235if result.success?236consecutive_error_count = 0237successful_users << credential.public238break if stop_on_success239elsif result.status == Metasploit::Model::Login::Status::LOCKED_OUT240ignored_users << credential.public241elsif result.status == Metasploit::Model::Login::Status::INVALID_PUBLIC_PART242ignored_users << credential.public243elsif result.status == Metasploit::Model::Login::Status::DISABLED244ignored_users << credential.public245else246if result.status == Metasploit::Model::Login::Status::UNABLE_TO_CONNECT247consecutive_error_count += 1248total_error_count += 1249break if consecutive_error_count >= 3250break if total_error_count >= 10251end252end253rescue => e254if framework_module255prefix = framework_module.respond_to?(:peer) ? "#{framework_module.peer} - LOGIN FAILED:" : "LOGIN FAILED:"256framework_module.print_warning("#{prefix} #{credential.to_h} - Unhandled error - scan may not produce correct results: #{e.message} - #{e.backtrace}")257end258elog("Scan Error: #{e.message}", error: e)259consecutive_error_count += 1260total_error_count += 1261break if consecutive_error_count >= 3262break if total_error_count >= 10263end264nil265end266267# Raise an exception if this scanner's attributes are not valid.268#269# @raise [Invalid] if the attributes are not valid on this scanner270# @return [void]271def valid!272unless valid?273raise Metasploit::Framework::LoginScanner::Invalid.new(self)274end275nil276end277278279private280281# This method validates that the host address is both282# of a valid type and is resolveable.283# @return [void]284def host_address_must_be_valid285if host.kind_of? String286begin287resolved_host = ::Rex::Socket.getaddress(host, true)288if host =~ /^\d{1,3}(\.\d{1,3}){1,3}$/289unless host =~ Rex::Socket::MATCH_IPV4290errors.add(:host, "could not be resolved")291end292end293self.host = resolved_host294rescue295errors.add(:host, "could not be resolved")296end297else298errors.add(:host, "must be a string")299end300end301302# This is a placeholder method. Each LoginScanner class303# will override this with any sane defaults specific to304# its own behaviour.305# @abstract306# @return [void]307def set_sane_defaults308self.connection_timeout = 30 if self.connection_timeout.nil?309end310311# This method validates that the credentials supplied312# are all valid.313# @return [void]314def validate_cred_details315unless cred_details.respond_to? :each316errors.add(:cred_details, "must respond to :each")317end318319if cred_details.empty?320errors.add(:cred_details, "can't be blank")321end322end323324end325326327end328329end330end331end332333334