diff --git a/lib/metasploit/framework/credential_collection.rb b/lib/metasploit/framework/credential_collection.rb index 02416aa111bf..342d3f523471 100644 --- a/lib/metasploit/framework/credential_collection.rb +++ b/lib/metasploit/framework/credential_collection.rb @@ -82,11 +82,24 @@ def prepend_cred(cred) self end + # Combines all the provided credential sources into a stream of {Credential} + # objects, yielding them one at a time + # + # @yieldparam credential [Metasploit::Framework::Credential] + # @return [void] def each_filtered - each_unfiltered do |credential| - next unless self.filter.nil? || self.filter.call(credential) + if password_spray + each_unfiltered_password_first do |credential| + next unless self.filter.nil? || self.filter.call(credential) + + yield credential + end + else + each_unfiltered_username_first do |credential| + next unless self.filter.nil? || self.filter.call(credential) - yield credential + yield credential + end end end @@ -164,6 +177,7 @@ def private_type(private) end class CredentialCollection < PrivateCredentialCollection + attr_accessor :password_spray # @!attribute additional_publics # Additional public values that should be tried @@ -219,12 +233,123 @@ def add_public(public_str='') additional_publics << public_str end - # Combines all the provided credential sources into a stream of {Credential} - # objects, yielding them one at a time - # + # When password spraying is enabled, do first passwords then usernames + # i.e. + # username1:password1 + # username2:password1 + # username3:password1 + # ... + # username1:password2 + # username2:password2 + # username3:password2 + # ... # @yieldparam credential [Metasploit::Framework::Credential] # @return [void] - def each_unfiltered + def each_unfiltered_password_first + if user_file.present? + user_fd = File.open(user_file, 'r:binary') + end + + prepended_creds.each { |c| yield c } + + if anonymous_login + yield Metasploit::Framework::Credential.new(public: '', private: '', realm: realm, private_type: :password) + end + + if password.present? + if nil_passwords + yield Metasploit::Framework::Credential.new(public: username, private: nil, realm: realm, private_type: :password) + end + if username.present? + yield Metasploit::Framework::Credential.new(public: username, private: password, realm: realm, private_type: private_type(password)) + end + if user_as_pass + yield Metasploit::Framework::Credential.new(public: username, private: username, realm: realm, private_type: :password) + end + if blank_passwords + yield Metasploit::Framework::Credential.new(public: username, private: "", realm: realm, private_type: :password) + end + if user_fd + user_fd.each_line do |user_from_file| + user_from_file.chomp! + yield Metasploit::Framework::Credential.new(public: user_from_file, private: password, realm: realm, private_type: private_type(password)) + end + user_fd.seek(0) + end + end + + if pass_file.present? + File.open(pass_file, 'r:binary') do |pass_fd| + pass_fd.each_line do |pass_from_file| + pass_from_file.chomp! + if user_as_pass + yield Metasploit::Framework::Credential.new(public: pass_from_file, private: pass_from_file, realm: realm, private_type: :password) + end + if user_fd + user_fd.each_line do |user_from_file| + user_from_file.chomp! + yield Metasploit::Framework::Credential.new(public: user_from_file, private: pass_from_file, realm: realm, private_type: private_type(pass_from_file)) + end + user_fd.seek(0) + end + additional_privates.each do |add_private| + yield Metasploit::Framework::Credential.new(public: user_from_file, private: add_private, realm: realm, private_type: private_type(add_private)) + end + end + end + end + + if userpass_file.present? + File.open(userpass_file, 'r:binary') do |userpass_fd| + userpass_fd.each_line do |line| + user, pass = line.split(" ", 2) + if pass.blank? + pass = '' + else + pass.chomp! + end + yield Metasploit::Framework::Credential.new(public: user, private: pass, realm: realm) + end + end + end + + additional_publics.each do |add_public| + if password.present? + yield Metasploit::Framework::Credential.new(public: add_public, private: password, realm: realm, private_type: private_type(password) ) + end + if user_as_pass + yield Metasploit::Framework::Credential.new(public: add_public, private: user_from_file, realm: realm, private_type: :password) + end + if blank_passwords + yield Metasploit::Framework::Credential.new(public: add_public, private: "", realm: realm, private_type: :password) + end + if user_fd + user_fd.each_line do |user_from_file| + user_from_file.chomp! + yield Metasploit::Framework::Credential.new(public: add_public, private: user_from_file, realm: realm, private_type: private_type(user_from_file)) + end + user_fd.seek(0) + end + additional_privates.each do |add_private| + yield Metasploit::Framework::Credential.new(public: add_public, private: add_private, realm: realm, private_type: private_type(add_private)) + end + end + ensure + user_fd.close if user_fd && !user_fd.closed? + end + + # When password spraying is not enabled, do first usernames then passwords + # i.e. + # username1:password1 + # username1:password2 + # username1:password3 + # ... + # username2:password1 + # username2:password2 + # username2:password3 + # @yieldparam credential [Metasploit::Framework::Credential] + # @return [void] + def each_unfiltered_username_first if pass_file.present? pass_fd = File.open(pass_file, 'r:binary') end @@ -325,7 +450,6 @@ def each_unfiltered yield Metasploit::Framework::Credential.new(public: add_public, private: add_private, realm: realm, private_type: private_type(add_private)) end end - ensure pass_fd.close if pass_fd && !pass_fd.closed? end diff --git a/lib/msf/core/auxiliary/auth_brute.rb b/lib/msf/core/auxiliary/auth_brute.rb index 3d96176ea327..67a4c2cc2434 100644 --- a/lib/msf/core/auxiliary/auth_brute.rb +++ b/lib/msf/core/auxiliary/auth_brute.rb @@ -61,6 +61,7 @@ def build_credential_collection(opts) 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 diff --git a/modules/auxiliary/scanner/ldap/ldap_login.rb b/modules/auxiliary/scanner/ldap/ldap_login.rb index 2b6c6a8e3bbf..c60b79b1b039 100644 --- a/modules/auxiliary/scanner/ldap/ldap_login.rb +++ b/modules/auxiliary/scanner/ldap/ldap_login.rb @@ -58,7 +58,8 @@ def run_host(ip) password: datastore['PASSWORD'], realm: datastore['DOMAIN'], anonymous_login: datastore['ANONYMOUS_LOGIN'], - blank_passwords: false + blank_passwords: false, + password_spray: datastore['PASSWORD_SPRAY'] ) opts = { diff --git a/spec/lib/metasploit/framework/credential_collection_spec.rb b/spec/lib/metasploit/framework/credential_collection_spec.rb index b07c3f64963e..a44ec2e73874 100644 --- a/spec/lib/metasploit/framework/credential_collection_spec.rb +++ b/spec/lib/metasploit/framework/credential_collection_spec.rb @@ -15,7 +15,8 @@ userpass_file: userpass_file, prepended_creds: prepended_creds, additional_privates: additional_privates, - additional_publics: additional_publics + additional_publics: additional_publics, + password_spray: password_spray ) end @@ -30,6 +31,7 @@ let(:prepended_creds) { [] } let(:additional_privates) { [] } let(:additional_publics) { [] } + let(:password_spray) { false } describe "#each" do specify do @@ -72,7 +74,6 @@ end end - context "when given a userspass_file" do let(:username) { nil } let(:password) { nil } @@ -120,6 +121,37 @@ end end + context "when given a pass_file and user_file and password spray" do + let(:password) { nil } + let(:username) { nil } + let(:password_spray) { true } + let(:pass_file) do + filename = "pass_file" + stub_file = StringIO.new("password1\npassword2\n") + allow(File).to receive(:open).with(filename,/^r/).and_yield stub_file + + filename + end + let(:user_file) do + filename = "user_file" + stub_file = StringIO.new("user1\nuser2\nuser3\n") + allow(File).to receive(:open).with(filename,/^r/).and_return stub_file + + filename + end + + specify do + expect { |b| collection.each(&b) }.to yield_successive_args( + Metasploit::Framework::Credential.new(public: "user1", private: "password1"), + Metasploit::Framework::Credential.new(public: "user2", private: "password1"), + Metasploit::Framework::Credential.new(public: "user3", private: "password1"), + Metasploit::Framework::Credential.new(public: "user1", private: "password2"), + Metasploit::Framework::Credential.new(public: "user2", private: "password2"), + Metasploit::Framework::Credential.new(public: "user3", private: "password2"), + ) + end + end + context 'when given a username, user_file and pass_file' do let(:password) { nil } let(:username) { 'my_username' }