Module: Msf::Auxiliary::AuthBrute
- Includes:
- LoginScanner
- Included in:
- CNPILOT, EPMP, Exploit::Remote::Kerberos::AuthBrute
- Defined in:
- lib/msf/core/auxiliary/auth_brute.rb
Overview
This module provides methods for brute forcing authentication
Instance Method Summary collapse
-
#adjust_credentials_by_max_user(credentials) ⇒ Array<Array<String, String>>
Limits the number of credential pairs retained for each username based on ‘MaxGuessesPerUser`.
-
#build_brute_message(host_ip, host_port, proto, msg) ⇒ String
Builds the standardized auth_brute status line for a login attempt or event.
-
#build_credential_collection(opts) ⇒ Metasploit::Framework::CredentialCollection
Build a new CredentialCollection instance configured based on the datastore options.
-
#build_credentials_array ⇒ Array<Array<String, String>>
Builds the ordered credential list from files, datastore overrides, and optional database values.
-
#cleanup_files ⇒ void
Deletes temporary credential dictionary files when their removal options are enabled.
-
#combine_users_and_passwords(user_array, pass_array) ⇒ Array<Array<String, String>>
Produces the ordered cartesian product of usernames and passwords, prioritizing explicit datastore choices.
-
#counters_expired?(this_service, credentials) ⇒ Boolean
Determines whether the current service has exceeded its guess-count or runtime limits.
-
#each_ntlm_cred {|| ... } ⇒ Object
Yields each Metasploit::Credential::Core in the Mdm::Workspace with a private type of ‘ntlm_hash’.
-
#each_password_cred {|| ... } ⇒ Object
Yields each Metasploit::Credential::Core in the Mdm::Workspace with a private type of ‘password’.
-
#each_ssh_cred {|| ... } ⇒ Object
Yields each Metasploit::Credential::Core in the Mdm::Workspace with a private type of ‘ssh_key’.
-
#each_user_pass(noconn = false) {|username, password| ... } ⇒ void
Checks all three files for usernames and passwords, and combines them into one credential list to apply against the supplied block.
-
#each_username_cred {|| ... } ⇒ Object
Yields each Metasploit::Credential::Core in the Mdm::Workspace with a private type of ‘nil’.
-
#extract_word_pair(wordfile) ⇒ Array<Array<String, String>>
Reads username/password pairs from disk or a memory-backed credential source.
-
#extract_word_pair_from_memory(memloc) ⇒ Array<Array<String, String>>
Extracts username/password pairs from a memory-backed credential builder.
-
#extract_words(wordfile) ⇒ Array<String>
Reads a newline-delimited wordlist from disk.
-
#gen_blank_passwords(user_array, cred_array) ⇒ Array<Array<String, String>>
Generates additional credential pairs that try blank passwords for each known username.
-
#gen_user_as_password(user_array, cred_array) ⇒ Array<Array<String, String>>
Generates additional credential pairs that try each username as its own password.
-
#get_object_from_memory_location(memloc) ⇒ Object?
Resolves a
memory:pseudo-path into the referenced Ruby object. -
#initialize(info = {}) ⇒ void
Registers datastore options used to build username/password permutations and control brute-force execution.
-
#initialize_class_variables(this_service, credentials) ⇒ void
Initializes shared brute-force counters for the current service and run configuration.
-
#just_uniq_passwords(credentials) ⇒ Array<Array<String, String>>
Collapses credential pairs to unique passwords while replacing every username with a blank value.
-
#just_uniq_users(credentials) ⇒ Array<Array<String, String>>
Collapses credential pairs to unique usernames while replacing every password with a blank value.
-
#load_password_vars(credentials = nil) ⇒ Array<String>
Loads passwords from the configured file and optional PASSWORD datastore override.
-
#load_user_vars(credentials = nil) ⇒ Array<String>
Loads usernames from the configured file and optional USERNAME datastore override.
-
#prepend_chosen_password(pass, cred_array) ⇒ Array<Array<String, String>>
Prepends credential pairs that reuse a chosen password across the supplied usernames.
-
#prepend_chosen_username(user, cred_array) ⇒ Array<Array<String, String>>
Prepends credential pairs that reuse a chosen username across the supplied passwords.
-
#prepend_db_creds? ⇒ TrueClass, FalseClass
Checks whether we should be adding creds from the DB to a CredCollection.
-
#prepend_db_hashes(cred_collection) ⇒ Metasploit::Framework::CredentialCollection
This method takes a Metasploit::Framework::CredentialCollection and prepends existing NTLMHashes from the database.
-
#prepend_db_keys(cred_collection) ⇒ Metasploit::Framework::CredentialCollection
This method takes a Metasploit::Framework::CredentialCollection and prepends existing SSHKeys from the database.
-
#prepend_db_passwords(cred_collection) ⇒ Metasploit::Framework::CredentialCollection
This method takes a Metasploit::Framework::CredentialCollection and prepends existing Password Credentials from the database.
-
#prepend_db_usernames(cred_collection) ⇒ Metasploit::Framework::CredentialCollection
This method takes a Metasploit::Framework::CredentialCollection and prepends existing Usernames from the database.
-
#print_brute(opts = {}) ⇒ Object
Provides a consistent way to display messages about AuthBrute-mixed modules.
-
#process_cred_for_collection(cred_collection, cred) ⇒ Object
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.
-
#proto_from_fullname ⇒ String?
Infers the protocol name from the module’s fullname suffix.
-
#setup ⇒ void
Resets per-service brute-force limits before a module run starts.
-
#translate_proto_datastores ⇒ void
Copies protocol-specific username/password datastore options into the generic auth-brute options.
-
#tried_over_total(ip, port) ⇒ String?
Formats the current attempt count and total guess limit for a service.
-
#userpass_interval ⇒ Integer, Float
Maps the configured brute-force speed to the delay inserted between login attempts.
-
#userpass_sleep_interval ⇒ Array<IO, nil>?
Sleeps for the delay implied by #userpass_interval.
-
#vprint_brute(opts = {}) ⇒ void
Prints a verbose auth-brute message when ‘VERBOSE` output is enabled.
-
#vprint_error(msg = '') ⇒ void
(also: #vprint_bad)
Prints a verbose error message via #print_brute.
-
#vprint_good(msg = '') ⇒ void
Prints a verbose success message via #print_brute.
-
#vprint_status(msg = '') ⇒ void
Prints a verbose status message via #print_brute.
Methods included from LoginScanner
Instance Method Details
#adjust_credentials_by_max_user(credentials) ⇒ Array<Array<String, String>>
Limits the number of credential pairs retained for each username based on ‘MaxGuessesPerUser`.
859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 859 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 |
#build_brute_message(host_ip, host_port, proto, msg) ⇒ String
Builds the standardized auth_brute status line for a login attempt or event.
832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 832 def (host_ip,host_port,proto,msg) ip = host_ip.to_s.strip if host_ip port = host_port.to_s.strip if host_port = 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) = msg.to_s.strip else = '' unless ip.blank? && port.blank? << Rex::Socket.(ip, port).ljust(21) else << proto || 'Bruteforce' end << " - " progress = tried_over_total(ip,port) << progress if progress << msg.to_s.strip end end |
#build_credential_collection(opts) ⇒ Metasploit::Framework::CredentialCollection
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.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 64 def build_credential_collection(opts) cred_collection = Metasploit::Framework::CredentialCollection.new({ anonymous_login: datastore['ANONYMOUS_LOGIN'], 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 |
#build_credentials_array ⇒ Array<Array<String, String>>
Builds the ordered credential list from files, datastore overrides, and optional database values.
If the user passed a memory-backed credential source, the list is returned as-is without additional transforms, deduplication, or database-backed augmentation.
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 407 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 |
#cleanup_files ⇒ void
This method returns an undefined value.
Deletes temporary credential dictionary files when their removal options are enabled.
909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 909 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 |
#combine_users_and_passwords(user_array, pass_array) ⇒ Array<Array<String, String>>
Produces the ordered cartesian product of usernames and passwords, prioritizing explicit datastore choices.
594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 594 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 = 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 > print_brute( :level => :vstatus, :msg => "Pair list is still building with #{remaining_pairs} pairs left to process" ) = Time.now + interval end remaining_pairs -= 1 end return creds[0] + creds[1] + creds[2] + creds[3] end |
#counters_expired?(this_service, credentials) ⇒ Boolean
Determines whether the current service has exceeded its guess-count or runtime limits.
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 370 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 |
#each_ntlm_cred {|| ... } ⇒ Object
Yields each Metasploit::Credential::Core in the Mdm::Workspace with a private type of ‘ntlm_hash’
134 135 136 137 138 139 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 134 def each_ntlm_cred creds = framework.db.creds(type: 'Metasploit::Credential::NTLMHash', workspace: myworkspace.name) creds.each do |cred| yield cred end end |
#each_password_cred {|| ... } ⇒ Object
Yields each Metasploit::Credential::Core in the Mdm::Workspace with a private type of ‘password’
145 146 147 148 149 150 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 145 def each_password_cred creds = framework.db.creds(type: 'Metasploit::Credential::Password', workspace: myworkspace.name) creds.each do |cred| yield cred end end |
#each_ssh_cred {|| ... } ⇒ Object
Yields each Metasploit::Credential::Core in the Mdm::Workspace with a private type of ‘ssh_key’
156 157 158 159 160 161 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 156 def each_ssh_cred creds = framework.db.creds(type: 'Metasploit::Credential::SSHKey', workspace: myworkspace.name) creds.each do |cred| yield cred end end |
#each_user_pass(noconn = false) {|username, password| ... } ⇒ void
This method returns an undefined value.
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.
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 275 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 |
#each_username_cred {|| ... } ⇒ Object
Yields each Metasploit::Credential::Core in the Mdm::Workspace with a private type of ‘nil’
167 168 169 170 171 172 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 167 def each_username_cred creds = framework.db.creds(type: nil, workspace: myworkspace.name) creds.each do |cred| yield cred end end |
#extract_word_pair(wordfile) ⇒ Array<Array<String, String>>
Reads username/password pairs from disk or a memory-backed credential source.
679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 679 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 |
#extract_word_pair_from_memory(memloc) ⇒ Array<Array<String, String>>
Extracts username/password pairs from a memory-backed credential builder.
703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 703 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.}" end end |
#extract_words(wordfile) ⇒ Array<String>
Reads a newline-delimited wordlist from disk.
653 654 655 656 657 658 659 660 661 662 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 653 def extract_words(wordfile) return [] unless wordfile && File.readable?(wordfile) begin File.readlines(wordfile, chomp: true) rescue ::StandardError => e elog(e) [] end end |
#gen_blank_passwords(user_array, cred_array) ⇒ Array<Array<String, String>>
Generates additional credential pairs that try blank passwords for each known username.
562 563 564 565 566 567 568 569 570 571 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 562 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 |
#gen_user_as_password(user_array, cred_array) ⇒ Array<Array<String, String>>
Generates additional credential pairs that try each username as its own password.
578 579 580 581 582 583 584 585 586 587 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 578 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 |
#get_object_from_memory_location(memloc) ⇒ Object?
Resolves a memory: pseudo-path into the referenced Ruby object.
668 669 670 671 672 673 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 668 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 |
#initialize(info = {}) ⇒ void
Registers datastore options used to build username/password permutations and control brute-force execution.
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 18 def initialize(info = {}) super ([ 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) ([ 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]), OptString.new('SSLKeyLogFile', [ false, 'The SSL key log file', ENV['SSLKeyLogFile']]), 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 |
#initialize_class_variables(this_service, credentials) ⇒ void
This method returns an undefined value.
Initializes shared brute-force counters for the current service and run configuration.
Class variables are used here so the counters remain visible across worker threads.
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 451 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 |
#just_uniq_passwords(credentials) ⇒ Array<Array<String, String>>
Collapses credential pairs to unique passwords while replacing every username with a blank value.
535 536 537 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 535 def just_uniq_passwords(credentials) credentials.map{|x| ["",x[1]]}.uniq end |
#just_uniq_users(credentials) ⇒ Array<Array<String, String>>
Collapses credential pairs to unique usernames while replacing every password with a blank value.
527 528 529 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 527 def just_uniq_users(credentials) credentials.map {|x| [x[0],""]}.uniq end |
#load_password_vars(credentials = nil) ⇒ Array<String>
Loads passwords from the configured file and optional PASSWORD datastore override.
494 495 496 497 498 499 500 501 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 494 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 |
#load_user_vars(credentials = nil) ⇒ Array<String>
Loads usernames from the configured file and optional USERNAME datastore override.
481 482 483 484 485 486 487 488 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 481 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 |
#prepend_chosen_password(pass, cred_array) ⇒ Array<Array<String, String>>
Prepends credential pairs that reuse a chosen password across the supplied usernames.
553 554 555 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 553 def prepend_chosen_password(pass,cred_array) cred_array.map {|pair| [pair[0],pass]} + cred_array end |
#prepend_chosen_username(user, cred_array) ⇒ Array<Array<String, String>>
Prepends credential pairs that reuse a chosen username across the supplied passwords.
544 545 546 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 544 def prepend_chosen_username(user,cred_array) cred_array.map {|pair| [user,pair[1]]} + cred_array end |
#prepend_db_creds? ⇒ TrueClass, FalseClass
Checks whether we should be adding creds from the DB to a CredCollection
178 179 180 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 178 def prepend_db_creds? (datastore['DB_ALL_CREDS'] || datastore['DB_ALL_PASS'] || datastore['DB_ALL_USERS']) && framework.db.active end |
#prepend_db_hashes(cred_collection) ⇒ Metasploit::Framework::CredentialCollection
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.
188 189 190 191 192 193 194 195 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 188 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 |
#prepend_db_keys(cred_collection) ⇒ Metasploit::Framework::CredentialCollection
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.
203 204 205 206 207 208 209 210 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 203 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 |
#prepend_db_passwords(cred_collection) ⇒ Metasploit::Framework::CredentialCollection
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.
218 219 220 221 222 223 224 225 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 218 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 |
#prepend_db_usernames(cred_collection) ⇒ Metasploit::Framework::CredentialCollection
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.
233 234 235 236 237 238 239 240 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 233 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 |
#print_brute(opts = {}) ⇒ Object
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 is set to true.
If :level would make the method nonsense, default to print_status.
TODO: This needs to be simpler to be useful.
803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 803 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 = (host_ip,host_port,proto,msg) print_method = "print_#{level}" if self.respond_to? print_method self.send print_method, else print_status end end |
#process_cred_for_collection(cred_collection, cred) ⇒ Object
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.
249 250 251 252 253 254 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 249 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 |
#proto_from_fullname ⇒ String?
Infers the protocol name from the module’s fullname suffix.
This assumes the module name follows auth/login naming patterns such as ssh_login or smb_auth.
902 903 904 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 902 def proto_from_fullname File.split(self.fullname).last.match(/^(.*)_(login|auth|identify)/)[1].upcase rescue nil end |
#setup ⇒ void
This method returns an undefined value.
Resets per-service brute-force limits before a module run starts.
126 127 128 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 126 def setup @@max_per_service = nil end |
#translate_proto_datastores ⇒ void
This method returns an undefined value.
Copies protocol-specific username/password datastore options into the generic auth-brute options.
Legacy datastore keys such as ‘SMBUser` and `FTPUSER` take precedence over the generic `USERNAME` and `PASSWORD` values when they are populated.
510 511 512 513 514 515 516 517 518 519 520 521 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 510 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 |
#tried_over_total(ip, port) ⇒ String?
Formats the current attempt count and total guess limit for a service.
This only returns progress text after #each_user_pass has initialized the shared counters.
887 888 889 890 891 892 893 894 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 887 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 |
#userpass_interval ⇒ Integer, Float
Maps the configured brute-force speed to the delay inserted between login attempts.
737 738 739 740 741 742 743 744 745 746 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 737 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 |
#userpass_sleep_interval ⇒ Array<IO, nil>?
Sleeps for the delay implied by #userpass_interval.
751 752 753 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 751 def userpass_sleep_interval ::IO.select(nil,nil,nil,userpass_interval) unless userpass_interval == 0 end |
#vprint_brute(opts = {}) ⇒ void
This method returns an undefined value.
Prints a verbose auth-brute message when ‘VERBOSE` output is enabled.
759 760 761 762 763 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 759 def vprint_brute(opts={}) if datastore['VERBOSE'] print_brute(opts) end end |
#vprint_error(msg = '') ⇒ void Also known as: vprint_bad
This method returns an undefined value.
Prints a verbose error message via #print_brute.
777 778 779 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 777 def vprint_error(msg='') print_brute :level => :verror, :msg => msg end |
#vprint_good(msg = '') ⇒ void
This method returns an undefined value.
Prints a verbose success message via #print_brute.
787 788 789 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 787 def vprint_good(msg='') print_brute :level => :vgood, :msg => msg end |
#vprint_status(msg = '') ⇒ void
This method returns an undefined value.
Prints a verbose status message via #print_brute.
769 770 771 |
# File 'lib/msf/core/auxiliary/auth_brute.rb', line 769 def vprint_status(msg='') print_brute :level => :vstatus, :msg => msg end |