From b7c7650f49752f88f8fd7c10d91e5e0921b6b47c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 18 Feb 2022 07:55:43 -0500 Subject: [PATCH 1/3] Agent: Copy credential generation from WormConfig to new brute_force.py * Create a new module for useful functions for brute-force exploiters * Copy functions for generating all pairs of username/password to brute_force.py * Replace specific functions for generating username/password pairs and username/ssh_key pairs with a single generate_identity_secret_pairs() function, since the distinction is no longer needed. * Add unit tests --- monkey/infection_monkey/utils/brute_force.py | 28 ++++++ .../utils/test_brute_force.py | 94 +++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 monkey/infection_monkey/utils/brute_force.py create mode 100644 monkey/tests/unit_tests/infection_monkey/utils/test_brute_force.py diff --git a/monkey/infection_monkey/utils/brute_force.py b/monkey/infection_monkey/utils/brute_force.py new file mode 100644 index 000000000..e353bd8d9 --- /dev/null +++ b/monkey/infection_monkey/utils/brute_force.py @@ -0,0 +1,28 @@ +from itertools import product +from typing import Any, Iterable, Tuple + + +def generate_identity_secret_pairs( + identities: Iterable, secrets: Iterable +) -> Iterable[Tuple[Any, Any]]: + return product(identities, secrets) + + +def generate_username_password_or_ntlm_hash_combinations( + usernames: Iterable[str], + passwords: Iterable[str], + lm_hashes: Iterable[str], + nt_hashes: Iterable[str], +) -> Iterable[Tuple[str, str, str, str]]: + """ + Returns all combinations of the configurations users and passwords or lm/ntlm hashes + :return: + """ + cred_list = [] + for cred in product(usernames, passwords, [""], [""]): + cred_list.append(cred) + for cred in product(usernames, [""], lm_hashes, [""]): + cred_list.append(cred) + for cred in product(usernames, [""], [""], nt_hashes): + cred_list.append(cred) + return cred_list diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_brute_force.py b/monkey/tests/unit_tests/infection_monkey/utils/test_brute_force.py new file mode 100644 index 000000000..6d79c361e --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_brute_force.py @@ -0,0 +1,94 @@ +from itertools import chain, compress + +import pytest + +from infection_monkey.utils.brute_force import ( + generate_identity_secret_pairs, + generate_username_password_or_ntlm_hash_combinations, +) + +USERNAMES = ["shaggy", "scooby"] +PASSWORDS = ["1234", "iloveyou", "the_cake_is_a_lie"] +EXPECTED_USERNAME_PASSWORD_PAIRS = { + (USERNAMES[0], PASSWORDS[0]), + (USERNAMES[0], PASSWORDS[1]), + (USERNAMES[0], PASSWORDS[2]), + (USERNAMES[1], PASSWORDS[0]), + (USERNAMES[1], PASSWORDS[1]), + (USERNAMES[1], PASSWORDS[2]), +} + +LM_HASHES = ["DEADBEEF", "FACADE"] +EXPECTED_USERNAME_LM_PAIRS = { + (USERNAMES[0], LM_HASHES[0]), + (USERNAMES[0], LM_HASHES[1]), + (USERNAMES[1], LM_HASHES[0]), + (USERNAMES[1], LM_HASHES[1]), +} + +NT_HASHES = ["FADED", "ADDED"] +EXPECTED_USERNAME_NT_PAIRS = { + (USERNAMES[0], NT_HASHES[0]), + (USERNAMES[0], NT_HASHES[1]), + (USERNAMES[1], NT_HASHES[0]), + (USERNAMES[1], NT_HASHES[1]), +} + + +def test_generate_username_password_pairs(): + generated_pairs = generate_identity_secret_pairs(USERNAMES, PASSWORDS) + assert set(generated_pairs) == EXPECTED_USERNAME_PASSWORD_PAIRS + + +@pytest.mark.parametrize("usernames, passwords", [(USERNAMES, []), ([], PASSWORDS), ([], [])]) +def test_generate_username_password_pairs__empty_inputs(usernames, passwords): + generated_pairs = generate_identity_secret_pairs(usernames, passwords) + assert len(set(generated_pairs)) == 0 + + +def generate_expected_username_password_hash_combinations( + passwords: bool, lm_hashes: bool, nt_hashes: bool +): + possible_combinations = ( + {(p[0], p[1], "", "") for p in EXPECTED_USERNAME_PASSWORD_PAIRS}, + {(p[0], "", p[1], "") for p in EXPECTED_USERNAME_LM_PAIRS}, + {(p[0], "", "", p[1]) for p in EXPECTED_USERNAME_NT_PAIRS}, + ) + + return set( + chain.from_iterable(compress(possible_combinations, (passwords, lm_hashes, nt_hashes))) + ) + + +def test_generate_username_password_or_ntlm_hash_pairs__empty_usernames(): + generated_pairs = generate_username_password_or_ntlm_hash_combinations( + [], PASSWORDS, LM_HASHES, NT_HASHES + ) + + assert len(set(generated_pairs)) == 0 + + +@pytest.mark.parametrize( + "passwords,lm_hashes,nt_hashes", + [ + (PASSWORDS, LM_HASHES, NT_HASHES), + ([], LM_HASHES, NT_HASHES), + (PASSWORDS, [], NT_HASHES), + (PASSWORDS, LM_HASHES, []), + (PASSWORDS, [], []), + ([], LM_HASHES, []), + ([], [], NT_HASHES), + ([], [], []), + ], +) +def test_generate_username_password_or_ntlm_hash_pairs__with_usernames( + passwords, lm_hashes, nt_hashes +): + expected_credential_combinations = generate_expected_username_password_hash_combinations( + bool(passwords), bool(lm_hashes), bool(nt_hashes) + ) + generated_pairs = generate_username_password_or_ntlm_hash_combinations( + USERNAMES, passwords, lm_hashes, nt_hashes + ) + + assert set(generated_pairs) == expected_credential_combinations From 5c872a67c3b94490abf75ac5131ea6eefa381aee Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 18 Feb 2022 08:01:49 -0500 Subject: [PATCH 2/3] Agent: Simplify generate_username_password_or_ntlm_hash_combinations() --- monkey/infection_monkey/utils/brute_force.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/monkey/infection_monkey/utils/brute_force.py b/monkey/infection_monkey/utils/brute_force.py index e353bd8d9..ff74d9712 100644 --- a/monkey/infection_monkey/utils/brute_force.py +++ b/monkey/infection_monkey/utils/brute_force.py @@ -1,4 +1,4 @@ -from itertools import product +from itertools import chain, product from typing import Any, Iterable, Tuple @@ -18,11 +18,8 @@ def generate_username_password_or_ntlm_hash_combinations( Returns all combinations of the configurations users and passwords or lm/ntlm hashes :return: """ - cred_list = [] - for cred in product(usernames, passwords, [""], [""]): - cred_list.append(cred) - for cred in product(usernames, [""], lm_hashes, [""]): - cred_list.append(cred) - for cred in product(usernames, [""], [""], nt_hashes): - cred_list.append(cred) - return cred_list + return chain( + product(usernames, passwords, [""], [""]), + product(usernames, [""], lm_hashes, [""]), + product(usernames, [""], [""], nt_hashes), + ) From 4d6f552ba22af7c1689ed03af5df6eea16c62baf Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 18 Feb 2022 09:02:41 -0500 Subject: [PATCH 3/3] Agent: Add documentation to functions in brute_force. --- monkey/infection_monkey/utils/brute_force.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/utils/brute_force.py b/monkey/infection_monkey/utils/brute_force.py index ff74d9712..192905aa8 100644 --- a/monkey/infection_monkey/utils/brute_force.py +++ b/monkey/infection_monkey/utils/brute_force.py @@ -5,6 +5,13 @@ from typing import Any, Iterable, Tuple def generate_identity_secret_pairs( identities: Iterable, secrets: Iterable ) -> Iterable[Tuple[Any, Any]]: + """ + Generates all possible combinations of identities and secrets (e.g. usernames and passwords). + :param identities: An iterable containing identity components of a credential pair + :param secrets: An iterable containing secret components of a credential pair + :return: An iterable of all combinations of identity/secret pairs. If either identities or + secrets is empty, an empty iterator is returned. + """ return product(identities, secrets) @@ -15,8 +22,16 @@ def generate_username_password_or_ntlm_hash_combinations( nt_hashes: Iterable[str], ) -> Iterable[Tuple[str, str, str, str]]: """ - Returns all combinations of the configurations users and passwords or lm/ntlm hashes - :return: + Generates all possible combinations of the following: username/password, username/lm_hash, + username/nt_hash. + :param usernames: An iterable containing usernames + :param passwords: An iterable containing passwords + :param lm_hashes: An iterable containing lm_hashes + :param nt_hashes: An iterable containing nt_hashes + :return: An iterable containing tuples of all possible credentials combinations. Note that each + tuple will contain a username and at most one secret component (i.e. password, lm_hash, + nt_hash). If usernames is empty, an empty iterator is returned. If all secret component + iterators are empty, an empty iterator is returned. """ return chain( product(usernames, passwords, [""], [""]),