lib/msf/core/auxiliary/auth_brute.rb
# -*- coding: binary -*-
module Msf
###
#
# This module provides methods for brute forcing authentication
#
###
module Auxiliary::AuthBrute
include Msf::Auxiliary::LoginScanner
def initialize(info = {})
super
register_options([
OptString.new('USERNAME', [ false, 'A specific username to authenticate as' ]),
OptString.new('PASSWORD', [ false, 'A specific password to authenticate with' ]),
OptPath.new('USER_FILE', [ false, "File containing usernames, one per line" ]),
OptPath.new('PASS_FILE', [ false, "File containing passwords, one per line" ]),
OptPath.new('USERPASS_FILE', [ false, "File containing users and passwords separated by space, one pair per line" ]),
OptInt.new('BRUTEFORCE_SPEED', [ true, "How fast to bruteforce, from 0 to 5", 5]),
OptBool.new('VERBOSE', [ true, "Whether to print output for all attempts", true]),
OptBool.new('BLANK_PASSWORDS', [ false, "Try blank passwords for all users", false]),
OptBool.new('USER_AS_PASS', [ false, "Try the username as the password for all users", false]),
OptBool.new('DB_ALL_CREDS', [false,"Try each user/password couple stored in the current database",false]),
OptBool.new('DB_ALL_USERS', [false,"Add all users in the current database to the list",false]),
OptBool.new('DB_ALL_PASS', [false,"Add all passwords in the current database to the list",false]),
OptEnum.new('DB_SKIP_EXISTING', [false,"Skip existing credentials stored in the current database", 'none', %w[ none user user&realm ]]),
OptBool.new('STOP_ON_SUCCESS', [ true, "Stop guessing when a credential works for a host", false]),
OptBool.new('ANONYMOUS_LOGIN', [ true, "Attempt to login with a blank username and password", false])
], Auxiliary::AuthBrute)
register_advanced_options([
OptBool.new('REMOVE_USER_FILE', [ true, "Automatically delete the USER_FILE on module completion", false]),
OptBool.new('REMOVE_PASS_FILE', [ true, "Automatically delete the PASS_FILE on module completion", false]),
OptBool.new('REMOVE_USERPASS_FILE', [ true, "Automatically delete the USERPASS_FILE on module completion", false]),
OptBool.new('PASSWORD_SPRAY', [true, "Reverse the credential pairing order. For each password, attempt every possible user.", false]),
OptInt.new('TRANSITION_DELAY', [false, "Amount of time (in minutes) to delay before transitioning to the next user in the array (or password when PASSWORD_SPRAY=true)", 0]),
OptInt.new('MaxGuessesPerService', [ false, "Maximum number of credentials to try per service instance. If set to zero or a non-number, this option will not be used.", 0]), # Tracked in @@guesses_per_service
OptInt.new('MaxMinutesPerService', [ false, "Maximum time in minutes to bruteforce the service instance. If set to zero or a non-number, this option will not be used.", 0]), # Tracked in @@brute_start_time
OptInt.new('MaxGuessesPerUser', [ false, %q{
Maximum guesses for a particular username for the service instance.
Note that users are considered unique among different services, so a
user at 10.1.1.1:22 is different from one at 10.2.2.2:22, and both will
be tried up to the MaxGuessesPerUser limit. If set to zero or a non-number,
this option will not be used.}.gsub(/[\t\r\n\s]+/nm,"\s"), 0]) # Tracked in @@brute_start_time
], Auxiliary::AuthBrute)
end
# Build a new CredentialCollection instance configured based on the datastore options. Any options passed in will take
# precedence over the datastore. Usernames and passwords will be prepended to the credential collection if their
# respective datastore options are configured appropriately. Finally the resulting CredentialCollection will be
# configured to perform any necessary filtering per the DB_SKIP_EXISTING option.
#
# @param [Hash] opts the options with which to build the CredentialCollection instance
# @return [Metasploit::Framework::CredentialCollection] the built CredentialCollection
def build_credential_collection(opts)
cred_collection = Metasploit::Framework::CredentialCollection.new({
blank_passwords: datastore['BLANK_PASSWORDS'],
pass_file: datastore['PASS_FILE'],
user_file: datastore['USER_FILE'],
userpass_file: datastore['USERPASS_FILE'],
user_as_pass: datastore['USER_AS_PASS'],
password_spray: datastore['PASSWORD_SPRAY']
}.merge(opts))
if framework.db.active
cred_collection = prepend_db_usernames(cred_collection)
cred_collection = prepend_db_passwords(cred_collection)
else
ignored = %w{ DB_ALL_CREDS DB_ALL_PASS DB_ALL_USERS }.select { |option| datastore[option] }
ignored << 'DB_SKIP_EXISTING' unless datastore['DB_SKIP_EXISTING'].blank? || datastore['DB_SKIP_EXISTING'] == 'none'
unless ignored.empty?
print_warning("No active DB -- The following option#{ ignored.length == 1 ? '' : 's'} will be ignored: #{ ignored.join(', ') }")
end
end
# only define the filter if any filtering needs to take place
unless datastore['DB_SKIP_EXISTING'].blank? || datastore['DB_SKIP_EXISTING'] == 'none'
cred_collection.filter = -> (cred) do
return true unless datastore['DB_SKIP_EXISTING']
return true unless framework.db.active
opts = { workspace: myworkspace.name }
opts[:type] =
case cred.private_type
when :ntlm_hash
'Metasploit::Credential::NTLMHash'
when :password
'Metasploit::Credential::Password'
when :ssh_key
'Metasploit::Credential::SSHKey'
else
return true # not a private type that we can filter on
end
case datastore['DB_SKIP_EXISTING']
when 'user'
opts[:user] = cred.public
when 'user&realm'
opts[:user] = cred.public
opts[:realm] = cred.realm
else
return true
end
# cred[@public, @private, @private_type[:password], @realm]
framework.db.creds(opts).length == 0
end
end
cred_collection
end
def setup
@@max_per_service = nil
end
# Yields each Metasploit::Credential::Core in the Mdm::Workspace with
# a private type of 'ntlm_hash'
#
# @yieldparam [Metasploit::Credential::Core]
def each_ntlm_cred
creds = framework.db.creds(type: 'Metasploit::Credential::NTLMHash', workspace: myworkspace.name)
creds.each do |cred|
yield cred
end
end
# Yields each Metasploit::Credential::Core in the Mdm::Workspace with
# a private type of 'password'
#
# @yieldparam [Metasploit::Credential::Core]
def each_password_cred
creds = framework.db.creds(type: 'Metasploit::Credential::Password', workspace: myworkspace.name)
creds.each do |cred|
yield cred
end
end
# Yields each Metasploit::Credential::Core in the Mdm::Workspace with
# a private type of 'ssh_key'
#
# @yieldparam [Metasploit::Credential::Core]
def each_ssh_cred
creds = framework.db.creds(type: 'Metasploit::Credential::SSHKey', workspace: myworkspace.name)
creds.each do |cred|
yield cred
end
end
# Yields each Metasploit::Credential::Core in the Mdm::Workspace with
# a private type of 'nil'
#
# @yieldparam [Metasploit::Credential::Core]
def each_username_cred
creds = framework.db.creds(type: nil, workspace: myworkspace.name)
creds.each do |cred|
yield cred
end
end
# Checks whether we should be adding creds from the DB to a CredCollection
#
# @return [TrueClass] if any of the datastore options for db creds are selected and the db is active
# @return [FalseClass] if none of the datastore options are selected OR the db is not active
def prepend_db_creds?
(datastore['DB_ALL_CREDS'] || datastore['DB_ALL_PASS'] || datastore['DB_ALL_USERS']) && framework.db.active
end
# This method takes a Metasploit::Framework::CredentialCollection and prepends existing NTLMHashes
# from the database. This allows the users to use the DB_ALL_CREDS option.
#
# @param cred_collection [Metasploit::Framework::CredentialCollection]
# the credential collection to add to
# @return [Metasploit::Framework::CredentialCollection] the modified Credentialcollection
def prepend_db_hashes(cred_collection)
if prepend_db_creds?
each_ntlm_cred do |cred|
process_cred_for_collection(cred_collection,cred)
end
end
cred_collection
end
# This method takes a Metasploit::Framework::CredentialCollection and prepends existing SSHKeys
# from the database. This allows the users to use the DB_ALL_CREDS option.
#
# @param [Metasploit::Framework::CredentialCollection] cred_collection
# the credential collection to add to
# @return [Metasploit::Framework::CredentialCollection] cred_collection the modified Credentialcollection
def prepend_db_keys(cred_collection)
if prepend_db_creds?
each_ssh_cred do |cred|
process_cred_for_collection(cred_collection,cred)
end
end
cred_collection
end
# This method takes a Metasploit::Framework::CredentialCollection and prepends existing Password Credentials
# from the database. This allows the users to use the DB_ALL_CREDS option.
#
# @param cred_collection [Metasploit::Framework::CredentialCollection]
# the credential collection to add to
# @return [Metasploit::Framework::CredentialCollection] the modified Credentialcollection
def prepend_db_passwords(cred_collection)
if prepend_db_creds?
each_password_cred do |cred|
process_cred_for_collection(cred_collection,cred)
end
end
cred_collection
end
# This method takes a Metasploit::Framework::CredentialCollection and prepends existing Usernames
# from the database. This allows the users to use the DB_ALL_USERS option.
#
# @param cred_collection [Metasploit::Framework::CredentialCollection]
# the credential collection to add to
# @return [Metasploit::Framework::CredentialCollection] the modified Credentialcollection
def prepend_db_usernames(cred_collection)
if prepend_db_creds?
each_username_cred do |cred|
process_cred_for_collection(cred_collection,cred)
end
end
cred_collection
end
# Takes a Metasploit::Credential::Core and converts it into a
# Metasploit::Framework::Credential and processes it into the
# Metasploit::Framework::CredentialCollection as dictated by the
# selected datastore options.
#
# @param [Metasploit::Framework::CredentialCollection] cred_collection the credential collection to add to
# @param [Metasploit::Credential::Core] cred the credential to process
def process_cred_for_collection(cred_collection, cred)
msf_cred = cred.to_credential
cred_collection.prepend_cred(msf_cred) if datastore['DB_ALL_CREDS']
cred_collection.add_private(msf_cred.private) if datastore['DB_ALL_PASS']
cred_collection.add_public(msf_cred.public) if datastore['DB_ALL_USERS']
end
# Checks all three files for usernames and passwords, and combines them into
# one credential list to apply against the supplied block. The block (usually
# something like do_login(user,pass) ) is responsible for actually recording
# success and failure in its own way; each_user_pass() will only respond to
# a return value of :done (which will signal to end all processing) and
# to :next_user (which will cause that username to be skipped for subsequent
# password guesses). Other return values won't affect the processing of the
# list.
#
# The 'noconn' argument should be set to true if each_user_pass is merely
# iterating over the usernames and passwords and should not respect
# bruteforce_speed as a delaying factor.
def each_user_pass(noconn=false,&block)
this_service = [datastore['RHOST'],datastore['RPORT']].join(":")
fq_rest = [this_service,"all remaining users"].join(":")
# This should kinda halfway be in setup, halfway in run... need to
# revisit this.
unless credentials ||= false # Assignment and comparison!
credentials ||= build_credentials_array()
credentials = adjust_credentials_by_max_user(credentials)
this_service = [datastore['RHOST'],datastore['RPORT']].join(":")
initialize_class_variables(this_service,credentials)
end
prev_iterator = nil
credentials.each do |u, p|
# Explicitly be able to set a blank (zero-byte) username by setting the
# username to <BLANK>. It's up to the caller to handle this if it's not
# allowed or if there's any special handling needed (such as smb_login).
u = "" if u =~ /^<BLANK>$/i
break if @@credentials_skipped[fq_rest]
fq_user = [this_service,u].join(":")
# Set noconn to indicate that in this case, each_user_pass
# is not actually kicking off a connection, so the
# bruteforce_speed datastore should be ignored.
if not noconn
userpass_sleep_interval unless @@credentials_tried.empty?
end
next if @@credentials_skipped[fq_user]
next if @@credentials_tried[fq_user] == p
# Used for tracking if we should TRANSITION_DELAY
# If the current user/password values don't match the previous iteration we know
# we've made it through all of the records for that iteration and should start the delay.
if ![u,p].include?(prev_iterator)
unless prev_iterator.nil? # Prevents a delay on the first run through
if datastore['TRANSITION_DELAY'] > 0
vprint_status("Delaying #{datastore['TRANSITION_DELAY']} minutes before attempting next iteration.")
sleep datastore['TRANSITION_DELAY'] * 60
end
end
prev_iterator = datastore['PASSWORD_SPRAY'] ? p : u # Update the iterator
end
ret = block.call(u, p)
case ret
when :abort # Skip the current host entirely.
abort_msg = {
:level => :error,
:ip => datastore['RHOST'],
:port => datastore['RPORT'],
:msg => "Bruteforce cancelled against this service."
}
unless datastore['VERBOSE']
abort_msg[:msg] << " Enable verbose output for service-specific details."
end
print_brute abort_msg
break
when :next_user # This means success for that user.
@@credentials_skipped[fq_user] = p
if datastore['STOP_ON_SUCCESS'] # See?
@@credentials_skipped[fq_rest] = true
end
when :skip_user # Skip the user in non-success cases.
@@credentials_skipped[fq_user] = p
when :connection_error # Report an error, skip this cred, but don't neccisarily abort.
print_brute(
:level => :verror,
:ip => datastore['RHOST'],
:port => datastore['RPORT'],
:msg => "Connection error, skipping '#{u}':'#{p}'")
end
@@guesses_per_service[this_service] ||= 1
@@credentials_tried[fq_user] = p
if counters_expired? this_service,credentials
break
else
@@guesses_per_service[this_service] += 1
end
end
end
def counters_expired?(this_service,credentials)
expired_cred = false
expired_time = false
# Workaround for cases where multiple auth_brute modules are running concurrently and
# someone stomps on the @max_per_service class variable during setup.
current_max_per_service = self.class.class_variable_get("@@max_per_service") rescue nil
return false unless current_max_per_service
if @@guesses_per_service[this_service] >= (@@max_per_service)
if @@max_per_service < credentials.size
print_brute(
:level => :vstatus,
:ip => datastore['RHOST'],
:port => datastore['RPORT'],
:msg => "Hit maximum guesses for this service (#{@@max_per_service}).")
expired_cred = true
end
end
seconds_to_run = datastore['MaxMinutesPerService'].to_i.abs * 60
if seconds_to_run > 0
if Time.now.utc.to_i > @@brute_start_time.to_i + seconds_to_run
print_brute(
:level => :vstatus,
:ip => datastore['RHOST'],
:port => datastore['RPORT'],
:msg => "Hit timeout for this service at #{seconds_to_run / 60}m.")
expired_time = true
end
end
expired_cred || expired_time
end
# If the user passed a memory location for credential gen, assume
# that that's precisely what's desired -- no other transforms or
# additions or uniqueness should be done. Otherwise, perform
# the usual alterations.
def build_credentials_array
credentials = extract_word_pair(datastore['USERPASS_FILE'])
translate_proto_datastores()
return credentials if datastore['USERPASS_FILE'] =~ /^memory:/
users = load_user_vars(credentials)
passwords = load_password_vars(credentials)
cleanup_files()
if datastore['USER_AS_PASS']
credentials = gen_user_as_password(users, credentials)
end
if datastore['BLANK_PASSWORDS']
credentials = gen_blank_passwords(users, credentials)
end
if framework.db.active
if datastore['DB_ALL_CREDS']
framework.db.creds(workspace: myworkspace.name).each do |o|
credentials << [o.public.username, o.private.data] if o.private && o.private.type =~ /password/i
end
end
if datastore['DB_ALL_USERS']
framework.db.creds(workspace: myworkspace.name).each do |o|
users << o.public.username if o.public
end
end
if datastore['DB_ALL_PASS']
framework.db.creds(workspace: myworkspace.name).each do |o|
passwords << o.private.data if o.private && o.private.type =~ /password/i
end
end
end
credentials.concat(combine_users_and_passwords(users, passwords))
credentials.uniq!
credentials = just_uniq_users(credentials) if @strip_passwords
credentials = just_uniq_passwords(credentials) if @strip_usernames
return credentials
end
# Class variables to track credential use. They need
# to be class variables due to threading.
def initialize_class_variables(this_service,credentials)
@@guesses_per_service ||= {}
@@guesses_per_service[this_service] = nil
@@credentials_skipped = {}
@@credentials_tried = {}
@@guesses_per_service = {}
if datastore['MaxGuessesPerService'].to_i.abs == 0
@@max_per_service = credentials.size
else
if datastore['MaxGuessesPerService'].to_i.abs >= credentials.size
@@max_per_service = credentials.size
print_brute(
:level => :vstatus,
:ip => datastore['RHOST'],
:port => datastore['RPORT'],
:msg => "Adjusting MaxGuessesPerService to the actual total number of credentials")
else
@@max_per_service = datastore['MaxGuessesPerService'].to_i.abs
end
end
unless datastore['MaxMinutesPerService'].to_i.abs == 0
@@brute_start_time = Time.now.utc
end
end
def load_user_vars(credentials = nil)
users = extract_words(datastore['USER_FILE'])
if datastore['USERNAME']
users.unshift datastore['USERNAME']
credentials = prepend_chosen_username(datastore['USERNAME'], credentials) if credentials
end
users
end
def load_password_vars(credentials = nil)
passwords = extract_words(datastore['PASS_FILE'])
if datastore['PASSWORD']
passwords.unshift datastore['PASSWORD']
credentials = prepend_chosen_password(datastore['PASSWORD'], credentials) if credentials
end
passwords
end
# Takes protocol-specific username and password fields, and,
# if present, prefer those over any given USERNAME or PASSWORD.
# Note, these special username/passwords should get deprecated
# some day. Note2: Don't use with SMB and FTP at the same time!
def translate_proto_datastores
['SMBUser','FTPUSER'].each do |u|
if datastore[u] and !datastore[u].empty?
datastore['USERNAME'] = datastore[u]
end
end
['SMBPass','FTPPASS'].each do |p|
if datastore[p] and !datastore[p].empty?
datastore['PASSWORD'] = datastore[p]
end
end
end
def just_uniq_users(credentials)
credentials.map {|x| [x[0],""]}.uniq
end
def just_uniq_passwords(credentials)
credentials.map{|x| ["",x[1]]}.uniq
end
def prepend_chosen_username(user,cred_array)
cred_array.map {|pair| [user,pair[1]]} + cred_array
end
def prepend_chosen_password(pass,cred_array)
cred_array.map {|pair| [pair[0],pass]} + cred_array
end
def gen_blank_passwords(user_array,cred_array)
blank_passwords = []
unless user_array.empty?
blank_passwords.concat(user_array.map {|u| [u,""]})
end
unless cred_array.empty?
cred_array.each {|u,p| blank_passwords << [u,""]}
end
return(blank_passwords + cred_array)
end
def gen_user_as_password(user_array,cred_array)
user_as_passwords = []
unless user_array.empty?
user_as_passwords.concat(user_array.map {|u| [u,u]})
end
unless cred_array.empty?
cred_array.each {|u,p| user_as_passwords << [u,u]}
end
return(user_as_passwords + cred_array)
end
def combine_users_and_passwords(user_array,pass_array)
if (user_array.length + pass_array.length) < 1
return []
end
combined_array = []
if pass_array.empty?
combined_array = user_array.map {|u| [u,""] }
elsif user_array.empty?
combined_array = pass_array.map {|p| ["",p] }
else
if datastore['PASSWORD_SPRAY']
pass_array.each do |p|
user_array.each do |u|
combined_array << [u,p]
end
end
else
user_array.each do |u|
pass_array.each do |p|
combined_array << [u,p]
end
end
end
end
creds = [ [], [], [], [] ] # userpass, pass, user, rest
remaining_pairs = combined_array.length # counter for our occasional output
interval = 60 # seconds between each remaining pair message reported to user
next_message_time = Time.now + interval # initial timing interval for user message
# Move datastore['USERNAME'] and datastore['PASSWORD'] to the front of the list.
# Note that we cannot tell the user intention if USERNAME or PASSWORD is blank --
# maybe (and it's often) they wanted a blank. One more credential won't kill
# anyone, and hey, won't they be lucky if blank user/blank pass actually works!
combined_array.each do |pair|
if pair == [datastore['USERNAME'],datastore['PASSWORD']]
creds[0] << pair
elsif pair[1] == datastore['PASSWORD']
creds[1] << pair
elsif pair[0] == datastore['USERNAME']
creds[2] << pair
else
creds[3] << pair
end
if Time.now > next_message_time
print_brute(
:level => :vstatus,
:msg => "Pair list is still building with #{remaining_pairs} pairs left to process"
)
next_message_time = Time.now + interval
end
remaining_pairs -= 1
end
return creds[0] + creds[1] + creds[2] + creds[3]
end
def extract_words(wordfile)
return [] unless wordfile && File.readable?(wordfile)
begin
File.readlines(wordfile, chomp: true)
rescue ::StandardError => e
elog(e)
[]
end
end
def get_object_from_memory_location(memloc)
if memloc.to_s =~ /^memory:\s*([0-9]+)/
id = $1
ObjectSpace._id2ref(id.to_s.to_i)
end
end
def extract_word_pair(wordfile)
creds = []
if wordfile.to_s =~ /^memory:/
return extract_word_pair_from_memory(wordfile.to_s)
else
return [] unless wordfile && File.readable?(wordfile)
begin
upfile_contents = File.open(wordfile) {|f| f.read(f.stat.size)}
rescue
return []
end
upfile_contents.split(/\n/).each do |line|
user,pass = line.split(/\s+/,2).map { |x| x.strip }
creds << [user.to_s, pass.to_s]
end
return creds
end
end
def extract_word_pair_from_memory(memloc)
begin
creds = []
obj = get_object_from_memory_location(memloc)
unless obj.all_creds.empty?
these_creds = obj.all_creds
else
these_creds = obj.builders.select {|x| x.respond_to? :imported_users}.map {|b| b.imported_users}.flatten
end
these_creds.each do |cred|
if @strip_passwords
user = cred.split(/\s+/,2).map {|x| x.strip}[0]
pass = ""
elsif @strip_usernames
user = ""
pass = cred.split(/\s+/,2).map {|x| x.strip}[1]
else
user,pass = cred.split(/\s+/,2).map {|x| x.strip}
end
creds << [Rex::Text.dehex(user.to_s), Rex::Text.dehex(pass.to_s)]
end
if @strip_passwords || @strip_usernames
return creds.uniq
else
return creds
end
rescue => e
raise ArgumentError, "Could not read credentials from memory, raised: #{e.class}: #{e.message}"
end
end
def userpass_interval
case datastore['BRUTEFORCE_SPEED'].to_i
when 0; 60 * 5
when 1; 15
when 2; 1
when 3; 0.5
when 4; 0.1
else; 0
end
end
def userpass_sleep_interval
::IO.select(nil,nil,nil,userpass_interval) unless userpass_interval == 0
end
# See #print_brute
def vprint_brute(opts={})
if datastore['VERBOSE']
print_brute(opts)
end
end
def vprint_status(msg='')
print_brute :level => :vstatus, :msg => msg
end
def vprint_error(msg='')
print_brute :level => :verror, :msg => msg
end
alias_method :vprint_bad, :vprint_error
def vprint_good(msg='')
print_brute :level => :vgood, :msg => msg
end
# Provides a consistent way to display messages about AuthBrute-mixed modules.
# Acceptable opts are fairly self-explanatory, but :level can be tricky.
#
# It can be one of status, good, error, or line (and corresponds to the usual
# print_status, print_good, etc. methods).
#
# If it's preceded by a "v" (ie, vgood, verror, etc), only print if
# datastore["VERBOSE"] is set to true.
#
# If :level would make the method nonsense, default to print_status.
#
# TODO: This needs to be simpler to be useful.
def print_brute(opts={})
if opts[:level] and opts[:level].to_s[/^v/]
return unless datastore["VERBOSE"]
level = opts[:level].to_s[1,16].strip
else
level = opts[:level].to_s.strip
end
host_ip = opts[:ip] || opts[:rhost] || opts[:host] || (rhost rescue nil) || datastore['RHOST']
host_port = opts[:port] || opts[:rport] || (rport rescue nil) || datastore['RPORT']
msg = opts[:msg] || opts[:message]
proto = opts[:proto] || opts[:protocol] || proto_from_fullname
complete_message = build_brute_message(host_ip,host_port,proto,msg)
print_method = "print_#{level}"
if self.respond_to? print_method
self.send print_method, complete_message
else
print_status complete_message
end
end
# Depending on the non-nil elements, build up a standardized
# auth_brute message.
def build_brute_message(host_ip,host_port,proto,msg)
ip = host_ip.to_s.strip if host_ip
port = host_port.to_s.strip if host_port
complete_message = nil
old_msg = msg.to_s.strip
msg_regex = /(#{ip})(:#{port})?(\s*-?\s*)(#{proto.to_s})?(\s*-?\s*)(.*)/i
if old_msg.match(msg_regex)
complete_message = msg.to_s.strip
else
complete_message = ''
unless ip.blank? && port.blank?
complete_message << "#{ip}:#{port}"
else
complete_message << proto || 'Bruteforce'
end
complete_message << " - "
progress = tried_over_total(ip,port)
complete_message << progress if progress
complete_message << msg.to_s.strip
end
end
# Takes a credentials array, and returns just the first X involving
# a particular user.
def adjust_credentials_by_max_user(credentials)
max = datastore['MaxGuessesPerUser'].to_i.abs
if max == 0
new_credentials = credentials
else
print_brute(
:level => :vstatus,
:msg => "Adjusting credentials by MaxGuessesPerUser (#{max})"
)
user_count = {}
new_credentials = []
credentials.each do |u,p|
user_count[u] ||= 0
user_count[u] += 1
next if user_count[u] > max
new_credentials << [u,p]
end
end
return new_credentials
end
# Fun trick: Only prints if we're already in each_user_pass, since
# only then is @@max_per_service defined.
def tried_over_total(ip,port)
total = self.class.class_variable_get("@@max_per_service") rescue nil
return unless total
total = total.to_i
current_try = (@@guesses_per_service["#{ip}:#{port}"] || 1).to_i
pad = total.to_s.size
"[%0#{pad}d/%0#{pad}d] - " % [current_try, total]
end
# Protocols can nearly always be automatically determined from the
# name of the module, assuming the name is sensible like ssh_login or
# smb_auth.
def proto_from_fullname
File.split(self.fullname).last.match(/^(.*)_(login|auth|identify)/)[1].upcase rescue nil
end
# This method deletes the dictionary files if requested
def cleanup_files
path = datastore['USERPASS_FILE']
if path and datastore['REMOVE_USERPASS_FILE']
::File.unlink(path) rescue nil
end
path = datastore['USER_FILE']
if path and datastore['REMOVE_USER_FILE']
::File.unlink(path) rescue nil
end
path = datastore['PASS_FILE']
if path and datastore['REMOVE_PASS_FILE']
::File.unlink(path) rescue nil
end
end
end
end