From cafbe97880eb1a10955178a46f783d035026b15e Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 24 Mar 2022 18:03:59 +0100 Subject: [PATCH 01/19] Agent: Add interface for Credentials Store --- .../credential_store/__init__.py | 1 + .../credential_store/i_credentials_store.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 monkey/infection_monkey/credential_store/__init__.py create mode 100644 monkey/infection_monkey/credential_store/i_credentials_store.py diff --git a/monkey/infection_monkey/credential_store/__init__.py b/monkey/infection_monkey/credential_store/__init__.py new file mode 100644 index 000000000..636e9baa7 --- /dev/null +++ b/monkey/infection_monkey/credential_store/__init__.py @@ -0,0 +1 @@ +from .i_credentials_store import ICredentialsStore diff --git a/monkey/infection_monkey/credential_store/i_credentials_store.py b/monkey/infection_monkey/credential_store/i_credentials_store.py new file mode 100644 index 000000000..7730c99d2 --- /dev/null +++ b/monkey/infection_monkey/credential_store/i_credentials_store.py @@ -0,0 +1,19 @@ +import abc +from typing import Mapping + + +class ICredentialsStore(metaclass=abc.ABCMeta): + @abc.abstractmethod + def add_credentials(self, credentials_to_add: Mapping = {}) -> None: + """ + Method that adds credentials to the CredentialStore + :param Credentials credentials: The credentials which will be added + """ + + @abc.abstractmethod + def get_credentials(self) -> Mapping: + """ + Method that gets credentials from the ControlChannel + :return: A squence of Credentials that have been added for propagation + :rtype: Mapping + """ From b5d2d1d64172519a61a54cf0fa8cf1c0b33457bd Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 24 Mar 2022 18:39:17 +0100 Subject: [PATCH 02/19] Agent: Implement concrete Credentials Store --- .../credential_store/__init__.py | 1 + .../credential_store/credentials_store.py | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 monkey/infection_monkey/credential_store/credentials_store.py diff --git a/monkey/infection_monkey/credential_store/__init__.py b/monkey/infection_monkey/credential_store/__init__.py index 636e9baa7..3b3f4475f 100644 --- a/monkey/infection_monkey/credential_store/__init__.py +++ b/monkey/infection_monkey/credential_store/__init__.py @@ -1 +1,2 @@ from .i_credentials_store import ICredentialsStore +from .credentials_store import CredentialsStore diff --git a/monkey/infection_monkey/credential_store/credentials_store.py b/monkey/infection_monkey/credential_store/credentials_store.py new file mode 100644 index 000000000..a0500804d --- /dev/null +++ b/monkey/infection_monkey/credential_store/credentials_store.py @@ -0,0 +1,29 @@ +from typing import Mapping + +from .i_credentials_store import ICredentialsStore + + +class CredentialsStore(ICredentialsStore): + def __init__(self, credentials: Mapping = None): + self.stored_credentials = credentials + + def add_credentials(self, credentials_to_add: Mapping) -> None: + if self.stored_credentials is None: + self.stored_credentials = {} + + for key, value in credentials_to_add.items(): + if key not in self.stored_credentials: + self.stored_credentials[key] = [] + + if key != "exploit_ssh_keys": + self.stored_credentials[key] = list( + sorted(set(self.stored_credentials[key]).union(credentials_to_add[key])) + ) + else: + self.stored_credentials[key] += credentials_to_add[key] + self.stored_credentials[key] = [ + dict(s) for s in set(frozenset(d.items()) for d in self.stored_credentials[key]) + ] + + def get_credentials(self) -> Mapping: + return self.stored_credentials From 162dd0a9200e67478780b7912b9fb308c4ec43e2 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 25 Mar 2022 15:24:59 +0100 Subject: [PATCH 03/19] UT: Add Credentials Store tests --- .../credential_store/test_credential_store.py | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 monkey/tests/unit_tests/infection_monkey/credential_store/test_credential_store.py diff --git a/monkey/tests/unit_tests/infection_monkey/credential_store/test_credential_store.py b/monkey/tests/unit_tests/infection_monkey/credential_store/test_credential_store.py new file mode 100644 index 000000000..83382dc3e --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/credential_store/test_credential_store.py @@ -0,0 +1,81 @@ +from unittest.mock import MagicMock + +import pytest + +from infection_monkey.credential_store import AggregatingCredentialsStore + +DEFAULT_CREDENTIALS = { + "exploit_user_list": ["Administrator", "root", "user1"], + "exploit_password_list": [ + "root", + "123456", + "password", + "123456789", + ], + "exploit_lm_hash_list": ["aasdf23asd1fdaasadasdfas"], + "exploit_ntlm_hash_list": ["qw4trklxklvznksbhasd1231", "asdfadvxvsdftw3e3421234123412"], + "exploit_ssh_keys": [ + { + "public_key": "ssh-ed25519 AAAAC3NzEIFaJ7xH+Yoxd\n", + "private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BdHIAAAAGYXjl0j66VAKruPEKjS3A=\n" + "-----END OPENSSH PRIVATE KEY-----\n", + "user": "ubuntu", + "ip": "10.0.3.15", + }, + {"public_key": "some_public_key", "private_key": "some_private_key"}, + ], +} + + +SAMPLE_CREDENTIALS = { + "exploit_user_list": ["user1", "user3"], + "exploit_password_list": ["abcdefg", "root"], + "exploit_ssh_keys": [{"public_key": "some_public_key", "private_key": "some_private_key"}], + "exploit_ntlm_hash_list": [], +} + + +@pytest.fixture +def aggregating_credentials_store() -> AggregatingCredentialsStore: + return AggregatingCredentialsStore() + + +@pytest.mark.parametrize("credentials_to_store", [DEFAULT_CREDENTIALS, SAMPLE_CREDENTIALS]) +def test_get_credentials_from_store(aggregating_credentials_store, credentials_to_store): + get_updated_credentials_for_propagation = MagicMock(return_value=credentials_to_store) + + aggregating_credentials_store.get_credentials(get_updated_credentials_for_propagation) + + assert aggregating_credentials_store.stored_credentials == credentials_to_store + + +def test_add_credentials_to_empty_store(aggregating_credentials_store): + + aggregating_credentials_store.add_credentials(SAMPLE_CREDENTIALS) + + assert aggregating_credentials_store.stored_credentials == SAMPLE_CREDENTIALS + + +def test_add_credentials_to_full_store(aggregating_credentials_store): + get_updated_credentials_for_propagation = MagicMock(return_value=DEFAULT_CREDENTIALS) + + aggregating_credentials_store.get_credentials(get_updated_credentials_for_propagation) + + aggregating_credentials_store.add_credentials(SAMPLE_CREDENTIALS) + + actual_stored_credentials = aggregating_credentials_store.stored_credentials + + assert actual_stored_credentials["exploit_user_list"] == [ + "Administrator", + "root", + "user1", + "user3", + ] + assert actual_stored_credentials["exploit_password_list"] == [ + "123456", + "123456789", + "abcdefg", + "password", + "root", + ] + assert actual_stored_credentials["exploit_ssh_keys"] == DEFAULT_CREDENTIALS["exploit_ssh_keys"] From 5060ddb5d1105d0938d6f7672451de73941041b7 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 28 Mar 2022 15:11:20 +0200 Subject: [PATCH 04/19] Agent: Fix logic in concrete Credentials Store --- .../credential_store/__init__.py | 2 +- .../aggregating_credentials_store.py | 68 ++++++++++++++++++ .../credential_store/credentials_store.py | 29 -------- .../credential_store/i_credentials_store.py | 17 ++--- .../credential_store/test_credential_store.py | 69 ++++++++++++------- 5 files changed, 122 insertions(+), 63 deletions(-) create mode 100644 monkey/infection_monkey/credential_store/aggregating_credentials_store.py delete mode 100644 monkey/infection_monkey/credential_store/credentials_store.py diff --git a/monkey/infection_monkey/credential_store/__init__.py b/monkey/infection_monkey/credential_store/__init__.py index 3b3f4475f..e05ce3160 100644 --- a/monkey/infection_monkey/credential_store/__init__.py +++ b/monkey/infection_monkey/credential_store/__init__.py @@ -1,2 +1,2 @@ from .i_credentials_store import ICredentialsStore -from .credentials_store import CredentialsStore +from .aggregating_credentials_store import AggregatingCredentialsStore diff --git a/monkey/infection_monkey/credential_store/aggregating_credentials_store.py b/monkey/infection_monkey/credential_store/aggregating_credentials_store.py new file mode 100644 index 000000000..d855b98dd --- /dev/null +++ b/monkey/infection_monkey/credential_store/aggregating_credentials_store.py @@ -0,0 +1,68 @@ +import logging +from typing import Iterable, Mapping + +from common.common_consts.credential_component_type import CredentialComponentType +from infection_monkey.i_control_channel import IControlChannel +from infection_monkey.i_puppet import Credentials + +from .i_credentials_store import ICredentialsStore + +logger = logging.getLogger(__name__) + + +class AggregatingCredentialsStore(ICredentialsStore): + def __init__(self, control_channel: IControlChannel): + self.stored_credentials = {} + self._control_channel = control_channel + + def add_credentials(self, credentials_to_add: Iterable[Credentials]) -> None: + for credentials in credentials_to_add: + usernames = [ + identity.username + for identity in credentials.identities + if identity.credential_type is CredentialComponentType.USERNAME + ] + self._set_attribute("exploit_user_list", usernames) + + for secret in credentials.secrets: + if secret.credential_type is CredentialComponentType.PASSWORD: + self._set_attribute("exploit_password_list", [secret.password]) + elif secret.credential_type is CredentialComponentType.LM_HASH: + self._set_attribute("exploit_lm_hash_list", [secret.lm_hash]) + elif secret.credential_type is CredentialComponentType.NT_HASH: + self._set_attribute("exploit_ntlm_hash_list", [secret.nt_hash]) + elif secret.credential_type is CredentialComponentType.SSH_KEYPAIR: + self._set_attribute( + "exploit_ssh_keys", + [{"public_key": secret.public_key, "private_key": secret.private_key}], + ) + + def get_credentials(self): + try: + propagation_credentials = self._control_channel.get_credentials_for_propagation() + self._aggregate_credentials(propagation_credentials) + except Exception as ex: + self.stored_credentials = {} + logger.error(f"Error while attempting to retrieve credentials for propagation: {ex}") + + def _aggregate_credentials(self, credentials_to_aggr: Mapping): + for cred_attr, credentials_values in credentials_to_aggr.items(): + if credentials_values: + self._set_attribute(cred_attr, credentials_values) + + def _set_attribute(self, attribute_to_be_set, credentials_values): + if attribute_to_be_set not in self.stored_credentials: + self.stored_credentials[attribute_to_be_set] = [] + + if isinstance(credentials_values[0], dict): + self.stored_credentials.setdefault(attribute_to_be_set, []).extend(credentials_values) + self.stored_credentials[attribute_to_be_set] = [ + dict(s_c) + for s_c in set( + frozenset(d_c.items()) for d_c in self.stored_credentials[attribute_to_be_set] + ) + ] + else: + self.stored_credentials[attribute_to_be_set] = sorted( + list(set(self.stored_credentials[attribute_to_be_set]).union(credentials_values)) + ) diff --git a/monkey/infection_monkey/credential_store/credentials_store.py b/monkey/infection_monkey/credential_store/credentials_store.py deleted file mode 100644 index a0500804d..000000000 --- a/monkey/infection_monkey/credential_store/credentials_store.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import Mapping - -from .i_credentials_store import ICredentialsStore - - -class CredentialsStore(ICredentialsStore): - def __init__(self, credentials: Mapping = None): - self.stored_credentials = credentials - - def add_credentials(self, credentials_to_add: Mapping) -> None: - if self.stored_credentials is None: - self.stored_credentials = {} - - for key, value in credentials_to_add.items(): - if key not in self.stored_credentials: - self.stored_credentials[key] = [] - - if key != "exploit_ssh_keys": - self.stored_credentials[key] = list( - sorted(set(self.stored_credentials[key]).union(credentials_to_add[key])) - ) - else: - self.stored_credentials[key] += credentials_to_add[key] - self.stored_credentials[key] = [ - dict(s) for s in set(frozenset(d.items()) for d in self.stored_credentials[key]) - ] - - def get_credentials(self) -> Mapping: - return self.stored_credentials diff --git a/monkey/infection_monkey/credential_store/i_credentials_store.py b/monkey/infection_monkey/credential_store/i_credentials_store.py index 7730c99d2..2ac10192b 100644 --- a/monkey/infection_monkey/credential_store/i_credentials_store.py +++ b/monkey/infection_monkey/credential_store/i_credentials_store.py @@ -1,19 +1,20 @@ import abc -from typing import Mapping +from typing import Iterable + +from infection_monkey.i_puppet import Credentials class ICredentialsStore(metaclass=abc.ABCMeta): @abc.abstractmethod - def add_credentials(self, credentials_to_add: Mapping = {}) -> None: - """ + def add_credentials(self, credentials_to_add: Iterable[Credentials]) -> None: + """a Method that adds credentials to the CredentialStore - :param Credentials credentials: The credentials which will be added + :param Credentials credentials: The credentials that will be added """ @abc.abstractmethod - def get_credentials(self) -> Mapping: + def get_credentials(self) -> None: """ - Method that gets credentials from the ControlChannel - :return: A squence of Credentials that have been added for propagation - :rtype: Mapping + Method that retrieves credentials from the store + :return: Credentials that can be used for propagation """ diff --git a/monkey/tests/unit_tests/infection_monkey/credential_store/test_credential_store.py b/monkey/tests/unit_tests/infection_monkey/credential_store/test_credential_store.py index 83382dc3e..1035de4d0 100644 --- a/monkey/tests/unit_tests/infection_monkey/credential_store/test_credential_store.py +++ b/monkey/tests/unit_tests/infection_monkey/credential_store/test_credential_store.py @@ -2,66 +2,83 @@ from unittest.mock import MagicMock import pytest +from infection_monkey.credential_collectors import Password, SSHKeypair, Username from infection_monkey.credential_store import AggregatingCredentialsStore +from infection_monkey.i_puppet import Credentials DEFAULT_CREDENTIALS = { "exploit_user_list": ["Administrator", "root", "user1"], - "exploit_password_list": [ - "root", - "123456", - "password", - "123456789", - ], + "exploit_password_list": ["123456", "123456789", "password", "root"], "exploit_lm_hash_list": ["aasdf23asd1fdaasadasdfas"], - "exploit_ntlm_hash_list": ["qw4trklxklvznksbhasd1231", "asdfadvxvsdftw3e3421234123412"], + "exploit_ntlm_hash_list": ["asdfadvxvsdftw3e3421234123412", "qw4trklxklvznksbhasd1231"], "exploit_ssh_keys": [ + {"public_key": "some_public_key", "private_key": "some_private_key"}, { "public_key": "ssh-ed25519 AAAAC3NzEIFaJ7xH+Yoxd\n", "private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BdHIAAAAGYXjl0j66VAKruPEKjS3A=\n" "-----END OPENSSH PRIVATE KEY-----\n", - "user": "ubuntu", - "ip": "10.0.3.15", }, - {"public_key": "some_public_key", "private_key": "some_private_key"}, ], } -SAMPLE_CREDENTIALS = { +PROPAGATION_CREDENTIALS = { "exploit_user_list": ["user1", "user3"], "exploit_password_list": ["abcdefg", "root"], "exploit_ssh_keys": [{"public_key": "some_public_key", "private_key": "some_private_key"}], - "exploit_ntlm_hash_list": [], } +TELEM_CREDENTIALS = [ + Credentials( + [Username("user1"), Username("user3")], + [ + Password("abcdefg"), + Password("root"), + SSHKeypair(public_key="some_public_key", private_key="some_private_key"), + ], + ) +] + @pytest.fixture def aggregating_credentials_store() -> AggregatingCredentialsStore: - return AggregatingCredentialsStore() + control_channel = MagicMock() + control_channel.get_credentials_for_propagation.return_value = DEFAULT_CREDENTIALS + return AggregatingCredentialsStore(control_channel) -@pytest.mark.parametrize("credentials_to_store", [DEFAULT_CREDENTIALS, SAMPLE_CREDENTIALS]) -def test_get_credentials_from_store(aggregating_credentials_store, credentials_to_store): - get_updated_credentials_for_propagation = MagicMock(return_value=credentials_to_store) +def test_get_credentials_from_store(aggregating_credentials_store): + aggregating_credentials_store.get_credentials() - aggregating_credentials_store.get_credentials(get_updated_credentials_for_propagation) + actual_stored_credentials = aggregating_credentials_store.stored_credentials - assert aggregating_credentials_store.stored_credentials == credentials_to_store + assert ( + actual_stored_credentials["exploit_user_list"] == DEFAULT_CREDENTIALS["exploit_user_list"] + ) + assert ( + actual_stored_credentials["exploit_password_list"] + == DEFAULT_CREDENTIALS["exploit_password_list"] + ) + assert ( + actual_stored_credentials["exploit_ntlm_hash_list"] + == DEFAULT_CREDENTIALS["exploit_ntlm_hash_list"] + ) + + for ssh_keypair in actual_stored_credentials["exploit_ssh_keys"]: + assert ssh_keypair in DEFAULT_CREDENTIALS["exploit_ssh_keys"] def test_add_credentials_to_empty_store(aggregating_credentials_store): + aggregating_credentials_store.add_credentials(TELEM_CREDENTIALS) - aggregating_credentials_store.add_credentials(SAMPLE_CREDENTIALS) - - assert aggregating_credentials_store.stored_credentials == SAMPLE_CREDENTIALS + assert aggregating_credentials_store.stored_credentials == PROPAGATION_CREDENTIALS def test_add_credentials_to_full_store(aggregating_credentials_store): - get_updated_credentials_for_propagation = MagicMock(return_value=DEFAULT_CREDENTIALS) - aggregating_credentials_store.get_credentials(get_updated_credentials_for_propagation) + aggregating_credentials_store.get_credentials() - aggregating_credentials_store.add_credentials(SAMPLE_CREDENTIALS) + aggregating_credentials_store.add_credentials(TELEM_CREDENTIALS) actual_stored_credentials = aggregating_credentials_store.stored_credentials @@ -78,4 +95,6 @@ def test_add_credentials_to_full_store(aggregating_credentials_store): "password", "root", ] - assert actual_stored_credentials["exploit_ssh_keys"] == DEFAULT_CREDENTIALS["exploit_ssh_keys"] + + for ssh_keypair in actual_stored_credentials["exploit_ssh_keys"]: + assert ssh_keypair in DEFAULT_CREDENTIALS["exploit_ssh_keys"] From eb6342e2f8ecd53a00ca3881fd31774390b26755 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 28 Mar 2022 09:45:45 -0400 Subject: [PATCH 05/19] Agent: Add public credentials property to CredentialsTelem --- .../telemetry/credentials_telem.py | 4 ++ .../telemetry/test_credentials_telem.py | 42 ++++++++++++------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/monkey/infection_monkey/telemetry/credentials_telem.py b/monkey/infection_monkey/telemetry/credentials_telem.py index c0573d942..4f5c43aa4 100644 --- a/monkey/infection_monkey/telemetry/credentials_telem.py +++ b/monkey/infection_monkey/telemetry/credentials_telem.py @@ -17,6 +17,10 @@ class CredentialsTelem(BaseTelem): """ self._credentials = credentials + @property + def credentials(self) -> Iterable[Credentials]: + return iter(self._credentials) + def send(self, log_data=True): super().send(log_data=False) diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py b/monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py index a3d1e3f6f..13c93f60f 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/test_credentials_telem.py @@ -1,37 +1,51 @@ import json +import pytest + from infection_monkey.credential_collectors import Password, SSHKeypair, Username from infection_monkey.i_puppet import Credentials from infection_monkey.telemetry.credentials_telem import CredentialsTelem +USERNAME = "m0nkey" +PASSWORD = "mmm" +PUBLIC_KEY = "pub_key" +PRIVATE_KEY = "priv_key" -def test_credential_telem_send(spy_send_telemetry): - username = "m0nkey" - password = "mmm" - public_key = "pub_key" - private_key = "priv_key" + +@pytest.fixture +def credentials_for_test(): + + return Credentials( + [Username(USERNAME)], [Password(PASSWORD), SSHKeypair(PRIVATE_KEY, PUBLIC_KEY)] + ) + + +def test_credential_telem_send(spy_send_telemetry, credentials_for_test): expected_data = [ { - "identities": [{"username": username, "credential_type": "USERNAME"}], + "identities": [{"username": USERNAME, "credential_type": "USERNAME"}], "secrets": [ - {"password": password, "credential_type": "PASSWORD"}, + {"password": PASSWORD, "credential_type": "PASSWORD"}, { - "private_key": "pub_key", - "public_key": "priv_key", + "private_key": PRIVATE_KEY, + "public_key": PUBLIC_KEY, "credential_type": "SSH_KEYPAIR", }, ], } ] - credentials = Credentials( - [Username(username)], [Password(password), SSHKeypair(public_key, private_key)] - ) - - telem = CredentialsTelem([credentials]) + telem = CredentialsTelem([credentials_for_test]) telem.send() expected_data = json.dumps(expected_data, cls=telem.json_encoder) assert spy_send_telemetry.data == expected_data assert spy_send_telemetry.telem_category == "credentials" + + +def test_credentials_property(credentials_for_test): + telem = CredentialsTelem([credentials_for_test]) + + assert len(list(telem.credentials)) == 1 + assert list(telem.credentials)[0] == credentials_for_test From 4de90584c9db3f6154568a74f8c2451ac35e6816 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 28 Mar 2022 21:11:35 +0200 Subject: [PATCH 06/19] Agent: Add Credentials intercepting telemetry messenger --- ...ntials_intercepting_telemetry_messenger.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 monkey/infection_monkey/telemetry/messengers/credentials_intercepting_telemetry_messenger.py diff --git a/monkey/infection_monkey/telemetry/messengers/credentials_intercepting_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/credentials_intercepting_telemetry_messenger.py new file mode 100644 index 000000000..541800577 --- /dev/null +++ b/monkey/infection_monkey/telemetry/messengers/credentials_intercepting_telemetry_messenger.py @@ -0,0 +1,38 @@ +from functools import singledispatch + +from infection_monkey.credential_store import ICredentialsStore +from infection_monkey.telemetry.credentials_telem import CredentialsTelem +from infection_monkey.telemetry.i_telem import ITelem +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger + + +class CredentialsInterceptingTelemetryMessenger(ITelemetryMessenger): + def __init__( + self, telemetry_messenger: ITelemetryMessenger, credentials_store: ICredentialsStore + ): + self._telemetry_messenger = telemetry_messenger + self._credentials_store = credentials_store + + def send_telemetry(self, telemetry: ITelem): + _send_telemetry(telemetry, self._telemetry_messenger, self._credentials_store) + + +# Note: We can use @singledispatchmethod instead of @singledispatch if we migrate to Python 3.8 or +# later. +@singledispatch +def _send_telemetry( + telemetry: ITelem, + telemetry_messenger: ITelemetryMessenger, + credentials_store: ICredentialsStore, +): + telemetry_messenger.send_telemetry(telemetry) + + +@_send_telemetry.register +def _( + telemetry: CredentialsTelem, + telemetry_messenger: ITelemetryMessenger, + credentials_store: ICredentialsStore, +): + credentials_store.add_credentials(telemetry.credentials) + telemetry_messenger.send_telemetry(telemetry) From d434c20bcbd861854a9051b476af7b86a27e0832 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 28 Mar 2022 21:14:46 +0200 Subject: [PATCH 07/19] Agent: Inject credentials store to Automated Master Intercept credentials and update the credentials store using credentials intercepting telemetry messenger --- .../infection_monkey/master/automated_master.py | 7 ++++--- monkey/infection_monkey/monkey.py | 17 ++++++++++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index f70d90b46..d05f9f5cf 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -3,6 +3,7 @@ import threading import time from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple +from infection_monkey.credential_store import ICredentialsStore from infection_monkey.i_control_channel import IControlChannel, IslandCommunicationError from infection_monkey.i_master import IMaster from infection_monkey.i_puppet import IPuppet @@ -36,6 +37,7 @@ class AutomatedMaster(IMaster): victim_host_factory: VictimHostFactory, control_channel: IControlChannel, local_network_interfaces: List[NetworkInterface], + credentials_store: ICredentialsStore, ): self._current_depth = current_depth self._puppet = puppet @@ -43,9 +45,8 @@ class AutomatedMaster(IMaster): self._control_channel = control_channel ip_scanner = IPScanner(self._puppet, NUM_SCAN_THREADS) - exploiter = Exploiter( - self._puppet, NUM_EXPLOIT_THREADS, self._control_channel.get_credentials_for_propagation - ) + + exploiter = Exploiter(self._puppet, NUM_EXPLOIT_THREADS, credentials_store.get_credentials) self._propagator = Propagator( self._telemetry_messenger, ip_scanner, diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 9576f76c0..6f12c4b89 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -15,6 +15,7 @@ from infection_monkey.credential_collectors import ( MimikatzCredentialCollector, SSHCredentialCollector, ) +from infection_monkey.credential_store import AggregatingCredentialsStore from infection_monkey.exploit import CachingAgentRepository, ExploiterWrapper from infection_monkey.exploit.hadoop import HadoopExploiter from infection_monkey.exploit.log4shell import Log4ShellExploiter @@ -54,6 +55,9 @@ from infection_monkey.puppet.puppet import Puppet from infection_monkey.system_singleton import SystemSingleton from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from infection_monkey.telemetry.attack.t1107_telem import T1107Telem +from infection_monkey.telemetry.messengers.credentials_intercepting_telemetry_messenger import ( + CredentialsInterceptingTelemetryMessenger, +) from infection_monkey.telemetry.messengers.exploit_intercepting_telemetry_messenger import ( ExploitInterceptingTelemetryMessenger, ) @@ -183,14 +187,25 @@ class InfectionMonkey: telemetry_messenger = ExploitInterceptingTelemetryMessenger( self.telemetry_messenger, self._monkey_inbound_tunnel ) + control_channel = ControlChannel(self._default_server, GUID) + + credentials_store = AggregatingCredentialsStore(control_channel) + + telemetry_messenger = CredentialsInterceptingTelemetryMessenger( + ExploitInterceptingTelemetryMessenger( + self.telemetry_messenger, self._monkey_inbound_tunnel + ), + credentials_store, + ) self._master = AutomatedMaster( self._current_depth, puppet, telemetry_messenger, victim_host_factory, - ControlChannel(self._default_server, GUID), + control_channel, local_network_interfaces, + credentials_store, ) @staticmethod From ccb0337aef8c32a2278259e81c72ad7eefd92e74 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 28 Mar 2022 21:15:57 +0200 Subject: [PATCH 08/19] Agent: Add return to get credentials method in Credentials Store --- .../credential_store/aggregating_credentials_store.py | 1 + .../infection_monkey/credential_store/i_credentials_store.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/credential_store/aggregating_credentials_store.py b/monkey/infection_monkey/credential_store/aggregating_credentials_store.py index d855b98dd..31c0a156a 100644 --- a/monkey/infection_monkey/credential_store/aggregating_credentials_store.py +++ b/monkey/infection_monkey/credential_store/aggregating_credentials_store.py @@ -41,6 +41,7 @@ class AggregatingCredentialsStore(ICredentialsStore): try: propagation_credentials = self._control_channel.get_credentials_for_propagation() self._aggregate_credentials(propagation_credentials) + return self.stored_credentials except Exception as ex: self.stored_credentials = {} logger.error(f"Error while attempting to retrieve credentials for propagation: {ex}") diff --git a/monkey/infection_monkey/credential_store/i_credentials_store.py b/monkey/infection_monkey/credential_store/i_credentials_store.py index 2ac10192b..17387480d 100644 --- a/monkey/infection_monkey/credential_store/i_credentials_store.py +++ b/monkey/infection_monkey/credential_store/i_credentials_store.py @@ -1,5 +1,5 @@ import abc -from typing import Iterable +from typing import Iterable, Mapping from infection_monkey.i_puppet import Credentials @@ -13,7 +13,7 @@ class ICredentialsStore(metaclass=abc.ABCMeta): """ @abc.abstractmethod - def get_credentials(self) -> None: + def get_credentials(self) -> Mapping: """ Method that retrieves credentials from the store :return: Credentials that can be used for propagation From 1b9bbfe75212b368374fe156e9b23970d82bf1c5 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 28 Mar 2022 21:16:43 +0200 Subject: [PATCH 09/19] Agent: Fix ssh string to include proper user and ip --- monkey/infection_monkey/exploit/sshexec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 0e6a9c038..dab29ae03 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -60,7 +60,7 @@ class SSHExploiter(HostExploiter): for user, ssh_key_pair in ssh_key_pairs_iterator: # Creating file-like private key for paramiko pkey = io.StringIO(ssh_key_pair["private_key"]) - ssh_string = "%s@%s" % (ssh_key_pair["user"], ssh_key_pair["ip"]) + ssh_string = "%s@%s" % (user, self.host.ip_addr) ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) From 06773ba9d9dd09e2ed8debc8dac68ae79dda69b5 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 28 Mar 2022 21:20:16 +0200 Subject: [PATCH 10/19] UT: Fix AutomatedMaster unit test to include Credentials Store --- .../infection_monkey/master/test_automated_master.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/master/test_automated_master.py b/monkey/tests/unit_tests/infection_monkey/master/test_automated_master.py index 4bb7b4294..cf0112d59 100644 --- a/monkey/tests/unit_tests/infection_monkey/master/test_automated_master.py +++ b/monkey/tests/unit_tests/infection_monkey/master/test_automated_master.py @@ -14,7 +14,7 @@ INTERVAL = 0.001 def test_terminate_without_start(): - m = AutomatedMaster(None, None, None, None, MagicMock(), []) + m = AutomatedMaster(None, None, None, None, MagicMock(), [], MagicMock()) # Test that call to terminate does not raise exception m.terminate() @@ -34,7 +34,7 @@ def test_stop_if_cant_get_config_from_island(monkeypatch): monkeypatch.setattr( "infection_monkey.master.automated_master.CHECK_FOR_TERMINATE_INTERVAL_SEC", INTERVAL ) - m = AutomatedMaster(None, None, None, None, cc, []) + m = AutomatedMaster(None, None, None, None, cc, [], MagicMock()) m.start() assert cc.get_config.call_count == CHECK_FOR_CONFIG_COUNT @@ -73,7 +73,7 @@ def test_stop_if_cant_get_stop_signal_from_island(monkeypatch, sleep_and_return_ "infection_monkey.master.automated_master.CHECK_FOR_TERMINATE_INTERVAL_SEC", INTERVAL ) - m = AutomatedMaster(None, None, None, None, cc, []) + m = AutomatedMaster(None, None, None, None, cc, [], MagicMock()) m.start() assert cc.should_agent_stop.call_count == CHECK_FOR_STOP_AGENT_COUNT From b8a72a971992d8b1a3d1d95da6110605e58a3615 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 29 Mar 2022 10:56:24 +0200 Subject: [PATCH 11/19] UT: Add credentials intercepting telemetry messenger tests Add __test__ to False to discard pytest warning about __init__ constructors of TestTelem classes --- ...ntials_intercepting_telemetry_messenger.py | 74 +++++++++++++++++++ ...xploit_intercepting_telemetry_messenger.py | 1 + 2 files changed, 75 insertions(+) create mode 100644 monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_credentials_intercepting_telemetry_messenger.py diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_credentials_intercepting_telemetry_messenger.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_credentials_intercepting_telemetry_messenger.py new file mode 100644 index 000000000..1e2d6b468 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_credentials_intercepting_telemetry_messenger.py @@ -0,0 +1,74 @@ +from unittest.mock import MagicMock + +from infection_monkey.credential_collectors import Password, SSHKeypair, Username +from infection_monkey.credential_store import AggregatingCredentialsStore +from infection_monkey.i_puppet import Credentials +from infection_monkey.telemetry.base_telem import BaseTelem +from infection_monkey.telemetry.credentials_telem import CredentialsTelem +from infection_monkey.telemetry.messengers.credentials_intercepting_telemetry_messenger import ( + CredentialsInterceptingTelemetryMessenger, +) + +TELEM_CREDENTIALS = [ + Credentials( + [Username("user1"), Username("user3")], + [ + Password("abcdefg"), + Password("root"), + SSHKeypair(public_key="some_public_key", private_key="some_private_key"), + ], + ) +] + + +class TestTelem(BaseTelem): + telem_category = None + __test__ = False + + def __init__(self): + pass + + def get_data(self): + return {} + + +class MockCredentialsTelem(CredentialsTelem): + def __init(self, credentials): + super().__init__(credentials) + + def get_data(self): + return {} + + +def test_credentials_generic_telemetry(): + mock_telemetry_messenger = MagicMock() + mock_credentials_store = MagicMock() + + telemetry_messenger = CredentialsInterceptingTelemetryMessenger( + mock_telemetry_messenger, mock_credentials_store + ) + + telemetry_messenger.send_telemetry(TestTelem()) + + assert mock_telemetry_messenger.send_telemetry.called + assert not mock_credentials_store.add_credentials.called + + +def test_successful_intercepting_credentials_telemetry(): + mock_telemetry_messenger = MagicMock() + aggregating_credentials_store = AggregatingCredentialsStore(MagicMock()) + mock_empty_credentials_telem = MockCredentialsTelem([]) + + telemetry_messenger = CredentialsInterceptingTelemetryMessenger( + mock_telemetry_messenger, aggregating_credentials_store + ) + + telemetry_messenger.send_telemetry(mock_empty_credentials_telem) + + assert mock_telemetry_messenger.send_telemetry.called + assert not aggregating_credentials_store.stored_credentials + + mock_credentials_telem = MockCredentialsTelem(TELEM_CREDENTIALS) + telemetry_messenger.send_telemetry(mock_credentials_telem) + + assert aggregating_credentials_store.stored_credentials diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py index f949738f6..b07ea4a1d 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py @@ -11,6 +11,7 @@ from infection_monkey.telemetry.messengers.exploit_intercepting_telemetry_messen class TestTelem(BaseTelem): telem_category = None + __test__ = False def __init__(self): pass From e7e6201d75b6ee9ae9589ae5a687813e50b1f70c Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 29 Mar 2022 11:42:59 +0200 Subject: [PATCH 12/19] Agent: Use credential intercepting messenger in Zerologon --- monkey/infection_monkey/monkey.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 6f12c4b89..04c794ab5 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -89,6 +89,7 @@ class InfectionMonkey: self._default_server = self._opts.server # TODO used in propogation phase self._monkey_inbound_tunnel = None + self._credentials_store = None self.telemetry_messenger = LegacyTelemetryMessengerAdapter() self._current_depth = self._opts.depth self._master = None @@ -189,13 +190,13 @@ class InfectionMonkey: ) control_channel = ControlChannel(self._default_server, GUID) - credentials_store = AggregatingCredentialsStore(control_channel) + self._credentials_store = AggregatingCredentialsStore(control_channel) telemetry_messenger = CredentialsInterceptingTelemetryMessenger( ExploitInterceptingTelemetryMessenger( self.telemetry_messenger, self._monkey_inbound_tunnel ), - credentials_store, + self._credentials_store, ) self._master = AutomatedMaster( @@ -205,7 +206,7 @@ class InfectionMonkey: victim_host_factory, control_channel, local_network_interfaces, - credentials_store, + self._credentials_store, ) @staticmethod @@ -256,9 +257,14 @@ class InfectionMonkey: puppet.load_plugin( "MSSQLExploiter", exploit_wrapper.wrap(MSSQLExploiter), PluginType.EXPLOITER ) + + zerologon_telemetry_messenger = CredentialsInterceptingTelemetryMessenger( + self.telemetry_messenger, self._credentials_store + ) + zerologon_wrapper = ExploiterWrapper(zerologon_telemetry_messenger, agent_repository) puppet.load_plugin( "ZerologonExploiter", - exploit_wrapper.wrap(ZerologonExploiter), + zerologon_wrapper.wrap(ZerologonExploiter), PluginType.EXPLOITER, ) From 0a5fc84b4e59013cd5baae3d3eec240881ff827e Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 29 Mar 2022 13:36:53 +0200 Subject: [PATCH 13/19] Agent: Fix timeout in ZeroLogon Timeout should be on DCERPC transport factory. --- monkey/infection_monkey/exploit/zerologon.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/exploit/zerologon.py b/monkey/infection_monkey/exploit/zerologon.py index df5b7b4c6..c8dba101d 100644 --- a/monkey/infection_monkey/exploit/zerologon.py +++ b/monkey/infection_monkey/exploit/zerologon.py @@ -91,8 +91,9 @@ class ZerologonExploiter(HostExploiter): @staticmethod def connect_to_dc(dc_ip) -> object: binding = epm.hept_map(dc_ip, nrpc.MSRPC_UUID_NRPC, protocol="ncacn_ip_tcp") - rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc() - rpc_con.set_connect_timeout(LONG_REQUEST_TIMEOUT) + rpc_transport = transport.DCERPCTransportFactory(binding) + rpc_transport.set_connect_timeout(LONG_REQUEST_TIMEOUT) + rpc_con = rpc_transport.get_dce_rpc() rpc_con.connect() rpc_con.bind(nrpc.MSRPC_UUID_NRPC) return rpc_con From 638658178b3a90791999c787b20142dab087888a Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 29 Mar 2022 13:39:58 +0200 Subject: [PATCH 14/19] Agent: Create credential attribute even if we don't have credentials --- .../aggregating_credentials_store.py | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/monkey/infection_monkey/credential_store/aggregating_credentials_store.py b/monkey/infection_monkey/credential_store/aggregating_credentials_store.py index 31c0a156a..f95611166 100644 --- a/monkey/infection_monkey/credential_store/aggregating_credentials_store.py +++ b/monkey/infection_monkey/credential_store/aggregating_credentials_store.py @@ -48,22 +48,27 @@ class AggregatingCredentialsStore(ICredentialsStore): def _aggregate_credentials(self, credentials_to_aggr: Mapping): for cred_attr, credentials_values in credentials_to_aggr.items(): - if credentials_values: - self._set_attribute(cred_attr, credentials_values) + self._set_attribute(cred_attr, credentials_values) def _set_attribute(self, attribute_to_be_set, credentials_values): if attribute_to_be_set not in self.stored_credentials: self.stored_credentials[attribute_to_be_set] = [] - if isinstance(credentials_values[0], dict): - self.stored_credentials.setdefault(attribute_to_be_set, []).extend(credentials_values) - self.stored_credentials[attribute_to_be_set] = [ - dict(s_c) - for s_c in set( - frozenset(d_c.items()) for d_c in self.stored_credentials[attribute_to_be_set] + if credentials_values: + if isinstance(credentials_values[0], dict): + self.stored_credentials.setdefault(attribute_to_be_set, []).extend( + credentials_values + ) + self.stored_credentials[attribute_to_be_set] = [ + dict(s_c) + for s_c in set( + frozenset(d_c.items()) + for d_c in self.stored_credentials[attribute_to_be_set] + ) + ] + else: + self.stored_credentials[attribute_to_be_set] = sorted( + list( + set(self.stored_credentials[attribute_to_be_set]).union(credentials_values) + ) ) - ] - else: - self.stored_credentials[attribute_to_be_set] = sorted( - list(set(self.stored_credentials[attribute_to_be_set]).union(credentials_values)) - ) From e844ecf4e457b7ddf61fe001b3529f614e52475a Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 29 Mar 2022 13:41:06 +0200 Subject: [PATCH 15/19] Agent: Create credentials store before building the puppet --- monkey/infection_monkey/monkey.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 04c794ab5..05f1155c5 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -181,6 +181,10 @@ class InfectionMonkey: def _build_master(self): local_network_interfaces = InfectionMonkey._get_local_network_interfaces() + + control_channel = ControlChannel(self._default_server, GUID) + self._credentials_store = AggregatingCredentialsStore(control_channel) + puppet = self._build_puppet() victim_host_factory = self._build_victim_host_factory(local_network_interfaces) @@ -188,9 +192,6 @@ class InfectionMonkey: telemetry_messenger = ExploitInterceptingTelemetryMessenger( self.telemetry_messenger, self._monkey_inbound_tunnel ) - control_channel = ControlChannel(self._default_server, GUID) - - self._credentials_store = AggregatingCredentialsStore(control_channel) telemetry_messenger = CredentialsInterceptingTelemetryMessenger( ExploitInterceptingTelemetryMessenger( From def62940af3df98ddcd506209e6522cdb4b59cc4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 29 Mar 2022 08:11:38 -0400 Subject: [PATCH 16/19] Agent: Add PropagationCredentials type --- monkey/infection_monkey/master/exploiter.py | 5 +++-- monkey/infection_monkey/typing.py | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 monkey/infection_monkey/typing.py diff --git a/monkey/infection_monkey/master/exploiter.py b/monkey/infection_monkey/master/exploiter.py index 3f0087af8..5f8e25b4d 100644 --- a/monkey/infection_monkey/master/exploiter.py +++ b/monkey/infection_monkey/master/exploiter.py @@ -9,6 +9,7 @@ from typing import Callable, Dict, List, Mapping from infection_monkey.i_puppet import ExploiterResultData, IPuppet from infection_monkey.model import VictimHost +from infection_monkey.typing import PropagationCredentials from infection_monkey.utils.threading import interruptible_iter, run_worker_threads QUEUE_TIMEOUT = 2 @@ -24,7 +25,7 @@ class Exploiter: self, puppet: IPuppet, num_workers: int, - get_updated_credentials_for_propagation: Callable[[], Mapping], + get_updated_credentials_for_propagation: Callable[[], PropagationCredentials], ): self._puppet = puppet self._num_workers = num_workers @@ -160,7 +161,7 @@ class Exploiter: exploitation_success=False, propagation_success=False, error_message=msg ) - def _get_credentials_for_propagation(self) -> Mapping: + def _get_credentials_for_propagation(self) -> PropagationCredentials: try: return self._get_updated_credentials_for_propagation() except Exception as ex: diff --git a/monkey/infection_monkey/typing.py b/monkey/infection_monkey/typing.py new file mode 100644 index 000000000..6e1eab9aa --- /dev/null +++ b/monkey/infection_monkey/typing.py @@ -0,0 +1,3 @@ +from typing import Iterable, Mapping + +PropagationCredentials = Mapping[str, Iterable[str]] From b49d9d9b9a290d85f1b550928660778c773622a1 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 29 Mar 2022 16:58:48 +0200 Subject: [PATCH 17/19] Agent, UT: Update credentials store using `setdefault().update` * get_credentials use PropgationCredentials type * private stored credentials in Aggregating Credentials Store * initial values in credentials store constructor * build_puppet accepts ICredentialsStore * private telemetry_messenger in monkey --- .../aggregating_credentials_store.py | 75 +++++++++++-------- .../credential_store/i_credentials_store.py | 16 ++-- monkey/infection_monkey/monkey.py | 51 +++++++------ ... => test_aggregating_credentials_store.py} | 73 ++++++++---------- ...ntials_intercepting_telemetry_messenger.py | 14 +--- 5 files changed, 114 insertions(+), 115 deletions(-) rename monkey/tests/unit_tests/infection_monkey/credential_store/{test_credential_store.py => test_aggregating_credentials_store.py} (56%) diff --git a/monkey/infection_monkey/credential_store/aggregating_credentials_store.py b/monkey/infection_monkey/credential_store/aggregating_credentials_store.py index f95611166..61883a49c 100644 --- a/monkey/infection_monkey/credential_store/aggregating_credentials_store.py +++ b/monkey/infection_monkey/credential_store/aggregating_credentials_store.py @@ -1,9 +1,10 @@ import logging -from typing import Iterable, Mapping +from typing import Any, Iterable, Mapping from common.common_consts.credential_component_type import CredentialComponentType from infection_monkey.i_control_channel import IControlChannel from infection_monkey.i_puppet import Credentials +from infection_monkey.typing import PropagationCredentials from .i_credentials_store import ICredentialsStore @@ -12,63 +13,73 @@ logger = logging.getLogger(__name__) class AggregatingCredentialsStore(ICredentialsStore): def __init__(self, control_channel: IControlChannel): - self.stored_credentials = {} + self._stored_credentials = { + "exploit_user_list": set(), + "exploit_password_list": set(), + "exploit_lm_hash_list": set(), + "exploit_ntlm_hash_list": set(), + "exploit_ssh_keys": [], + } self._control_channel = control_channel - def add_credentials(self, credentials_to_add: Iterable[Credentials]) -> None: + def add_credentials(self, credentials_to_add: Iterable[Credentials]): for credentials in credentials_to_add: - usernames = [ + usernames = { identity.username for identity in credentials.identities if identity.credential_type is CredentialComponentType.USERNAME - ] - self._set_attribute("exploit_user_list", usernames) + } + self._stored_credentials.setdefault("exploit_user_list", set()).update(usernames) for secret in credentials.secrets: if secret.credential_type is CredentialComponentType.PASSWORD: - self._set_attribute("exploit_password_list", [secret.password]) + self._stored_credentials.setdefault("exploit_password_list", set()).update( + [secret.password] + ) elif secret.credential_type is CredentialComponentType.LM_HASH: - self._set_attribute("exploit_lm_hash_list", [secret.lm_hash]) + self._stored_credentials.setdefault("exploit_lm_hash_list", set()).update( + [secret.lm_hash] + ) elif secret.credential_type is CredentialComponentType.NT_HASH: - self._set_attribute("exploit_ntlm_hash_list", [secret.nt_hash]) + self._stored_credentials.setdefault("exploit_ntlm_hash_list", set()).update( + [secret.nt_hash] + ) elif secret.credential_type is CredentialComponentType.SSH_KEYPAIR: self._set_attribute( "exploit_ssh_keys", [{"public_key": secret.public_key, "private_key": secret.private_key}], ) - def get_credentials(self): + def get_credentials(self) -> PropagationCredentials: try: propagation_credentials = self._control_channel.get_credentials_for_propagation() + + # Needs to be reworked when exploiters accepts sequence of Credentials self._aggregate_credentials(propagation_credentials) - return self.stored_credentials + + return self._stored_credentials except Exception as ex: - self.stored_credentials = {} + self._stored_credentials = {} logger.error(f"Error while attempting to retrieve credentials for propagation: {ex}") def _aggregate_credentials(self, credentials_to_aggr: Mapping): for cred_attr, credentials_values in credentials_to_aggr.items(): self._set_attribute(cred_attr, credentials_values) - def _set_attribute(self, attribute_to_be_set, credentials_values): - if attribute_to_be_set not in self.stored_credentials: - self.stored_credentials[attribute_to_be_set] = [] + def _set_attribute(self, attribute_to_be_set: str, credentials_values: Iterable[Any]): + if not credentials_values: + return - if credentials_values: - if isinstance(credentials_values[0], dict): - self.stored_credentials.setdefault(attribute_to_be_set, []).extend( - credentials_values - ) - self.stored_credentials[attribute_to_be_set] = [ - dict(s_c) - for s_c in set( - frozenset(d_c.items()) - for d_c in self.stored_credentials[attribute_to_be_set] - ) - ] - else: - self.stored_credentials[attribute_to_be_set] = sorted( - list( - set(self.stored_credentials[attribute_to_be_set]).union(credentials_values) - ) + if isinstance(credentials_values[0], dict): + self._stored_credentials[attribute_to_be_set] = [] + self._stored_credentials.setdefault(attribute_to_be_set, []).extend(credentials_values) + self._stored_credentials[attribute_to_be_set] = [ + dict(s_c) + for s_c in set( + frozenset(d_c.items()) for d_c in self._stored_credentials[attribute_to_be_set] ) + ] + else: + self._stored_credentials.setdefault(attribute_to_be_set, set()).update( + credentials_values + ) diff --git a/monkey/infection_monkey/credential_store/i_credentials_store.py b/monkey/infection_monkey/credential_store/i_credentials_store.py index 17387480d..72ef12d44 100644 --- a/monkey/infection_monkey/credential_store/i_credentials_store.py +++ b/monkey/infection_monkey/credential_store/i_credentials_store.py @@ -1,20 +1,22 @@ import abc -from typing import Iterable, Mapping +from typing import Iterable from infection_monkey.i_puppet import Credentials +from infection_monkey.typing import PropagationCredentials class ICredentialsStore(metaclass=abc.ABCMeta): @abc.abstractmethod - def add_credentials(self, credentials_to_add: Iterable[Credentials]) -> None: - """a - Method that adds credentials to the CredentialStore - :param Credentials credentials: The credentials that will be added + def add_credentials(self, credentials_to_add: Iterable[Credentials]): + """ + Adds credentials to the CredentialStore + :param Iterable[Credentials] credentials: The credentials that will be added """ @abc.abstractmethod - def get_credentials(self) -> Mapping: + def get_credentials(self) -> PropagationCredentials: """ - Method that retrieves credentials from the store + Retrieves credentials from the store :return: Credentials that can be used for propagation + :type: PropagationCredentials """ diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 05f1155c5..64408874d 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -15,7 +15,7 @@ from infection_monkey.credential_collectors import ( MimikatzCredentialCollector, SSHCredentialCollector, ) -from infection_monkey.credential_store import AggregatingCredentialsStore +from infection_monkey.credential_store import AggregatingCredentialsStore, ICredentialsStore from infection_monkey.exploit import CachingAgentRepository, ExploiterWrapper from infection_monkey.exploit.hadoop import HadoopExploiter from infection_monkey.exploit.log4shell import Log4ShellExploiter @@ -89,8 +89,7 @@ class InfectionMonkey: self._default_server = self._opts.server # TODO used in propogation phase self._monkey_inbound_tunnel = None - self._credentials_store = None - self.telemetry_messenger = LegacyTelemetryMessengerAdapter() + self._telemetry_messenger = LegacyTelemetryMessengerAdapter() self._current_depth = self._opts.depth self._master = None @@ -125,7 +124,7 @@ class InfectionMonkey: if is_windows_os(): T1106Telem(ScanStatus.USED, UsageEnum.SINGLETON_WINAPI).send() - run_aws_environment_check(self.telemetry_messenger) + run_aws_environment_check(self._telemetry_messenger) should_stop = ControlChannel(WormConfiguration.current_server, GUID).should_agent_stop() if should_stop: @@ -183,21 +182,21 @@ class InfectionMonkey: local_network_interfaces = InfectionMonkey._get_local_network_interfaces() control_channel = ControlChannel(self._default_server, GUID) - self._credentials_store = AggregatingCredentialsStore(control_channel) + credentials_store = AggregatingCredentialsStore(control_channel) - puppet = self._build_puppet() + puppet = self._build_puppet(credentials_store) victim_host_factory = self._build_victim_host_factory(local_network_interfaces) telemetry_messenger = ExploitInterceptingTelemetryMessenger( - self.telemetry_messenger, self._monkey_inbound_tunnel + self._telemetry_messenger, self._monkey_inbound_tunnel ) telemetry_messenger = CredentialsInterceptingTelemetryMessenger( ExploitInterceptingTelemetryMessenger( - self.telemetry_messenger, self._monkey_inbound_tunnel + self._telemetry_messenger, self._monkey_inbound_tunnel ), - self._credentials_store, + credentials_store, ) self._master = AutomatedMaster( @@ -207,7 +206,7 @@ class InfectionMonkey: victim_host_factory, control_channel, local_network_interfaces, - self._credentials_store, + credentials_store, ) @staticmethod @@ -218,7 +217,7 @@ class InfectionMonkey: return local_network_interfaces - def _build_puppet(self) -> IPuppet: + def _build_puppet(self, credentials_store: ICredentialsStore) -> IPuppet: puppet = Puppet() puppet.load_plugin( @@ -228,7 +227,7 @@ class InfectionMonkey: ) puppet.load_plugin( "SSHCollector", - SSHCredentialCollector(self.telemetry_messenger), + SSHCredentialCollector(self._telemetry_messenger), PluginType.CREDENTIAL_COLLECTOR, ) @@ -241,7 +240,7 @@ class InfectionMonkey: agent_repository = CachingAgentRepository( f"https://{self._default_server}", ControlClient.proxies ) - exploit_wrapper = ExploiterWrapper(self.telemetry_messenger, agent_repository) + exploit_wrapper = ExploiterWrapper(self._telemetry_messenger, agent_repository) puppet.load_plugin( "HadoopExploiter", exploit_wrapper.wrap(HadoopExploiter), PluginType.EXPLOITER @@ -260,7 +259,7 @@ class InfectionMonkey: ) zerologon_telemetry_messenger = CredentialsInterceptingTelemetryMessenger( - self.telemetry_messenger, self._credentials_store + self._telemetry_messenger, credentials_store ) zerologon_wrapper = ExploiterWrapper(zerologon_telemetry_messenger, agent_repository) puppet.load_plugin( @@ -271,54 +270,54 @@ class InfectionMonkey: puppet.load_plugin( "CommunicateAsBackdoorUser", - CommunicateAsBackdoorUser(self.telemetry_messenger), + CommunicateAsBackdoorUser(self._telemetry_messenger), PluginType.POST_BREACH_ACTION, ) puppet.load_plugin( "ModifyShellStartupFiles", - ModifyShellStartupFiles(self.telemetry_messenger), + ModifyShellStartupFiles(self._telemetry_messenger), PluginType.POST_BREACH_ACTION, ) puppet.load_plugin( - "HiddenFiles", HiddenFiles(self.telemetry_messenger), PluginType.POST_BREACH_ACTION + "HiddenFiles", HiddenFiles(self._telemetry_messenger), PluginType.POST_BREACH_ACTION ) puppet.load_plugin( "TrapCommand", - CommunicateAsBackdoorUser(self.telemetry_messenger), + CommunicateAsBackdoorUser(self._telemetry_messenger), PluginType.POST_BREACH_ACTION, ) puppet.load_plugin( "ChangeSetuidSetgid", - ChangeSetuidSetgid(self.telemetry_messenger), + ChangeSetuidSetgid(self._telemetry_messenger), PluginType.POST_BREACH_ACTION, ) puppet.load_plugin( - "ScheduleJobs", ScheduleJobs(self.telemetry_messenger), PluginType.POST_BREACH_ACTION + "ScheduleJobs", ScheduleJobs(self._telemetry_messenger), PluginType.POST_BREACH_ACTION ) puppet.load_plugin( - "Timestomping", Timestomping(self.telemetry_messenger), PluginType.POST_BREACH_ACTION + "Timestomping", Timestomping(self._telemetry_messenger), PluginType.POST_BREACH_ACTION ) puppet.load_plugin( "AccountDiscovery", - AccountDiscovery(self.telemetry_messenger), + AccountDiscovery(self._telemetry_messenger), PluginType.POST_BREACH_ACTION, ) puppet.load_plugin( "ProcessListCollection", - ProcessListCollection(self.telemetry_messenger), + ProcessListCollection(self._telemetry_messenger), PluginType.POST_BREACH_ACTION, ) puppet.load_plugin( - "TrapCommand", TrapCommand(self.telemetry_messenger), PluginType.POST_BREACH_ACTION + "TrapCommand", TrapCommand(self._telemetry_messenger), PluginType.POST_BREACH_ACTION ) puppet.load_plugin( "SignedScriptProxyExecution", - SignedScriptProxyExecution(self.telemetry_messenger), + SignedScriptProxyExecution(self._telemetry_messenger), PluginType.POST_BREACH_ACTION, ) puppet.load_plugin( "ClearCommandHistory", - ClearCommandHistory(self.telemetry_messenger), + ClearCommandHistory(self._telemetry_messenger), PluginType.POST_BREACH_ACTION, ) diff --git a/monkey/tests/unit_tests/infection_monkey/credential_store/test_credential_store.py b/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_credentials_store.py similarity index 56% rename from monkey/tests/unit_tests/infection_monkey/credential_store/test_credential_store.py rename to monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_credentials_store.py index 1035de4d0..ff25dc02f 100644 --- a/monkey/tests/unit_tests/infection_monkey/credential_store/test_credential_store.py +++ b/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_credentials_store.py @@ -6,7 +6,7 @@ from infection_monkey.credential_collectors import Password, SSHKeypair, Usernam from infection_monkey.credential_store import AggregatingCredentialsStore from infection_monkey.i_puppet import Credentials -DEFAULT_CREDENTIALS = { +CONTROL_CHANNEL_CREDENTIALS = { "exploit_user_list": ["Administrator", "root", "user1"], "exploit_password_list": ["123456", "123456789", "password", "root"], "exploit_lm_hash_list": ["aasdf23asd1fdaasadasdfas"], @@ -28,7 +28,7 @@ PROPAGATION_CREDENTIALS = { "exploit_ssh_keys": [{"public_key": "some_public_key", "private_key": "some_private_key"}], } -TELEM_CREDENTIALS = [ +CREDENTIALS_COLLECTION = [ Credentials( [Username("user1"), Username("user3")], [ @@ -43,58 +43,51 @@ TELEM_CREDENTIALS = [ @pytest.fixture def aggregating_credentials_store() -> AggregatingCredentialsStore: control_channel = MagicMock() - control_channel.get_credentials_for_propagation.return_value = DEFAULT_CREDENTIALS + control_channel.get_credentials_for_propagation.return_value = CONTROL_CHANNEL_CREDENTIALS return AggregatingCredentialsStore(control_channel) def test_get_credentials_from_store(aggregating_credentials_store): - aggregating_credentials_store.get_credentials() + actual_stored_credentials = aggregating_credentials_store.get_credentials() - actual_stored_credentials = aggregating_credentials_store.stored_credentials + print(actual_stored_credentials) - assert ( - actual_stored_credentials["exploit_user_list"] == DEFAULT_CREDENTIALS["exploit_user_list"] + assert actual_stored_credentials["exploit_user_list"] == set( + CONTROL_CHANNEL_CREDENTIALS["exploit_user_list"] ) - assert ( - actual_stored_credentials["exploit_password_list"] - == DEFAULT_CREDENTIALS["exploit_password_list"] + assert actual_stored_credentials["exploit_password_list"] == set( + CONTROL_CHANNEL_CREDENTIALS["exploit_password_list"] ) - assert ( - actual_stored_credentials["exploit_ntlm_hash_list"] - == DEFAULT_CREDENTIALS["exploit_ntlm_hash_list"] + assert actual_stored_credentials["exploit_ntlm_hash_list"] == set( + CONTROL_CHANNEL_CREDENTIALS["exploit_ntlm_hash_list"] ) for ssh_keypair in actual_stored_credentials["exploit_ssh_keys"]: - assert ssh_keypair in DEFAULT_CREDENTIALS["exploit_ssh_keys"] + assert ssh_keypair in CONTROL_CHANNEL_CREDENTIALS["exploit_ssh_keys"] -def test_add_credentials_to_empty_store(aggregating_credentials_store): - aggregating_credentials_store.add_credentials(TELEM_CREDENTIALS) +def test_add_credentials_to_store(aggregating_credentials_store): + aggregating_credentials_store.add_credentials(CREDENTIALS_COLLECTION) - assert aggregating_credentials_store.stored_credentials == PROPAGATION_CREDENTIALS + actual_stored_credentials = aggregating_credentials_store.get_credentials() - -def test_add_credentials_to_full_store(aggregating_credentials_store): - - aggregating_credentials_store.get_credentials() - - aggregating_credentials_store.add_credentials(TELEM_CREDENTIALS) - - actual_stored_credentials = aggregating_credentials_store.stored_credentials - - assert actual_stored_credentials["exploit_user_list"] == [ - "Administrator", - "root", - "user1", - "user3", - ] - assert actual_stored_credentials["exploit_password_list"] == [ - "123456", - "123456789", - "abcdefg", - "password", - "root", - ] + assert actual_stored_credentials["exploit_user_list"] == set( + [ + "Administrator", + "root", + "user1", + "user3", + ] + ) + assert actual_stored_credentials["exploit_password_list"] == set( + [ + "123456", + "123456789", + "abcdefg", + "password", + "root", + ] + ) for ssh_keypair in actual_stored_credentials["exploit_ssh_keys"]: - assert ssh_keypair in DEFAULT_CREDENTIALS["exploit_ssh_keys"] + assert ssh_keypair in CONTROL_CHANNEL_CREDENTIALS["exploit_ssh_keys"] diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_credentials_intercepting_telemetry_messenger.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_credentials_intercepting_telemetry_messenger.py index 1e2d6b468..214f001b6 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_credentials_intercepting_telemetry_messenger.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_credentials_intercepting_telemetry_messenger.py @@ -1,7 +1,6 @@ from unittest.mock import MagicMock from infection_monkey.credential_collectors import Password, SSHKeypair, Username -from infection_monkey.credential_store import AggregatingCredentialsStore from infection_monkey.i_puppet import Credentials from infection_monkey.telemetry.base_telem import BaseTelem from infection_monkey.telemetry.credentials_telem import CredentialsTelem @@ -56,19 +55,14 @@ def test_credentials_generic_telemetry(): def test_successful_intercepting_credentials_telemetry(): mock_telemetry_messenger = MagicMock() - aggregating_credentials_store = AggregatingCredentialsStore(MagicMock()) - mock_empty_credentials_telem = MockCredentialsTelem([]) + mock_credentials_store = MagicMock() + mock_empty_credentials_telem = MockCredentialsTelem(TELEM_CREDENTIALS) telemetry_messenger = CredentialsInterceptingTelemetryMessenger( - mock_telemetry_messenger, aggregating_credentials_store + mock_telemetry_messenger, mock_credentials_store ) telemetry_messenger.send_telemetry(mock_empty_credentials_telem) assert mock_telemetry_messenger.send_telemetry.called - assert not aggregating_credentials_store.stored_credentials - - mock_credentials_telem = MockCredentialsTelem(TELEM_CREDENTIALS) - telemetry_messenger.send_telemetry(mock_credentials_telem) - - assert aggregating_credentials_store.stored_credentials + assert mock_credentials_store.add_credentials.called From 763cf578c74f70bcc24c001e950bdc3db90269de Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 29 Mar 2022 11:52:43 -0400 Subject: [PATCH 18/19] Agent: Move credentials request caching to AggregatingCredentialsStore The ControlChannel shouldn't be concerned with caching. It's mission should be to service requests. The caching is more appropriately placed in the AggregatingCredentialsStore. --- .../credential_store/aggregating_credentials_store.py | 9 ++++++++- monkey/infection_monkey/master/control_channel.py | 7 ++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/credential_store/aggregating_credentials_store.py b/monkey/infection_monkey/credential_store/aggregating_credentials_store.py index 61883a49c..696b87bd5 100644 --- a/monkey/infection_monkey/credential_store/aggregating_credentials_store.py +++ b/monkey/infection_monkey/credential_store/aggregating_credentials_store.py @@ -5,11 +5,14 @@ from common.common_consts.credential_component_type import CredentialComponentTy from infection_monkey.i_control_channel import IControlChannel from infection_monkey.i_puppet import Credentials from infection_monkey.typing import PropagationCredentials +from infection_monkey.utils.decorators import request_cache from .i_credentials_store import ICredentialsStore logger = logging.getLogger(__name__) +CREDENTIALS_POLL_PERIOD_SEC = 30 + class AggregatingCredentialsStore(ICredentialsStore): def __init__(self, control_channel: IControlChannel): @@ -52,7 +55,7 @@ class AggregatingCredentialsStore(ICredentialsStore): def get_credentials(self) -> PropagationCredentials: try: - propagation_credentials = self._control_channel.get_credentials_for_propagation() + propagation_credentials = self._get_credentials_from_control_channel() # Needs to be reworked when exploiters accepts sequence of Credentials self._aggregate_credentials(propagation_credentials) @@ -62,6 +65,10 @@ class AggregatingCredentialsStore(ICredentialsStore): self._stored_credentials = {} logger.error(f"Error while attempting to retrieve credentials for propagation: {ex}") + @request_cache(CREDENTIALS_POLL_PERIOD_SEC) + def _get_credentials_from_control_channel(self) -> PropagationCredentials: + return self._control_channel.get_credentials_for_propagation() + def _aggregate_credentials(self, credentials_to_aggr: Mapping): for cred_attr, credentials_values in credentials_to_aggr.items(): self._set_attribute(cred_attr, credentials_values) diff --git a/monkey/infection_monkey/master/control_channel.py b/monkey/infection_monkey/master/control_channel.py index d6781af7f..eb6dd12d5 100644 --- a/monkey/infection_monkey/master/control_channel.py +++ b/monkey/infection_monkey/master/control_channel.py @@ -7,14 +7,12 @@ from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT from infection_monkey.config import WormConfiguration from infection_monkey.control import ControlClient from infection_monkey.i_control_channel import IControlChannel, IslandCommunicationError -from infection_monkey.utils.decorators import request_cache +from infection_monkey.typing import PropagationCredentials requests.packages.urllib3.disable_warnings() logger = logging.getLogger(__name__) -CREDENTIALS_POLL_PERIOD_SEC = 30 - class ControlChannel(IControlChannel): def __init__(self, server: str, agent_id: str): @@ -69,8 +67,7 @@ class ControlChannel(IControlChannel): ) as e: raise IslandCommunicationError(e) - @request_cache(CREDENTIALS_POLL_PERIOD_SEC) - def get_credentials_for_propagation(self) -> dict: + def get_credentials_for_propagation(self) -> PropagationCredentials: propagation_credentials_url = ( f"https://{self._control_channel_server}/api/propagation-credentials/{self._agent_id}" ) From 6ab7bd2f45a27748646a93a7c79023e1967c662b Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 29 Mar 2022 19:34:19 +0200 Subject: [PATCH 19/19] Agent, UT: Remove leftover that cause overwrite in CredentialsStore * Use `add` instead of `update` - `add` doesn't let to have duplicates * Move TestTelem to conftest in UT telemetry messenger --- .../aggregating_credentials_store.py | 13 +++++----- .../test_aggregating_credentials_store.py | 24 ++++++++++--------- .../telemetry/messengers/conftest.py | 18 ++++++++++++++ ...ntials_intercepting_telemetry_messenger.py | 14 +---------- ...xploit_intercepting_telemetry_messenger.py | 14 +---------- 5 files changed, 39 insertions(+), 44 deletions(-) create mode 100644 monkey/tests/unit_tests/infection_monkey/telemetry/messengers/conftest.py diff --git a/monkey/infection_monkey/credential_store/aggregating_credentials_store.py b/monkey/infection_monkey/credential_store/aggregating_credentials_store.py index 696b87bd5..eef6f72fc 100644 --- a/monkey/infection_monkey/credential_store/aggregating_credentials_store.py +++ b/monkey/infection_monkey/credential_store/aggregating_credentials_store.py @@ -36,16 +36,16 @@ class AggregatingCredentialsStore(ICredentialsStore): for secret in credentials.secrets: if secret.credential_type is CredentialComponentType.PASSWORD: - self._stored_credentials.setdefault("exploit_password_list", set()).update( - [secret.password] + self._stored_credentials.setdefault("exploit_password_list", set()).add( + secret.password ) elif secret.credential_type is CredentialComponentType.LM_HASH: - self._stored_credentials.setdefault("exploit_lm_hash_list", set()).update( - [secret.lm_hash] + self._stored_credentials.setdefault("exploit_lm_hash_list", set()).add( + secret.lm_hash ) elif secret.credential_type is CredentialComponentType.NT_HASH: - self._stored_credentials.setdefault("exploit_ntlm_hash_list", set()).update( - [secret.nt_hash] + self._stored_credentials.setdefault("exploit_ntlm_hash_list", set()).add( + secret.nt_hash ) elif secret.credential_type is CredentialComponentType.SSH_KEYPAIR: self._set_attribute( @@ -78,7 +78,6 @@ class AggregatingCredentialsStore(ICredentialsStore): return if isinstance(credentials_values[0], dict): - self._stored_credentials[attribute_to_be_set] = [] self._stored_credentials.setdefault(attribute_to_be_set, []).extend(credentials_values) self._stored_credentials[attribute_to_be_set] = [ dict(s_c) diff --git a/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_credentials_store.py b/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_credentials_store.py index ff25dc02f..0b6fd8545 100644 --- a/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_credentials_store.py +++ b/monkey/tests/unit_tests/infection_monkey/credential_store/test_aggregating_credentials_store.py @@ -21,19 +21,21 @@ CONTROL_CHANNEL_CREDENTIALS = { ], } - -PROPAGATION_CREDENTIALS = { - "exploit_user_list": ["user1", "user3"], - "exploit_password_list": ["abcdefg", "root"], - "exploit_ssh_keys": [{"public_key": "some_public_key", "private_key": "some_private_key"}], -} - -CREDENTIALS_COLLECTION = [ +TEST_CREDENTIALS = [ Credentials( [Username("user1"), Username("user3")], [ Password("abcdefg"), Password("root"), + SSHKeypair(public_key="some_public_key_1", private_key="some_private_key_1"), + ], + ) +] + +SSH_KEYS_CREDENTIALS = [ + Credentials( + [Username("root")], + [ SSHKeypair(public_key="some_public_key", private_key="some_private_key"), ], ) @@ -67,7 +69,8 @@ def test_get_credentials_from_store(aggregating_credentials_store): def test_add_credentials_to_store(aggregating_credentials_store): - aggregating_credentials_store.add_credentials(CREDENTIALS_COLLECTION) + aggregating_credentials_store.add_credentials(TEST_CREDENTIALS) + aggregating_credentials_store.add_credentials(SSH_KEYS_CREDENTIALS) actual_stored_credentials = aggregating_credentials_store.get_credentials() @@ -89,5 +92,4 @@ def test_add_credentials_to_store(aggregating_credentials_store): ] ) - for ssh_keypair in actual_stored_credentials["exploit_ssh_keys"]: - assert ssh_keypair in CONTROL_CHANNEL_CREDENTIALS["exploit_ssh_keys"] + assert len(actual_stored_credentials["exploit_ssh_keys"]) == 3 diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/conftest.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/conftest.py new file mode 100644 index 000000000..c29555262 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/conftest.py @@ -0,0 +1,18 @@ +import pytest + +from infection_monkey.telemetry.base_telem import BaseTelem + + +@pytest.fixture(scope="package") +def TestTelem(): + class InnerTestTelem(BaseTelem): + telem_category = None + __test__ = False + + def __init__(self): + pass + + def get_data(self): + return {} + + return InnerTestTelem diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_credentials_intercepting_telemetry_messenger.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_credentials_intercepting_telemetry_messenger.py index 214f001b6..7481bff34 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_credentials_intercepting_telemetry_messenger.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_credentials_intercepting_telemetry_messenger.py @@ -2,7 +2,6 @@ from unittest.mock import MagicMock from infection_monkey.credential_collectors import Password, SSHKeypair, Username from infection_monkey.i_puppet import Credentials -from infection_monkey.telemetry.base_telem import BaseTelem from infection_monkey.telemetry.credentials_telem import CredentialsTelem from infection_monkey.telemetry.messengers.credentials_intercepting_telemetry_messenger import ( CredentialsInterceptingTelemetryMessenger, @@ -20,17 +19,6 @@ TELEM_CREDENTIALS = [ ] -class TestTelem(BaseTelem): - telem_category = None - __test__ = False - - def __init__(self): - pass - - def get_data(self): - return {} - - class MockCredentialsTelem(CredentialsTelem): def __init(self, credentials): super().__init__(credentials) @@ -39,7 +27,7 @@ class MockCredentialsTelem(CredentialsTelem): return {} -def test_credentials_generic_telemetry(): +def test_credentials_generic_telemetry(TestTelem): mock_telemetry_messenger = MagicMock() mock_credentials_store = MagicMock() diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py index b07ea4a1d..969489107 100644 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py +++ b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_exploit_intercepting_telemetry_messenger.py @@ -2,24 +2,12 @@ from unittest.mock import MagicMock from infection_monkey.i_puppet.i_puppet import ExploiterResultData from infection_monkey.model.host import VictimHost -from infection_monkey.telemetry.base_telem import BaseTelem from infection_monkey.telemetry.exploit_telem import ExploitTelem from infection_monkey.telemetry.messengers.exploit_intercepting_telemetry_messenger import ( ExploitInterceptingTelemetryMessenger, ) -class TestTelem(BaseTelem): - telem_category = None - __test__ = False - - def __init__(self): - pass - - def get_data(self): - return {} - - class MockExpliotTelem(ExploitTelem): def __init__(self, propagation_success): erd = ExploiterResultData() @@ -30,7 +18,7 @@ class MockExpliotTelem(ExploitTelem): return {} -def test_generic_telemetry(): +def test_generic_telemetry(TestTelem): mock_telemetry_messenger = MagicMock() mock_tunnel = MagicMock()