-
Notifications
You must be signed in to change notification settings - Fork 14k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixing multiple bugs in credential generation + refactoring #19653
base: master
Are you sure you want to change the base?
Fixing multiple bugs in credential generation + refactoring #19653
Conversation
let(:password) { nil } | ||
specify do | ||
expect { |b| collection.each(&b) }.to yield_successive_args( | ||
Metasploit::Framework::Credential.new(public: username, private: nil), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fails because the collection yields nothing in case of "password spraying".
# REMOVE BEFORE COMMIT currently failing | ||
specify do | ||
expect { |b| collection.each(&b) }.to yield_successive_args( | ||
Metasploit::Framework::Credential.new(public: "test_public", private: "test_public"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Crashes because of a non-existent variable.
# REMOVE BEFORE COMMIT currently failing | ||
specify do | ||
expect { |b| collection.each(&b) }.to yield_successive_args( | ||
Metasploit::Framework::Credential.new(public: "test_public", private: "test_public"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Crashes because of a non-existent variable.
# REMOVE BEFORE COMMIT: this option is ignored currently for additional_publics | ||
specify do | ||
expect { |b| collection.each(&b) }.to yield_successive_args( | ||
Metasploit::Framework::Credential.new(public: "test_public", private: nil), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This case yields nothing: nil passwords are ignored in additional_public credential generation. (is there a reason for this?)
Metasploit::Framework::Credential.new(public: "test_public1", private: ""), | ||
Metasploit::Framework::Credential.new(public: "test_public2", private: ""), | ||
Metasploit::Framework::Credential.new(public: "test_public1", private: nil), | ||
Metasploit::Framework::Credential.new(public: "test_public2", private: nil), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fails because the password spraying option does not work with additional_publics.
It generates the correct list of credentials, but not in the correct order.
expect { |b| collection.each(&b) }.to yield_successive_args( | ||
Metasploit::Framework::Credential.new(public: "adsf", private: "password"), | ||
Metasploit::Framework::Credential.new(public: "jkl", private: "password"), | ||
Metasploit::Framework::Credential.new(public: "test_public", private: "password"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fails because for some reason, in addition to those 3 credentials, it also uses the additional_publics ("test_public") as a password.
|
||
# REMOVE BEFORE COMMIT: this yields empty creds (no username, no pass) | ||
specify do | ||
expect { |b| collection.each(&b) }.to yield_successive_args() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fails because in this case (no username defined!) it generates a set of empty credentials.
|
||
specify do | ||
expect { |b| collection.each(&b) }.to yield_successive_args( | ||
Metasploit::Framework::Credential.new(public: username, private: ''), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fails because it generates no credential.
But in this case we have a username and are asking for a blank password so it should generate this. (this is the case I chose to describe in the issue)
64aeccc
to
4e9d771
Compare
…monstrate multiple bugs in the each method
4e9d771
to
7cab903
Compare
expect { |b| collection.each(&b) }.to yield_successive_args( | ||
Metasploit::Framework::Credential.new(public: "asdf", private: ''), | ||
Metasploit::Framework::Credential.new(public: "jkl", private: ''), | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fails also because it yields nothing. The given file is completely ignored even if we would like to have the usernames with the blank passwords generated.
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") | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently failing because of the various bugs that make the other tests fail.
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"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently failing because of the various bugs that make the other tests fail.
end | ||
end | ||
|
||
context "when every possible option is used" do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to create some generic non-regression tests to demonstrate how the credentials should be yielded in case of various user/password data source (in fact in case of all them being active).
The idea is mainly to check that all credentials are present, but also to have the proper order.
I am not sure they should be kept after the fixes/refacto have been made (as there may be some question regarding the order, many are probably valid?) but here they will help demonstrate what we expect.
…of password spraying) to make all tests pass
|
||
additional_publics.each do |add_public| | ||
yield Metasploit::Framework::Credential.new(public: add_public, private: add_public, realm: realm, private_type: :password) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can easily see the potential for a refactoring here as many blocks are almost complete c/p of previous blocks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know it's still in draft but I thought I'd give it a once over anyway. Looks great and thanks for following up on the last pull request 🚀
context "when using password spraying" do | ||
let(:password_spray) { true } | ||
|
||
# REMOVE BEFORE COMMIT: yields nothings, fails because of bug in method | ||
context "without password" do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would your thoughts be on not nesting the context
s here. My thinking is it would make searching for failing tests much harder, as the sting would be broken across multiple lines whereas if it was a single context it would be much easier to find.
Here is where I usually check "best practices" when writing tests, just in case it's useful:
https://www.betterspecs.org/#contexts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok with me 👍
I struggled a bit to find a proper organization of the cases, given the already existing specs.
Do you want to merge only those two, or do you think I should also do the same for the other cases?
(meaning having only 1-level deep contexts)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to merge only those two, or do you think I should also do the same for the other cases? (meaning having only 1-level deep contexts)
I think having only one context for each test would be better when combined with this comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, fine with me. I'll change that!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was a bit painful to do but I agree, once it is done, it is much clearer to read and to debug 🎉
end | ||
end | ||
|
||
context "when given a user_file" do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm assuming from the comment below you want to have blank_passwords
set here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is actually set up on line 434, but with the nested contexts it is not easy to follow along...
I am wondering if it is better to duplicate setup code so as to have 1-level context where you can properly see all the setup, or be a bit more dry and nest.
Happy to follow what you feel is best.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
better to duplicate setup code so as to have 1-level context where you can properly see all the setup
I would personally be in favor of that as it is much easier to see the configuration for each test.
Fixes #19652
Summary
In this PR, I added various tests on the CredentialCollection class, all of them currently failing and illustrating what I think are bugs.
Issues can be grouped in two categories:
additional_publics
arraypassword_spray
optionI added some comments on each test explaining what it is currently yielding (instead of the result I think should be expected - let me know if I misunderstood anything).
Refactoring opportunity
You will see that there are many bugs linked to the
password_spray
option.It seems that this branch of the code has been copy/pasted from the original branch (that didn't handle password spraying) and adapted.
There was not a lot of tests on this part of the code + the structure is difficult to maintain which probably explains those issues.
The original code branch (without spraying:
#each_unfiltered_username_first
) is also a bit complex with a lot of duplicated code.I think there are some opportunities to simplify and clarify the code a lot with some refactoring.
Non-regression tests
For this, I created 2 additional "non-regression" tests that activate all options and show how the credentials should be yielded.
There is one test with password spraying, one without.
I don't know if we want to keep them after the refactoring, but they will surely help.
Also, even if a part of the order in which the credentials are yielded should be "fixed", there are questions around others.
For example:
user_as_pass
option in case ofpassword_spraying
?Additional question
As a side note, the
nil_passwords
,blank_passwords
anduser_as_pass
options do not apply to the userpass file.Is it expected behaviour or should we extend this behaviour to users yielded in the userpass file?
Next steps