From 14bc1f14fc47e695b462737c7fe53a4f1312cc19 Mon Sep 17 00:00:00 2001 From: Mathieu Date: Wed, 12 Feb 2025 17:47:18 +0100 Subject: [PATCH 1/3] Change how ldap_login generate its specific credentials for SCHANNEL && KERBEROS auth --- modules/auxiliary/scanner/ldap/ldap_login.rb | 40 ++++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/modules/auxiliary/scanner/ldap/ldap_login.rb b/modules/auxiliary/scanner/ldap/ldap_login.rb index d7a41f5c622d5..3d85edc7aa50c 100644 --- a/modules/auxiliary/scanner/ldap/ldap_login.rb +++ b/modules/auxiliary/scanner/ldap/ldap_login.rb @@ -93,22 +93,9 @@ def validate_connect_options! end def run_host(ip) - ignore_public = ignore_private = false - case datastore['LDAP::Auth'] - when Msf::Exploit::Remote::AuthOption::SCHANNEL - ignore_public = ignore_private = true - when Msf::Exploit::Remote::AuthOption::KERBEROS - ignore_private = !datastore['ANONYMOUS_LOGIN'] && !datastore['LDAPPassword'] - end - - cred_collection = build_credential_collection( - username: datastore['LDAPUsername'], - password: datastore['LDAPPassword'], - realm: datastore['LDAPDomain'], - anonymous_login: datastore['ANONYMOUS_LOGIN'], - blank_passwords: false, - ignore_public: ignore_public, - ignore_private: ignore_private + cred_collection = build_specific_credential_collection( + void_login: datastore['LDAP::Auth'] == Msf::Exploit::Remote::AuthOption::SCHANNEL, + no_password_login: datastore['LDAP::Auth'] == Msf::Exploit::Remote::AuthOption::KERBEROS && !datastore['ANONYMOUS_LOGIN'] && !datastore['LDAPPassword'] ) pkcs12_storage = Msf::Exploit::Remote::Pkcs12::Storage.new(framework: framework, framework_module: self) @@ -215,4 +202,25 @@ def session_setup(result) start_session(self, nil, merge_me, false, my_session.rstream, my_session) end + + def build_specific_credential_collection(void_login:, no_password_login:) + if void_login + Metasploit::Framework::PrivateCredentialCollection.new({ + nil_passwords: true + }) + elsif no_password_login + Metasploit::Framework::CredentialCollection.new({ + username: datastore['LDAPUsername'], + nil_passwords: true + }) + else + build_credential_collection( + username: datastore['LDAPUsername'], + password: datastore['LDAPPassword'], + realm: datastore['DOMAIN'], + anonymous_login: datastore['ANONYMOUS_LOGIN'], + blank_passwords: false + ) + end + end end From 4b97240eca490be6ed94cca621d1cffac1cc2c91 Mon Sep 17 00:00:00 2001 From: Mathieu Date: Mon, 18 Nov 2024 15:38:17 +0100 Subject: [PATCH 2/3] Fixes for the CredentialCollection Fixes trivial bugs when combining additional_publics with user_as_pass Fixes the fact that no nil credential is generated for additional_publics Re-implement the each_unfiltered_password_first method (used in case of password spraying) to make all tests pass First refactoring pass in order to dry the code that iterates on passwords and usernames --- .../framework/credential_collection.rb | 289 ++++++------------ 1 file changed, 99 insertions(+), 190 deletions(-) diff --git a/lib/metasploit/framework/credential_collection.rb b/lib/metasploit/framework/credential_collection.rb index 11e8a4f818676..de9135b932348 100644 --- a/lib/metasploit/framework/credential_collection.rb +++ b/lib/metasploit/framework/credential_collection.rb @@ -212,23 +212,6 @@ class CredentialCollection < PrivateCredentialCollection # @return [Boolean] attr_accessor :anonymous_login - # @!attribute ignore_private - # Whether to ignore private (password). This is usually set when Kerberos - # or Schannel authentication is requested and the credentials are - # retrieved from cache or from a file. This attribute should be true in - # these scenarios, otherwise validation will fail since the password is not - # provided. - # @return [Boolean] - attr_accessor :ignore_private - - # @!attribute ignore_public - # Whether to ignore public (username). This is usually set when Schannel - # authentication is requested and the credentials are retrieved from a - # file (certificate). This attribute should be true in this case, - # otherwise validation will fail since the password is not provided. - # @return [Boolean] - attr_accessor :ignore_public - # @option opts [Boolean] :blank_passwords See {#blank_passwords} # @option opts [String] :pass_file See {#pass_file} # @option opts [String] :password See {#password} @@ -257,29 +240,29 @@ def add_public(public_str='') # @yieldparam credential [Metasploit::Framework::Credential] # @return [void] def each_filtered - if ignore_private - if ignore_public - yield Metasploit::Framework::Credential.new(public: nil, private: nil, realm: realm) - else - yield Metasploit::Framework::Credential.new(public: username, private: nil, realm: realm) - end - elsif 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) + each_unfiltered do |credential| + next unless self.filter.nil? || self.filter.call(credential) - yield credential - end + yield credential end end alias each each_filtered + def each_unfiltered(&block) + prepended_creds.each { |c| yield c } + + if anonymous_login + yield Metasploit::Framework::Credential.new(public: '', private: '', realm: realm, private_type: :password) + end + + if password_spray + each_unfiltered_password_first(&block) + else + each_unfiltered_username_first(&block) + end + end + # When password spraying is enabled, do first passwords then usernames # i.e. # username1:password1 @@ -293,117 +276,72 @@ def each_filtered # @yieldparam credential [Metasploit::Framework::Credential] # @return [void] 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 user_as_pass - 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: user_from_file, realm: realm, private_type: private_type(password)) - end - user_fd.seek(0) + if nil_passwords + each_username do |username| + yield Metasploit::Framework::Credential.new(public: username, private: nil, realm: realm, private_type: :password) end 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? + each_username do |username| yield Metasploit::Framework::Credential.new(public: username, private: password, realm: realm, private_type: private_type(password)) end - if user_as_pass + end + + if user_as_pass + each_username do |username| yield Metasploit::Framework::Credential.new(public: username, private: username, realm: realm, private_type: :password) end - if blank_passwords + end + + if blank_passwords + each_username do |username| 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 username.present? - yield Metasploit::Framework::Credential.new(public: username, private: pass_from_file, realm: realm, private_type: :password) - end - next unless 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)) + each_username do |username| + yield Metasploit::Framework::Credential.new(public: username, private: pass_from_file, realm: realm, private_type: private_type(pass_from_file)) end - user_fd.seek(0) 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 + each_user_pass_from_userpass_file do |user, pass| + yield Metasploit::Framework::Credential.new(public: user, private: pass, realm: realm) end additional_privates.each do |add_private| - if username.present? + each_username do |username| yield Metasploit::Framework::Credential.new(public: username, private: add_private, realm: realm, private_type: private_type(add_private)) end - user_fd.each_line do |user_from_file| - user_from_file.chomp! - yield Metasploit::Framework::Credential.new(public: user_from_file, private: add_private, realm: realm, private_type: private_type(add_private)) - end - user_fd.seek(0) 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 nil_passwords - yield Metasploit::Framework::Credential.new(public: add_public, private: nil, realm: realm, private_type: :password) - end - if user_fd + # Iterates over all possible usernames + def each_username + if username.present? + yield username + end + + if user_file.present? + File.open(user_file, 'r:binary') do |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)) + yield 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? + + additional_publics.each do |add_public| + yield add_public + end end # When password spraying is not enabled, do first usernames then passwords @@ -418,38 +356,9 @@ def each_unfiltered_password_first # @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 - - prepended_creds.each { |c| yield c } - - if anonymous_login - yield Metasploit::Framework::Credential.new(public: '', private: '', realm: realm, private_type: :password) - end - if username.present? - if nil_passwords - yield Metasploit::Framework::Credential.new(public: username, private: nil, realm: realm, private_type: :password) - end - if password.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 pass_fd - pass_fd.each_line do |pass_from_file| - pass_from_file.chomp! - yield Metasploit::Framework::Credential.new(public: username, private: pass_from_file, realm: realm, private_type: private_type(pass_from_file)) - end - pass_fd.seek(0) - end - additional_privates.each do |add_private| - yield Metasploit::Framework::Credential.new(public: username, private: add_private, realm: realm, private_type: private_type(add_private)) + each_password(username) do |password, private_type| + yield Metasploit::Framework::Credential.new(public: username, private: password, realm: realm, private_type: private_type) end end @@ -457,69 +366,69 @@ def each_unfiltered_username_first File.open(user_file, 'r:binary') do |user_fd| user_fd.each_line do |user_from_file| user_from_file.chomp! - if nil_passwords - yield Metasploit::Framework::Credential.new(public: user_from_file, private: nil, realm: realm, private_type: :password) - end - if password.present? - yield Metasploit::Framework::Credential.new(public: user_from_file, private: password, realm: realm, private_type: private_type(password) ) - end - if user_as_pass - yield Metasploit::Framework::Credential.new(public: user_from_file, private: user_from_file, realm: realm, private_type: :password) - end - if blank_passwords - yield Metasploit::Framework::Credential.new(public: user_from_file, private: "", realm: realm, private_type: :password) - end - if pass_fd - pass_fd.each_line do |pass_from_file| - pass_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 - pass_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)) + each_password(user_from_file) do |password, private_type| + yield Metasploit::Framework::Credential.new(public: user_from_file, private: password, realm: realm, private_type: private_type) 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 + each_user_pass_from_userpass_file do |user, pass| + yield Metasploit::Framework::Credential.new(public: user, private: pass, realm: realm) 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) ) + each_password(add_public) do |password, private_type| + yield Metasploit::Framework::Credential.new(public: add_public, private: password, realm: realm, private_type: private_type) 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 pass_fd + end + end + + # Iterates over all possible passwords + def each_password(user) + if nil_passwords + yield [nil, :password] + end + + if password.present? + yield [password, private_type(password)] + end + + if user_as_pass + yield [user, :password] + end + + if blank_passwords + yield ["", :password] + end + + if pass_file + File.open(pass_file, 'r:binary') do |pass_fd| pass_fd.each_line do |pass_from_file| pass_from_file.chomp! - yield Metasploit::Framework::Credential.new(public: add_public, private: pass_from_file, realm: realm, private_type: private_type(pass_from_file)) + yield [pass_from_file, private_type(pass_from_file)] end pass_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 + + additional_privates.each do |add_private| + yield [add_private, private_type(add_private)] + end + end + + # Iterates on userpass file if present + def each_user_pass_from_userpass_file + return unless userpass_file.present? + + File.open(userpass_file, 'r:binary') do |userpass_fd| + userpass_fd.each_line do |line| + user, pass = line.split(" ", 2) + pass = pass.blank? ? '' : pass.chomp! + + yield [user, pass] end end - ensure - pass_fd.close if pass_fd && !pass_fd.closed? end # Returns true when #each will have no results to iterate @@ -533,14 +442,14 @@ def empty? # # @return [Boolean] def has_users? - username.present? || user_file.present? || userpass_file.present? || !additional_publics.empty? || !!ignore_public + username.present? || user_file.present? || userpass_file.present? || !additional_publics.empty? end # Returns true when there are any private values set # # @return [Boolean] def has_privates? - super || userpass_file.present? || user_as_pass || !!ignore_private + super || userpass_file.present? || user_as_pass end end From 736d89bc2cdfd5f332047a7eea432a9b81afb331 Mon Sep 17 00:00:00 2001 From: Mathieu Date: Fri, 15 Nov 2024 17:47:17 +0100 Subject: [PATCH 3/3] Update the CredentialCollectionSpecs CredentialCollection: adding various tests that currently fails to demonstrate multiple bugs in the each method Fix incorrect expectations of currently failing tests Inline all specs to avoid nesting context Reverts some test expectations Some final tidy up Re-revert tests expectations --- .../framework/credential_collection_spec.rb | 338 +++++++++++++++--- 1 file changed, 280 insertions(+), 58 deletions(-) diff --git a/spec/lib/metasploit/framework/credential_collection_spec.rb b/spec/lib/metasploit/framework/credential_collection_spec.rb index 3fd679966d6fa..a4d3cc3650124 100644 --- a/spec/lib/metasploit/framework/credential_collection_spec.rb +++ b/spec/lib/metasploit/framework/credential_collection_spec.rb @@ -16,9 +16,7 @@ prepended_creds: prepended_creds, additional_privates: additional_privates, additional_publics: additional_publics, - password_spray: password_spray, - ignore_public: ignore_public, - ignore_private: ignore_private + password_spray: password_spray ) end @@ -41,8 +39,6 @@ let(:additional_privates) { [] } let(:additional_publics) { [] } let(:password_spray) { false } - let(:ignore_public) { nil } - let(:ignore_private) { nil } describe "#each" do specify do @@ -72,7 +68,7 @@ let(:pass_file) do filename = "foo" stub_file = StringIO.new("asdf\njkl\n") - allow(File).to receive(:open).with(filename,/^r/).and_return stub_file + allow(File).to receive(:open).with(filename,/^r/).and_yield stub_file filename end @@ -85,7 +81,7 @@ end end - context "when given a userspass_file" do + context "when given a userpass_file" do let(:username) { nil } let(:password) { nil } let(:userpass_file) do @@ -117,7 +113,7 @@ let(:pass_file) do filename = "pass_file" stub_file = StringIO.new("asdf\njkl\n") - allow(File).to receive(:open).with(filename,/^r/).and_return stub_file + allow(File).to receive(:open).with(filename,/^r/).and_yield stub_file filename end @@ -146,7 +142,7 @@ 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 + allow(File).to receive(:open).with(filename,/^r/).and_yield stub_file filename end @@ -161,23 +157,40 @@ Metasploit::Framework::Credential.new(public: "user3", private: "password2"), ) end + end - context 'when :user_as_pass is true' do - let(:user_as_pass) { true } + context 'when given a pass_file and user_file and password spray and :user_as_pass is true' 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 - specify do - expect { |b| collection.each(&b) }.to yield_successive_args( - Metasploit::Framework::Credential.new(public: "user1", private: "user1"), - Metasploit::Framework::Credential.new(public: "user2", private: "user2"), - Metasploit::Framework::Credential.new(public: "user3", private: "user3"), - 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 + 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_yield stub_file + + filename + end + let(:user_as_pass) { true } + + specify do + expect { |b| collection.each(&b) }.to yield_successive_args( + Metasploit::Framework::Credential.new(public: "user1", private: "user1"), + Metasploit::Framework::Credential.new(public: "user2", private: "user2"), + Metasploit::Framework::Credential.new(public: "user3", private: "user3"), + 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 @@ -207,7 +220,7 @@ 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 + allow(File).to receive(:open).with(filename,/^r/).and_yield stub_file filename end @@ -242,7 +255,7 @@ 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 + allow(File).to receive(:open).with(filename,/^r/).and_yield stub_file filename end @@ -280,7 +293,7 @@ let(:pass_file) do filename = "pass_file" stub_file = StringIO.new("asdf\njkl\n") - allow(File).to receive(:open).with(filename, /^r/).and_return stub_file + allow(File).to receive(:open).with(filename, /^r/).and_yield stub_file filename end @@ -317,6 +330,20 @@ end end + context "when using password spraying and :nil_passwords is true" do + let(:password_spray) { true } + let(:nil_passwords) { true } + + context "without password" do + let(:password) { nil } + specify do + expect { |b| collection.each(&b) }.to yield_successive_args( + Metasploit::Framework::Credential.new(public: username, private: nil) + ) + end + end + end + context "when :blank_passwords is true" do let(:blank_passwords) { true } specify do @@ -327,34 +354,244 @@ end end - context 'when :ignore_public is true and :username is nil' do - let(:ignore_public) { true } + context "when given additional_publics and :user_as_pass is true" do let(:username) { nil } - specify do - expect { |b| collection.each(&b) }.to_not yield_control + let(:password) { nil } + let(:additional_publics) { [ "test_public" ] } + let(:user_as_pass) { true } + + specify do + expect { |b| collection.each(&b) }.to yield_successive_args( + Metasploit::Framework::Credential.new(public: "test_public", private: "test_public") + ) end end - context 'when :ignore_private is true and password is nil' do - let(:ignore_private) { true } + context "when given additional_publics, :user_as_pass is true and using password spraying" do + let(:username) { nil } let(:password) { nil } - specify do + let(:additional_publics) { [ "test_public" ] } + let(:user_as_pass) { true } + let(:password_spray) { true } + + specify do expect { |b| collection.each(&b) }.to yield_successive_args( - Metasploit::Framework::Credential.new(public: username, private: nil) + Metasploit::Framework::Credential.new(public: "test_public", private: "test_public") ) end + end - context 'when :ignore_public is also true and username is nil' do - let(:ignore_public) { true } - let(:username) { nil } - specify do - expect { |b| collection.each(&b) }.to yield_successive_args( - Metasploit::Framework::Credential.new(public: nil, private: nil) - ) - end + context "when given additional_publics and :nil_password is true" do + let(:username) { nil } + let(:password) { nil } + let(:additional_publics) { [ "test_public" ] } + let(:nil_passwords) { true } + + specify do + expect { |b| collection.each(&b) }.to yield_successive_args( + Metasploit::Framework::Credential.new(public: "test_public", private: nil) + ) + end + end + + context "when given additional_publics, :nil_password is true, :blank_passwords is true and using password spraying" do + let(:username) { nil } + let(:password) { nil } + let(:additional_publics) { [ "test_public1", "test_public2" ] } + let(:nil_passwords) { true } + let(:blank_passwords) { true } + let(:password_spray) { true } + + specify do + expect { |b| collection.each(&b) }.to yield_successive_args( + Metasploit::Framework::Credential.new(public: "test_public1", private: nil), + Metasploit::Framework::Credential.new(public: "test_public2", private: nil), + Metasploit::Framework::Credential.new(public: "test_public1", private: ""), + Metasploit::Framework::Credential.new(public: "test_public2", private: "") + ) + end + end + + context "when given additional_publics, a user_file, a password and using password spraying" do + let(:username) { nil } + let(:password) { "password" } + let(:additional_publics) { [ "test_public" ] } + let(:user_file) do + filename = "user_file" + stub_file = StringIO.new("asdf\njkl\n") + allow(File).to receive(:open).with(filename, /^r/).and_yield stub_file + + filename + end + + specify do + expect { |b| collection.each(&b) }.to yield_successive_args( + Metasploit::Framework::Credential.new(public: "asdf", private: "password"), + Metasploit::Framework::Credential.new(public: "jkl", private: "password"), + Metasploit::Framework::Credential.new(public: "test_public", private: "password") + ) + end + end + + context "when using password spraying with blank_passwords and a password (but no username)" do + let(:password_spray) { true } + let(:blank_passwords) { true } + let(:username) { nil } + let(:password) { "pass" } + + specify do + expect { |b| collection.each(&b) }.to yield_successive_args() + end + end + + context "when using password spraying with blank_passwords and a username (but no password)" do + let(:password_spray) { true } + let(:blank_passwords) { true } + let(:username) { "user" } + let(:password) { nil } + + specify do + expect { |b| collection.each(&b) }.to yield_successive_args( + Metasploit::Framework::Credential.new(public: username, private: '') + ) + end + end + + context "when using password spraying with blank_passwords and given a user_file" do + let(:password_spray) { true } + let(:blank_passwords) { true } + let(:username) { nil } + let(:password) { nil } + let(:user_file) do + filename = "foo" + stub_file = StringIO.new("asdf\njkl\n") + allow(File).to receive(:open).with(filename,/^r/).and_yield stub_file + + filename + end + + specify do + expect { |b| collection.each(&b) }.to yield_successive_args( + Metasploit::Framework::Credential.new(public: "asdf", private: ''), + Metasploit::Framework::Credential.new(public: "jkl", private: '') + ) end end + context "when every possible option is used" do + let(:nil_passwords) { true } + let(:blank_passwords) { true } + let(:username) { "user" } + let(:password) { "pass" } + let(:user_file) do + filename = "user_file" + stub_file = StringIO.new("userfile") + allow(File).to receive(:open).with(filename,/^r/).and_yield stub_file + + filename + end + let(:pass_file) do + filename = "pass_file" + stub_file = StringIO.new("passfile\n") + allow(File).to receive(:open).with(filename,/^r/).and_yield stub_file + + filename + end + let(:user_as_pass) { true } + let(:userpass_file) do + filename = "userpass_file" + stub_file = StringIO.new("userpass_user userpass_pass\n") + allow(File).to receive(:open).with(filename,/^r/).and_yield stub_file + + filename + end + let(:prepended_creds) { ['test_prepend'] } + let(:additional_privates) { ['test_private'] } + let(:additional_publics) { ['test_public'] } + + specify do + expect { |b| collection.each(&b) }.to yield_successive_args( + "test_prepend", + Metasploit::Framework::Credential.new(public: "user", private: nil), + Metasploit::Framework::Credential.new(public: "user", private: "pass"), + Metasploit::Framework::Credential.new(public: "user", private: "user"), + Metasploit::Framework::Credential.new(public: "user", private: ""), + Metasploit::Framework::Credential.new(public: "user", private: "passfile"), + Metasploit::Framework::Credential.new(public: "user", private: "test_private"), + Metasploit::Framework::Credential.new(public: "userfile", private: nil), + Metasploit::Framework::Credential.new(public: "userfile", private: "pass"), + Metasploit::Framework::Credential.new(public: "userfile", private: "userfile"), + Metasploit::Framework::Credential.new(public: "userfile", private: ""), + Metasploit::Framework::Credential.new(public: "userfile", private: "passfile"), + Metasploit::Framework::Credential.new(public: "userfile", private: "test_private"), + Metasploit::Framework::Credential.new(public: "userpass_user", private: "userpass_pass"), + Metasploit::Framework::Credential.new(public: "test_public", private: nil), # missing this case + Metasploit::Framework::Credential.new(public: "test_public", private: "pass"), + Metasploit::Framework::Credential.new(public: "test_public", private: "test_public"), + Metasploit::Framework::Credential.new(public: "test_public", private: ""), + Metasploit::Framework::Credential.new(public: "test_public", private: "passfile"), + Metasploit::Framework::Credential.new(public: "test_public", private: "test_private") + ) + end + end + + context "when using password spraying in combination with every other option" do + let(:password_spray) { true } + let(:nil_passwords) { true } + let(:blank_passwords) { true } + let(:username) { "user" } + let(:password) { "pass" } + let(:user_file) do + filename = "user_file" + stub_file = StringIO.new("userfile") + allow(File).to receive(:open).with(filename,/^r/).and_yield stub_file + + filename + end + let(:pass_file) do + filename = "pass_file" + stub_file = StringIO.new("passfile\n") + allow(File).to receive(:open).with(filename,/^r/).and_yield stub_file + + filename + end + let(:user_as_pass) { true } + let(:userpass_file) do + filename = "userpass_file" + stub_file = StringIO.new("userpass_user userpass_pass\n") + allow(File).to receive(:open).with(filename,/^r/).and_yield stub_file + + filename + end + let(:prepended_creds) { ['test_prepend'] } + let(:additional_privates) { ['test_private'] } + let(:additional_publics) { ['test_public'] } + + specify do + expect { |b| collection.each(&b) }.to yield_successive_args( + "test_prepend", + Metasploit::Framework::Credential.new(public: "user", private: nil), + Metasploit::Framework::Credential.new(public: "userfile", private: nil), + Metasploit::Framework::Credential.new(public: "test_public", private: nil), + Metasploit::Framework::Credential.new(public: "user", private: "pass"), + Metasploit::Framework::Credential.new(public: "userfile", private: "pass"), + Metasploit::Framework::Credential.new(public: "test_public", private: "pass"), + Metasploit::Framework::Credential.new(public: "user", private: "user"), + Metasploit::Framework::Credential.new(public: "userfile", private: "userfile"), + Metasploit::Framework::Credential.new(public: "test_public", private: "test_public"), + Metasploit::Framework::Credential.new(public: "user", private: ""), + Metasploit::Framework::Credential.new(public: "userfile", private: ""), + Metasploit::Framework::Credential.new(public: "test_public", private: ""), + Metasploit::Framework::Credential.new(public: "user", private: "passfile"), + Metasploit::Framework::Credential.new(public: "userfile", private: "passfile"), + Metasploit::Framework::Credential.new(public: "test_public", private: "passfile"), + Metasploit::Framework::Credential.new(public: "userpass_user", private: "userpass_pass"), + Metasploit::Framework::Credential.new(public: "user", private: "test_private"), + Metasploit::Framework::Credential.new(public: "userfile", private: "test_private"), + Metasploit::Framework::Credential.new(public: "test_public", private: "test_private") + ) + end + end end describe "#empty?" do @@ -424,21 +661,6 @@ expect(collection.empty?).to eq true end end - - context "and :ignore_public is set" do - let(:ignore_public) { true } - specify do - expect(collection.empty?).to eq true - end - - context "and :ignore_private is also set" do - let(:ignore_private) { true } - specify do - expect(collection.empty?).to eq false - end - end - end - end end end