From c21cf681a4bea4b33a1e2907389e645c8437b9f4 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Mon, 14 Feb 2022 11:41:47 +0200 Subject: [PATCH 01/25] Agent: define credential collector, credentials interfaces --- .../infection_monkey/credential_collectors/__init__.py | 0 .../credential_components/__init__.py | 0 .../credential_components/i_credential_component.py | 10 ++++++++++ .../credential_components/ntlm_hash.py | 8 ++++++++ .../credential_components/password.py | 8 ++++++++ .../credential_components/ssh_keypair.py | 8 ++++++++ .../credential_components/username.py | 8 ++++++++ .../credential_collectors/credential_types.py | 8 ++++++++ .../credential_collectors/credentials.py | 10 ++++++++++ .../credential_collectors/i_credential_collector.py | 9 +++++++++ 10 files changed, 69 insertions(+) create mode 100644 monkey/infection_monkey/credential_collectors/__init__.py create mode 100644 monkey/infection_monkey/credential_collectors/credential_components/__init__.py create mode 100644 monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py create mode 100644 monkey/infection_monkey/credential_collectors/credential_components/ntlm_hash.py create mode 100644 monkey/infection_monkey/credential_collectors/credential_components/password.py create mode 100644 monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py create mode 100644 monkey/infection_monkey/credential_collectors/credential_components/username.py create mode 100644 monkey/infection_monkey/credential_collectors/credential_types.py create mode 100644 monkey/infection_monkey/credential_collectors/credentials.py create mode 100644 monkey/infection_monkey/credential_collectors/i_credential_collector.py diff --git a/monkey/infection_monkey/credential_collectors/__init__.py b/monkey/infection_monkey/credential_collectors/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/infection_monkey/credential_collectors/credential_components/__init__.py b/monkey/infection_monkey/credential_collectors/credential_components/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py b/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py new file mode 100644 index 000000000..566d3ed05 --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py @@ -0,0 +1,10 @@ +from abc import ABC +from dataclasses import dataclass + +from ..credential_types import CredentialTypes + + +@dataclass +class ICredentialComponent(ABC): + type: CredentialTypes + content: dict diff --git a/monkey/infection_monkey/credential_collectors/credential_components/ntlm_hash.py b/monkey/infection_monkey/credential_collectors/credential_components/ntlm_hash.py new file mode 100644 index 000000000..35ddae49b --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/credential_components/ntlm_hash.py @@ -0,0 +1,8 @@ +from ..credential_types import CredentialTypes + +from .i_credential_component import ICredentialComponent + + +class NtlmHash(ICredentialComponent): + def __init__(self, content: dict): + super().__init__(type=CredentialTypes.NTLM_HASH, content=content) diff --git a/monkey/infection_monkey/credential_collectors/credential_components/password.py b/monkey/infection_monkey/credential_collectors/credential_components/password.py new file mode 100644 index 000000000..fd5b71812 --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/credential_components/password.py @@ -0,0 +1,8 @@ +from ..credential_types import CredentialTypes + +from .i_credential_component import ICredentialComponent + + +class Password(ICredentialComponent): + def __init__(self, content: dict): + super().__init__(type=CredentialTypes.PASSWORD, content=content) diff --git a/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py b/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py new file mode 100644 index 000000000..02390f781 --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py @@ -0,0 +1,8 @@ +from ..credential_types import CredentialTypes + +from .i_credential_component import ICredentialComponent + + +class SSHKeypair(ICredentialComponent): + def __init__(self, content: dict): + super().__init__(type=CredentialTypes.KEYPAIR, content=content) diff --git a/monkey/infection_monkey/credential_collectors/credential_components/username.py b/monkey/infection_monkey/credential_collectors/credential_components/username.py new file mode 100644 index 000000000..348a6df47 --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/credential_components/username.py @@ -0,0 +1,8 @@ +from ..credential_types import CredentialTypes + +from .i_credential_component import ICredentialComponent + + +class Username(ICredentialComponent): + def __init__(self, content: dict): + super().__init__(type=CredentialTypes.USERNAME, content=content) diff --git a/monkey/infection_monkey/credential_collectors/credential_types.py b/monkey/infection_monkey/credential_collectors/credential_types.py new file mode 100644 index 000000000..01b83797e --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/credential_types.py @@ -0,0 +1,8 @@ +from enum import Enum + + +class CredentialTypes(Enum): + KEYPAIR = 1 + USERNAME = 2 + PASSWORD = 3 + NTLM_HASH = 4 diff --git a/monkey/infection_monkey/credential_collectors/credentials.py b/monkey/infection_monkey/credential_collectors/credentials.py new file mode 100644 index 000000000..dc7d9a375 --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/credentials.py @@ -0,0 +1,10 @@ +from dataclasses import dataclass +from typing import List + +from .credential_components.i_credential_component import ICredentialComponent + + +@dataclass +class Credentials: + identities: List[ICredentialComponent] + secrets: List[ICredentialComponent] diff --git a/monkey/infection_monkey/credential_collectors/i_credential_collector.py b/monkey/infection_monkey/credential_collectors/i_credential_collector.py new file mode 100644 index 000000000..79ef9cf8a --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/i_credential_collector.py @@ -0,0 +1,9 @@ +from abc import ABC, abstractmethod + +from .credentials import Credentials + + +class ICredentialCollector(ABC): + @abstractmethod + def collect_credentials(self) -> Credentials: + pass From 6aa2160f315d50bcea77754f0da11854b923ae32 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Mon, 14 Feb 2022 15:25:06 +0200 Subject: [PATCH 02/25] Agent: refactor mimikatz_cred_collector to credential collector --- .../credential_collectors/__init__.py | 6 +++ .../credential_components/nt_hashes.py | 9 ++++ .../credential_components/ntlm_hash.py | 8 ---- .../credential_components/password.py | 5 +-- .../credential_components/ssh_keypair.py | 3 +- .../credential_components/username.py | 5 +-- .../credential_collectors/credential_types.py | 2 +- .../mimikatz_cred_collector.py | 41 ++++++++++++------- 8 files changed, 48 insertions(+), 31 deletions(-) create mode 100644 monkey/infection_monkey/credential_collectors/credential_components/nt_hashes.py delete mode 100644 monkey/infection_monkey/credential_collectors/credential_components/ntlm_hash.py diff --git a/monkey/infection_monkey/credential_collectors/__init__.py b/monkey/infection_monkey/credential_collectors/__init__.py index e69de29bb..4ec480246 100644 --- a/monkey/infection_monkey/credential_collectors/__init__.py +++ b/monkey/infection_monkey/credential_collectors/__init__.py @@ -0,0 +1,6 @@ +from .i_credential_collector import ICredentialCollector +from .credential_components.nt_hashes import NTHashes +from .credential_components.password import Password +from .credential_components.ssh_keypair import SSHKeypair +from .credential_components.username import Username +from .credentials import Credentials diff --git a/monkey/infection_monkey/credential_collectors/credential_components/nt_hashes.py b/monkey/infection_monkey/credential_collectors/credential_components/nt_hashes.py new file mode 100644 index 000000000..13acd83f6 --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/credential_components/nt_hashes.py @@ -0,0 +1,9 @@ +from ..credential_types import CredentialTypes +from .i_credential_component import ICredentialComponent + + +class NTHashes(ICredentialComponent): + def __init__(self, ntlm_hash: str, lm_hash: str): + super().__init__( + type=CredentialTypes.NTLM_HASH, content={"ntlm_hash": ntlm_hash, "lm_hash": lm_hash} + ) diff --git a/monkey/infection_monkey/credential_collectors/credential_components/ntlm_hash.py b/monkey/infection_monkey/credential_collectors/credential_components/ntlm_hash.py deleted file mode 100644 index 35ddae49b..000000000 --- a/monkey/infection_monkey/credential_collectors/credential_components/ntlm_hash.py +++ /dev/null @@ -1,8 +0,0 @@ -from ..credential_types import CredentialTypes - -from .i_credential_component import ICredentialComponent - - -class NtlmHash(ICredentialComponent): - def __init__(self, content: dict): - super().__init__(type=CredentialTypes.NTLM_HASH, content=content) diff --git a/monkey/infection_monkey/credential_collectors/credential_components/password.py b/monkey/infection_monkey/credential_collectors/credential_components/password.py index fd5b71812..3691ee69a 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/password.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/password.py @@ -1,8 +1,7 @@ from ..credential_types import CredentialTypes - from .i_credential_component import ICredentialComponent class Password(ICredentialComponent): - def __init__(self, content: dict): - super().__init__(type=CredentialTypes.PASSWORD, content=content) + def __init__(self, password: str): + super().__init__(type=CredentialTypes.PASSWORD, content={"password": password}) diff --git a/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py b/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py index 02390f781..09d0d2f01 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py @@ -1,8 +1,7 @@ from ..credential_types import CredentialTypes - from .i_credential_component import ICredentialComponent class SSHKeypair(ICredentialComponent): def __init__(self, content: dict): - super().__init__(type=CredentialTypes.KEYPAIR, content=content) + super().__init__(type=CredentialTypes.SSH_KEYPAIR, content=content) diff --git a/monkey/infection_monkey/credential_collectors/credential_components/username.py b/monkey/infection_monkey/credential_collectors/credential_components/username.py index 348a6df47..4ccf0ddea 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/username.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/username.py @@ -1,8 +1,7 @@ from ..credential_types import CredentialTypes - from .i_credential_component import ICredentialComponent class Username(ICredentialComponent): - def __init__(self, content: dict): - super().__init__(type=CredentialTypes.USERNAME, content=content) + def __init__(self, username: str): + super().__init__(type=CredentialTypes.USERNAME, content={"username": username}) diff --git a/monkey/infection_monkey/credential_collectors/credential_types.py b/monkey/infection_monkey/credential_collectors/credential_types.py index 01b83797e..2354664a1 100644 --- a/monkey/infection_monkey/credential_collectors/credential_types.py +++ b/monkey/infection_monkey/credential_collectors/credential_types.py @@ -2,7 +2,7 @@ from enum import Enum class CredentialTypes(Enum): - KEYPAIR = 1 + SSH_KEYPAIR = 1 USERNAME = 2 PASSWORD = 3 NTLM_HASH = 4 diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py b/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py index ab44d85ea..ff31667cf 100644 --- a/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py +++ b/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py @@ -1,25 +1,38 @@ from typing import List +from infection_monkey.credential_collectors import ( + Credentials, + ICredentialCollector, + NTHashes, + Password, + Username, +) from infection_monkey.system_info.windows_cred_collector import pypykatz_handler from infection_monkey.system_info.windows_cred_collector.windows_credentials import ( WindowsCredentials, ) -class MimikatzCredentialCollector(object): - @staticmethod - def get_creds(): +class MimikatzCredentialCollector(ICredentialCollector): + def collect_credentials(self) -> Credentials: creds = pypykatz_handler.get_windows_creds() - return MimikatzCredentialCollector.cred_list_to_cred_dict(creds) + return MimikatzCredentialCollector.to_credentials(creds) @staticmethod - def cred_list_to_cred_dict(creds: List[WindowsCredentials]): - cred_dict = {} - for cred in creds: - # TODO: This should be handled by the island, not the agent. There is already similar - # code in monkey_island/cc/models/report/report_dal.py. - # Lets not use "." and "$" in keys, because it will confuse mongo. - # Ideally we should refactor island not to use a dict and simply parse credential list. - key = cred.username.replace(".", ",").replace("$", "") - cred_dict.update({key: cred.to_dict()}) - return cred_dict + def to_credentials(win_creds: List[WindowsCredentials]) -> Credentials: + creds_obj = Credentials(identities=[], secrets=[]) + for win_cred in win_creds: + + if win_cred.username: + identity = Username(win_cred.username) + creds_obj.identities.append(identity) + + if win_cred.password: + password = Password(win_cred.password) + creds_obj.secrets.append(password) + + if win_cred.lm_hash or win_cred.ntlm_hash: + hashes = NTHashes(ntlm_hash=win_cred.ntlm_hash, lm_hash=win_cred.lm_hash) + creds_obj.secrets.append(hashes) + + return creds_obj From 2ba793e0cf7472a5cf0990c8bf62b25b395d2977 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Mon, 14 Feb 2022 15:55:11 +0200 Subject: [PATCH 03/25] Agent: move mimikatz collector to credential collectors --- .../mimikatz_collector}/__init__.py | 0 .../mimikatz_collector}/mimikatz_cred_collector.py | 8 ++++---- .../mimikatz_collector}/pypykatz_handler.py | 4 +--- .../mimikatz_collector}/windows_credentials.py | 0 .../system_info/windows_info_collector.py | 2 +- .../windows_cred_collector/test_pypykatz_handler.py | 2 +- 6 files changed, 7 insertions(+), 9 deletions(-) rename monkey/infection_monkey/{system_info/windows_cred_collector => credential_collectors/mimikatz_collector}/__init__.py (100%) rename monkey/infection_monkey/{system_info/windows_cred_collector => credential_collectors/mimikatz_collector}/mimikatz_cred_collector.py (84%) rename monkey/infection_monkey/{system_info/windows_cred_collector => credential_collectors/mimikatz_collector}/pypykatz_handler.py (96%) rename monkey/infection_monkey/{system_info/windows_cred_collector => credential_collectors/mimikatz_collector}/windows_credentials.py (100%) diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/__init__.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/__init__.py similarity index 100% rename from monkey/infection_monkey/system_info/windows_cred_collector/__init__.py rename to monkey/infection_monkey/credential_collectors/mimikatz_collector/__init__.py diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py similarity index 84% rename from monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py rename to monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py index ff31667cf..7c58fbe12 100644 --- a/monkey/infection_monkey/system_info/windows_cred_collector/mimikatz_cred_collector.py +++ b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py @@ -1,5 +1,7 @@ from typing import List +import pypykatz_handler + from infection_monkey.credential_collectors import ( Credentials, ICredentialCollector, @@ -7,10 +9,8 @@ from infection_monkey.credential_collectors import ( Password, Username, ) -from infection_monkey.system_info.windows_cred_collector import pypykatz_handler -from infection_monkey.system_info.windows_cred_collector.windows_credentials import ( - WindowsCredentials, -) + +from .windows_credentials import WindowsCredentials class MimikatzCredentialCollector(ICredentialCollector): diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/pypykatz_handler.py similarity index 96% rename from monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py rename to monkey/infection_monkey/credential_collectors/mimikatz_collector/pypykatz_handler.py index 23bcce771..2b7ceec65 100644 --- a/monkey/infection_monkey/system_info/windows_cred_collector/pypykatz_handler.py +++ b/monkey/infection_monkey/credential_collectors/mimikatz_collector/pypykatz_handler.py @@ -3,9 +3,7 @@ from typing import Any, Dict, List, NewType from pypykatz.pypykatz import pypykatz -from infection_monkey.system_info.windows_cred_collector.windows_credentials import ( - WindowsCredentials, -) +from .windows_credentials import WindowsCredentials CREDENTIAL_TYPES = [ "msv_creds", diff --git a/monkey/infection_monkey/system_info/windows_cred_collector/windows_credentials.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/windows_credentials.py similarity index 100% rename from monkey/infection_monkey/system_info/windows_cred_collector/windows_credentials.py rename to monkey/infection_monkey/credential_collectors/mimikatz_collector/windows_credentials.py diff --git a/monkey/infection_monkey/system_info/windows_info_collector.py b/monkey/infection_monkey/system_info/windows_info_collector.py index f3242922e..6285fee0f 100644 --- a/monkey/infection_monkey/system_info/windows_info_collector.py +++ b/monkey/infection_monkey/system_info/windows_info_collector.py @@ -2,7 +2,7 @@ import logging import sys from common.common_consts.system_info_collectors_names import MIMIKATZ_COLLECTOR -from infection_monkey.system_info.windows_cred_collector.mimikatz_cred_collector import ( +from infection_monkey.credential_collectors.windows_cred_collector.mimikatz_cred_collector import ( MimikatzCredentialCollector, ) diff --git a/monkey/tests/unit_tests/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py b/monkey/tests/unit_tests/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py index 4d3259e67..9bacb2070 100644 --- a/monkey/tests/unit_tests/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py +++ b/monkey/tests/unit_tests/infection_monkey/system_info/windows_cred_collector/test_pypykatz_handler.py @@ -1,6 +1,6 @@ from unittest import TestCase -from infection_monkey.system_info.windows_cred_collector.pypykatz_handler import ( +from infection_monkey.credential_collectors.mimikatz_collector.pypykatz_handler import ( _get_creds_from_pypykatz_session, ) From 2f1b57a52622e19d3655f87315bc7309f60a8a31 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Mon, 14 Feb 2022 17:16:41 +0200 Subject: [PATCH 04/25] Agent: fix pypykatz import in mimikatz_cred_collector.py --- .../mimikatz_collector/mimikatz_cred_collector.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py index 7c58fbe12..46fb77d6e 100644 --- a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py +++ b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py @@ -1,7 +1,5 @@ from typing import List -import pypykatz_handler - from infection_monkey.credential_collectors import ( Credentials, ICredentialCollector, @@ -10,6 +8,7 @@ from infection_monkey.credential_collectors import ( Username, ) +from . import pypykatz_handler from .windows_credentials import WindowsCredentials From a6c2762823c5118a27f016f3ca3263fa914278c7 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 15 Feb 2022 10:36:33 +0200 Subject: [PATCH 05/25] Agent: change mimikatz collector to return a list of credentials --- .../mimikatz_collector/mimikatz_cred_collector.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py index 46fb77d6e..534580145 100644 --- a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py +++ b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py @@ -13,15 +13,15 @@ from .windows_credentials import WindowsCredentials class MimikatzCredentialCollector(ICredentialCollector): - def collect_credentials(self) -> Credentials: + def collect_credentials(self) -> List[Credentials]: creds = pypykatz_handler.get_windows_creds() return MimikatzCredentialCollector.to_credentials(creds) @staticmethod - def to_credentials(win_creds: List[WindowsCredentials]) -> Credentials: - creds_obj = Credentials(identities=[], secrets=[]) + def to_credentials(win_creds: List[WindowsCredentials]) -> [Credentials]: + all_creds = [] for win_cred in win_creds: - + creds_obj = Credentials(identities=[], secrets=[]) if win_cred.username: identity = Username(win_cred.username) creds_obj.identities.append(identity) @@ -34,4 +34,7 @@ class MimikatzCredentialCollector(ICredentialCollector): hashes = NTHashes(ntlm_hash=win_cred.ntlm_hash, lm_hash=win_cred.lm_hash) creds_obj.secrets.append(hashes) - return creds_obj + if creds_obj.identities != [] or creds_obj.secrets != []: + all_creds.append(creds_obj) + + return all_creds From f5740b2a6ea47b7137594e12feb936dbe5640e57 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 15 Feb 2022 10:37:13 +0200 Subject: [PATCH 06/25] Agent: add mimikatz collector unit tests --- .../test_mimikatz_collector.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py diff --git a/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py b/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py new file mode 100644 index 000000000..5882315c4 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py @@ -0,0 +1,56 @@ +from infection_monkey.credential_collectors import Credentials, NTHashes, Password, Username +from infection_monkey.credential_collectors.mimikatz_collector.mimikatz_cred_collector import ( + MimikatzCredentialCollector, +) +from infection_monkey.credential_collectors.mimikatz_collector.windows_credentials import ( + WindowsCredentials, +) + + +def patch_pypykatz(win_creds: [WindowsCredentials], monkeypatch): + monkeypatch.setattr( + "infection_monkey.credential_collectors" + ".mimikatz_collector.pypykatz_handler.get_windows_creds", + lambda: win_creds, + ) + + +def test_empty_results(monkeypatch): + win_creds = [WindowsCredentials(username="", password="", ntlm_hash="", lm_hash="")] + patch_pypykatz(win_creds, monkeypatch) + expected = [] + collected = MimikatzCredentialCollector().collect_credentials() + assert expected == collected + + patch_pypykatz([], monkeypatch) + collected = MimikatzCredentialCollector().collect_credentials() + assert [] == collected + + +def test_pypykatz_result_parsing(monkeypatch): + win_creds = [ + WindowsCredentials(username="user", password="secret", ntlm_hash="", lm_hash=""), + WindowsCredentials(username="", password="", ntlm_hash="ntlm_hash", lm_hash="lm_hash"), + WindowsCredentials(username="user", password="secret", ntlm_hash="", lm_hash=""), + WindowsCredentials( + username="user2", password="secret2", ntlm_hash="ntlm_hash2", lm_hash="lm_hash2" + ), + ] + patch_pypykatz(win_creds, monkeypatch) + + # Expected credentials + username = Username("user") + username2 = Username("user2") + password = Password("secret") + password2 = Password("secret2") + hash = NTHashes(ntlm_hash="ntlm_hash", lm_hash="lm_hash") + hash2 = NTHashes(ntlm_hash="ntlm_hash2", lm_hash="lm_hash2") + + expected = [ + Credentials(identities=[username], secrets=[password]), + Credentials(identities=[], secrets=[hash]), + Credentials(identities=[username], secrets=[password]), + Credentials(identities=[username2], secrets=[password2, hash2]), + ] + collected = MimikatzCredentialCollector().collect_credentials() + assert expected == collected From 02cdebb88b1f0d5e6b353344f14ddaf08844dd39 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 15 Feb 2022 12:41:19 +0200 Subject: [PATCH 07/25] Agent: fix ICredentialCollector return type-hint --- .../credential_collectors/i_credential_collector.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/credential_collectors/i_credential_collector.py b/monkey/infection_monkey/credential_collectors/i_credential_collector.py index 79ef9cf8a..15798cf06 100644 --- a/monkey/infection_monkey/credential_collectors/i_credential_collector.py +++ b/monkey/infection_monkey/credential_collectors/i_credential_collector.py @@ -1,9 +1,10 @@ from abc import ABC, abstractmethod +from typing import List from .credentials import Credentials class ICredentialCollector(ABC): @abstractmethod - def collect_credentials(self) -> Credentials: + def collect_credentials(self) -> List[Credentials]: pass From 9037dfdf992a3513f8cea36bd3c151dd575ff11f Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 15 Feb 2022 12:42:36 +0200 Subject: [PATCH 08/25] Agent: rename CredentialTypes enum to CredentialType --- .../credential_components/i_credential_component.py | 4 ++-- .../credential_collectors/credential_components/password.py | 4 ++-- .../credential_components/ssh_keypair.py | 4 ++-- .../credential_collectors/credential_components/username.py | 4 ++-- .../{credential_types.py => credential_type.py} | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) rename monkey/infection_monkey/credential_collectors/{credential_types.py => credential_type.py} (76%) diff --git a/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py b/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py index 566d3ed05..43ebb9aca 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py @@ -1,10 +1,10 @@ from abc import ABC from dataclasses import dataclass -from ..credential_types import CredentialTypes +from ..credential_type import CredentialType @dataclass class ICredentialComponent(ABC): - type: CredentialTypes + type: CredentialType content: dict diff --git a/monkey/infection_monkey/credential_collectors/credential_components/password.py b/monkey/infection_monkey/credential_collectors/credential_components/password.py index 3691ee69a..87abe1575 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/password.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/password.py @@ -1,7 +1,7 @@ -from ..credential_types import CredentialTypes +from ..credential_type import CredentialType from .i_credential_component import ICredentialComponent class Password(ICredentialComponent): def __init__(self, password: str): - super().__init__(type=CredentialTypes.PASSWORD, content={"password": password}) + super().__init__(type=CredentialType.PASSWORD, content={"password": password}) diff --git a/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py b/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py index 09d0d2f01..040256ee0 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py @@ -1,7 +1,7 @@ -from ..credential_types import CredentialTypes +from ..credential_type import CredentialType from .i_credential_component import ICredentialComponent class SSHKeypair(ICredentialComponent): def __init__(self, content: dict): - super().__init__(type=CredentialTypes.SSH_KEYPAIR, content=content) + super().__init__(type=CredentialType.SSH_KEYPAIR, content=content) diff --git a/monkey/infection_monkey/credential_collectors/credential_components/username.py b/monkey/infection_monkey/credential_collectors/credential_components/username.py index 4ccf0ddea..98c7b0b3d 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/username.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/username.py @@ -1,7 +1,7 @@ -from ..credential_types import CredentialTypes +from ..credential_type import CredentialType from .i_credential_component import ICredentialComponent class Username(ICredentialComponent): def __init__(self, username: str): - super().__init__(type=CredentialTypes.USERNAME, content={"username": username}) + super().__init__(type=CredentialType.USERNAME, content={"username": username}) diff --git a/monkey/infection_monkey/credential_collectors/credential_types.py b/monkey/infection_monkey/credential_collectors/credential_type.py similarity index 76% rename from monkey/infection_monkey/credential_collectors/credential_types.py rename to monkey/infection_monkey/credential_collectors/credential_type.py index 2354664a1..5e8a9e6ea 100644 --- a/monkey/infection_monkey/credential_collectors/credential_types.py +++ b/monkey/infection_monkey/credential_collectors/credential_type.py @@ -1,7 +1,7 @@ from enum import Enum -class CredentialTypes(Enum): +class CredentialType(Enum): SSH_KEYPAIR = 1 USERNAME = 2 PASSWORD = 3 From b7003bc231b4a5382db3b9b01393adf1700d2aa8 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 15 Feb 2022 12:43:37 +0200 Subject: [PATCH 09/25] Agent: split up nt and lm hashes into separate credential components --- .../credential_collectors/__init__.py | 3 ++- .../credential_components/lm_hash.py | 7 +++++++ .../credential_components/nt_hash.py | 7 +++++++ .../credential_components/nt_hashes.py | 9 --------- .../mimikatz_cred_collector.py | 17 +++++++++++------ .../test_mimikatz_collector.py | 14 ++++++-------- 6 files changed, 33 insertions(+), 24 deletions(-) create mode 100644 monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py create mode 100644 monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py delete mode 100644 monkey/infection_monkey/credential_collectors/credential_components/nt_hashes.py diff --git a/monkey/infection_monkey/credential_collectors/__init__.py b/monkey/infection_monkey/credential_collectors/__init__.py index 4ec480246..7d48c7bb1 100644 --- a/monkey/infection_monkey/credential_collectors/__init__.py +++ b/monkey/infection_monkey/credential_collectors/__init__.py @@ -1,5 +1,6 @@ from .i_credential_collector import ICredentialCollector -from .credential_components.nt_hashes import NTHashes +from .credential_components.nt_hash import NTHash +from .credential_components.lm_hash import LMHash from .credential_components.password import Password from .credential_components.ssh_keypair import SSHKeypair from .credential_components.username import Username diff --git a/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py b/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py new file mode 100644 index 000000000..d9557c9b0 --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py @@ -0,0 +1,7 @@ +from ..credential_type import CredentialType +from .i_credential_component import ICredentialComponent + + +class LMHash(ICredentialComponent): + def __init__(self, lm_hash: str): + super().__init__(type=CredentialType.NTLM_HASH, content={"lm_hash": lm_hash}) diff --git a/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py b/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py new file mode 100644 index 000000000..ae412310e --- /dev/null +++ b/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py @@ -0,0 +1,7 @@ +from ..credential_type import CredentialType +from .i_credential_component import ICredentialComponent + + +class NTHash(ICredentialComponent): + def __init__(self, nt_hash: str): + super().__init__(type=CredentialType.NTLM_HASH, content={"nt_hash": nt_hash}) diff --git a/monkey/infection_monkey/credential_collectors/credential_components/nt_hashes.py b/monkey/infection_monkey/credential_collectors/credential_components/nt_hashes.py deleted file mode 100644 index 13acd83f6..000000000 --- a/monkey/infection_monkey/credential_collectors/credential_components/nt_hashes.py +++ /dev/null @@ -1,9 +0,0 @@ -from ..credential_types import CredentialTypes -from .i_credential_component import ICredentialComponent - - -class NTHashes(ICredentialComponent): - def __init__(self, ntlm_hash: str, lm_hash: str): - super().__init__( - type=CredentialTypes.NTLM_HASH, content={"ntlm_hash": ntlm_hash, "lm_hash": lm_hash} - ) diff --git a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py index 534580145..708bc7a32 100644 --- a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py +++ b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py @@ -3,7 +3,8 @@ from typing import List from infection_monkey.credential_collectors import ( Credentials, ICredentialCollector, - NTHashes, + LMHash, + NTHash, Password, Username, ) @@ -15,10 +16,10 @@ from .windows_credentials import WindowsCredentials class MimikatzCredentialCollector(ICredentialCollector): def collect_credentials(self) -> List[Credentials]: creds = pypykatz_handler.get_windows_creds() - return MimikatzCredentialCollector.to_credentials(creds) + return MimikatzCredentialCollector._to_credentials(creds) @staticmethod - def to_credentials(win_creds: List[WindowsCredentials]) -> [Credentials]: + def _to_credentials(win_creds: List[WindowsCredentials]) -> [Credentials]: all_creds = [] for win_cred in win_creds: creds_obj = Credentials(identities=[], secrets=[]) @@ -30,9 +31,13 @@ class MimikatzCredentialCollector(ICredentialCollector): password = Password(win_cred.password) creds_obj.secrets.append(password) - if win_cred.lm_hash or win_cred.ntlm_hash: - hashes = NTHashes(ntlm_hash=win_cred.ntlm_hash, lm_hash=win_cred.lm_hash) - creds_obj.secrets.append(hashes) + if win_cred.lm_hash: + lm_hash = LMHash(lm_hash=win_cred.lm_hash) + creds_obj.secrets.append(lm_hash) + + if win_cred.ntlm_hash: + lm_hash = NTHash(nt_hash=win_cred.ntlm_hash) + creds_obj.secrets.append(lm_hash) if creds_obj.identities != [] or creds_obj.secrets != []: all_creds.append(creds_obj) diff --git a/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py b/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py index 5882315c4..0bf0c3628 100644 --- a/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py +++ b/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py @@ -1,4 +1,4 @@ -from infection_monkey.credential_collectors import Credentials, NTHashes, Password, Username +from infection_monkey.credential_collectors import Credentials, LMHash, NTHash, Password, Username from infection_monkey.credential_collectors.mimikatz_collector.mimikatz_cred_collector import ( MimikatzCredentialCollector, ) @@ -32,9 +32,7 @@ def test_pypykatz_result_parsing(monkeypatch): WindowsCredentials(username="user", password="secret", ntlm_hash="", lm_hash=""), WindowsCredentials(username="", password="", ntlm_hash="ntlm_hash", lm_hash="lm_hash"), WindowsCredentials(username="user", password="secret", ntlm_hash="", lm_hash=""), - WindowsCredentials( - username="user2", password="secret2", ntlm_hash="ntlm_hash2", lm_hash="lm_hash2" - ), + WindowsCredentials(username="user2", password="secret2", lm_hash="lm_hash"), ] patch_pypykatz(win_creds, monkeypatch) @@ -43,14 +41,14 @@ def test_pypykatz_result_parsing(monkeypatch): username2 = Username("user2") password = Password("secret") password2 = Password("secret2") - hash = NTHashes(ntlm_hash="ntlm_hash", lm_hash="lm_hash") - hash2 = NTHashes(ntlm_hash="ntlm_hash2", lm_hash="lm_hash2") + nt_hash = NTHash(nt_hash="ntlm_hash") + lm_hash = LMHash(lm_hash="lm_hash") expected = [ Credentials(identities=[username], secrets=[password]), - Credentials(identities=[], secrets=[hash]), + Credentials(identities=[], secrets=[lm_hash, nt_hash]), Credentials(identities=[username], secrets=[password]), - Credentials(identities=[username2], secrets=[password2, hash2]), + Credentials(identities=[username2], secrets=[password2, lm_hash]), ] collected = MimikatzCredentialCollector().collect_credentials() assert expected == collected From 0fae933477c29ce505727c0a898b8aa0bc5be037 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 15 Feb 2022 14:46:21 +0200 Subject: [PATCH 10/25] Agent: refactor content dict out of credential component Content dict serves no purpose, because dataclasses can be serialized without explicit conversion to dict --- .../credential_components/i_credential_component.py | 1 - .../credential_collectors/credential_components/lm_hash.py | 3 ++- .../credential_collectors/credential_components/nt_hash.py | 3 ++- .../credential_collectors/credential_components/password.py | 3 ++- .../credential_collectors/credential_components/ssh_keypair.py | 3 ++- .../credential_collectors/credential_components/username.py | 3 ++- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py b/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py index 43ebb9aca..f2d46c091 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py @@ -7,4 +7,3 @@ from ..credential_type import CredentialType @dataclass class ICredentialComponent(ABC): type: CredentialType - content: dict diff --git a/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py b/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py index d9557c9b0..ecfb97d49 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py @@ -4,4 +4,5 @@ from .i_credential_component import ICredentialComponent class LMHash(ICredentialComponent): def __init__(self, lm_hash: str): - super().__init__(type=CredentialType.NTLM_HASH, content={"lm_hash": lm_hash}) + super().__init__(type=CredentialType.NTLM_HASH) + self.lm_hash = lm_hash diff --git a/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py b/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py index ae412310e..5ffc83016 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py @@ -4,4 +4,5 @@ from .i_credential_component import ICredentialComponent class NTHash(ICredentialComponent): def __init__(self, nt_hash: str): - super().__init__(type=CredentialType.NTLM_HASH, content={"nt_hash": nt_hash}) + super().__init__(type=CredentialType.NTLM_HASH) + self.nt_hash = nt_hash diff --git a/monkey/infection_monkey/credential_collectors/credential_components/password.py b/monkey/infection_monkey/credential_collectors/credential_components/password.py index 87abe1575..01b970de0 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/password.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/password.py @@ -4,4 +4,5 @@ from .i_credential_component import ICredentialComponent class Password(ICredentialComponent): def __init__(self, password: str): - super().__init__(type=CredentialType.PASSWORD, content={"password": password}) + super().__init__(type=CredentialType.PASSWORD) + self.password = password diff --git a/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py b/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py index 040256ee0..0ccb14ec3 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py @@ -4,4 +4,5 @@ from .i_credential_component import ICredentialComponent class SSHKeypair(ICredentialComponent): def __init__(self, content: dict): - super().__init__(type=CredentialType.SSH_KEYPAIR, content=content) + super().__init__(type=CredentialType.SSH_KEYPAIR) + self.content = content diff --git a/monkey/infection_monkey/credential_collectors/credential_components/username.py b/monkey/infection_monkey/credential_collectors/credential_components/username.py index 98c7b0b3d..154fa3817 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/username.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/username.py @@ -4,4 +4,5 @@ from .i_credential_component import ICredentialComponent class Username(ICredentialComponent): def __init__(self, username: str): - super().__init__(type=CredentialType.USERNAME, content={"username": username}) + super().__init__(type=CredentialType.USERNAME) + self.username = username From 01612c402aa462bf8f1106d5c68911b155ce2438 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 15 Feb 2022 14:58:03 +0200 Subject: [PATCH 11/25] Agent: add options to ICredentialCollector interface --- .../credential_collectors/i_credential_collector.py | 4 ++-- .../mimikatz_collector/mimikatz_cred_collector.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/credential_collectors/i_credential_collector.py b/monkey/infection_monkey/credential_collectors/i_credential_collector.py index 15798cf06..846cd26ec 100644 --- a/monkey/infection_monkey/credential_collectors/i_credential_collector.py +++ b/monkey/infection_monkey/credential_collectors/i_credential_collector.py @@ -1,10 +1,10 @@ from abc import ABC, abstractmethod -from typing import List +from typing import List, Mapping, Union from .credentials import Credentials class ICredentialCollector(ABC): @abstractmethod - def collect_credentials(self) -> List[Credentials]: + def collect_credentials(self, options: Union[Mapping, None]) -> List[Credentials]: pass diff --git a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py index 708bc7a32..75a84c0bb 100644 --- a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py +++ b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py @@ -14,7 +14,7 @@ from .windows_credentials import WindowsCredentials class MimikatzCredentialCollector(ICredentialCollector): - def collect_credentials(self) -> List[Credentials]: + def collect_credentials(self, options=None) -> List[Credentials]: creds = pypykatz_handler.get_windows_creds() return MimikatzCredentialCollector._to_credentials(creds) From ae9fed3c2b0ab97f9cfab41a5611df08143ae99a Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 15 Feb 2022 16:16:43 +0200 Subject: [PATCH 12/25] Agent: fixup typehints in ICredentialCollector --- .../credential_collectors/i_credential_collector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/credential_collectors/i_credential_collector.py b/monkey/infection_monkey/credential_collectors/i_credential_collector.py index 846cd26ec..847cd929d 100644 --- a/monkey/infection_monkey/credential_collectors/i_credential_collector.py +++ b/monkey/infection_monkey/credential_collectors/i_credential_collector.py @@ -1,10 +1,10 @@ from abc import ABC, abstractmethod -from typing import List, Mapping, Union +from typing import Iterable, Mapping, Optional from .credentials import Credentials class ICredentialCollector(ABC): @abstractmethod - def collect_credentials(self, options: Union[Mapping, None]) -> List[Credentials]: + def collect_credentials(self, options: Optional[Mapping]) -> Iterable[Credentials]: pass From d392de4a022533d1b526938c0dc1cbd3f73c0ed4 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 15 Feb 2022 18:31:28 +0200 Subject: [PATCH 13/25] Agent: remove ssh_keypair, as it's not used anywhere --- monkey/infection_monkey/credential_collectors/__init__.py | 1 - .../credential_components/ssh_keypair.py | 8 -------- .../credential_collectors/credential_type.py | 1 - 3 files changed, 10 deletions(-) delete mode 100644 monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py diff --git a/monkey/infection_monkey/credential_collectors/__init__.py b/monkey/infection_monkey/credential_collectors/__init__.py index 7d48c7bb1..7265bbc00 100644 --- a/monkey/infection_monkey/credential_collectors/__init__.py +++ b/monkey/infection_monkey/credential_collectors/__init__.py @@ -2,6 +2,5 @@ from .i_credential_collector import ICredentialCollector from .credential_components.nt_hash import NTHash from .credential_components.lm_hash import LMHash from .credential_components.password import Password -from .credential_components.ssh_keypair import SSHKeypair from .credential_components.username import Username from .credentials import Credentials diff --git a/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py b/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py deleted file mode 100644 index 0ccb14ec3..000000000 --- a/monkey/infection_monkey/credential_collectors/credential_components/ssh_keypair.py +++ /dev/null @@ -1,8 +0,0 @@ -from ..credential_type import CredentialType -from .i_credential_component import ICredentialComponent - - -class SSHKeypair(ICredentialComponent): - def __init__(self, content: dict): - super().__init__(type=CredentialType.SSH_KEYPAIR) - self.content = content diff --git a/monkey/infection_monkey/credential_collectors/credential_type.py b/monkey/infection_monkey/credential_collectors/credential_type.py index 5e8a9e6ea..bab015dd8 100644 --- a/monkey/infection_monkey/credential_collectors/credential_type.py +++ b/monkey/infection_monkey/credential_collectors/credential_type.py @@ -2,7 +2,6 @@ from enum import Enum class CredentialType(Enum): - SSH_KEYPAIR = 1 USERNAME = 2 PASSWORD = 3 NTLM_HASH = 4 From 26806392ec04ef9608cb76854fdc520f92660388 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 15 Feb 2022 18:33:04 +0200 Subject: [PATCH 14/25] Agent: split up nt and lm hash credential types --- .../infection_monkey/credential_collectors/credential_type.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/credential_collectors/credential_type.py b/monkey/infection_monkey/credential_collectors/credential_type.py index bab015dd8..b437e0b87 100644 --- a/monkey/infection_monkey/credential_collectors/credential_type.py +++ b/monkey/infection_monkey/credential_collectors/credential_type.py @@ -4,4 +4,5 @@ from enum import Enum class CredentialType(Enum): USERNAME = 2 PASSWORD = 3 - NTLM_HASH = 4 + NT_HASH = 4 + LM_HASH = 5 From 8868fb9b0ce4bae5b0ff5ba060ac0e8b514a358d Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 15 Feb 2022 18:35:32 +0200 Subject: [PATCH 15/25] Agent: change ICredentialComponent interface Interface changed from dataclass (dataclasses are not inheritable) to simple class with type abstract property --- .../credential_components/i_credential_component.py | 11 ++++++----- .../credential_components/lm_hash.py | 3 ++- .../credential_components/nt_hash.py | 3 ++- .../credential_components/password.py | 3 ++- .../credential_components/username.py | 3 ++- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py b/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py index f2d46c091..97b3e35c4 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py @@ -1,9 +1,10 @@ -from abc import ABC -from dataclasses import dataclass +from abc import ABC, abstractmethod -from ..credential_type import CredentialType +from infection_monkey.credential_collectors.credential_type import CredentialType -@dataclass class ICredentialComponent(ABC): - type: CredentialType + @property + @abstractmethod + def type(self) -> CredentialType: + pass diff --git a/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py b/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py index ecfb97d49..603422dca 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py @@ -3,6 +3,7 @@ from .i_credential_component import ICredentialComponent class LMHash(ICredentialComponent): + type = CredentialType.LM_HASH + def __init__(self, lm_hash: str): - super().__init__(type=CredentialType.NTLM_HASH) self.lm_hash = lm_hash diff --git a/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py b/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py index 5ffc83016..9ca0f888d 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py @@ -3,6 +3,7 @@ from .i_credential_component import ICredentialComponent class NTHash(ICredentialComponent): + type = CredentialType.NT_HASH + def __init__(self, nt_hash: str): - super().__init__(type=CredentialType.NTLM_HASH) self.nt_hash = nt_hash diff --git a/monkey/infection_monkey/credential_collectors/credential_components/password.py b/monkey/infection_monkey/credential_collectors/credential_components/password.py index 01b970de0..cf546aac6 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/password.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/password.py @@ -3,6 +3,7 @@ from .i_credential_component import ICredentialComponent class Password(ICredentialComponent): + type = CredentialType.PASSWORD + def __init__(self, password: str): - super().__init__(type=CredentialType.PASSWORD) self.password = password diff --git a/monkey/infection_monkey/credential_collectors/credential_components/username.py b/monkey/infection_monkey/credential_collectors/credential_components/username.py index 154fa3817..578551489 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/username.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/username.py @@ -3,6 +3,7 @@ from .i_credential_component import ICredentialComponent class Username(ICredentialComponent): + type = CredentialType.USERNAME + def __init__(self, username: str): - super().__init__(type=CredentialType.USERNAME) self.username = username From ac376a00145099fe5bff88b6859627d4069f8be6 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 15 Feb 2022 18:39:17 +0200 Subject: [PATCH 16/25] Agent: change the interface of Credentials Refactor from dataclass to object with tuples. This enforces read only identities and secrets so users don't modify them --- .../credential_collectors/credentials.py | 11 ++-- .../mimikatz_cred_collector.py | 17 +++--- .../test_mimikatz_collector.py | 60 ++++++++++++++----- 3 files changed, 59 insertions(+), 29 deletions(-) diff --git a/monkey/infection_monkey/credential_collectors/credentials.py b/monkey/infection_monkey/credential_collectors/credentials.py index dc7d9a375..1e3d05844 100644 --- a/monkey/infection_monkey/credential_collectors/credentials.py +++ b/monkey/infection_monkey/credential_collectors/credentials.py @@ -1,10 +1,11 @@ -from dataclasses import dataclass -from typing import List +from typing import Iterable from .credential_components.i_credential_component import ICredentialComponent -@dataclass class Credentials: - identities: List[ICredentialComponent] - secrets: List[ICredentialComponent] + def __init__( + self, identities: Iterable[ICredentialComponent], secrets: Iterable[ICredentialComponent] + ): + self.identities = tuple(identities) + self.secrets = tuple(secrets) diff --git a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py index 75a84c0bb..9463b3d4f 100644 --- a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py +++ b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py @@ -8,7 +8,6 @@ from infection_monkey.credential_collectors import ( Password, Username, ) - from . import pypykatz_handler from .windows_credentials import WindowsCredentials @@ -22,24 +21,24 @@ class MimikatzCredentialCollector(ICredentialCollector): def _to_credentials(win_creds: List[WindowsCredentials]) -> [Credentials]: all_creds = [] for win_cred in win_creds: - creds_obj = Credentials(identities=[], secrets=[]) + identities = [] + secrets = [] if win_cred.username: identity = Username(win_cred.username) - creds_obj.identities.append(identity) + identities.append(identity) if win_cred.password: password = Password(win_cred.password) - creds_obj.secrets.append(password) + secrets.append(password) if win_cred.lm_hash: lm_hash = LMHash(lm_hash=win_cred.lm_hash) - creds_obj.secrets.append(lm_hash) + secrets.append(lm_hash) if win_cred.ntlm_hash: lm_hash = NTHash(nt_hash=win_cred.ntlm_hash) - creds_obj.secrets.append(lm_hash) - - if creds_obj.identities != [] or creds_obj.secrets != []: - all_creds.append(creds_obj) + secrets.append(lm_hash) + if identities != [] or secrets != []: + all_creds.append(Credentials(identities, secrets)) return all_creds diff --git a/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py b/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py index 0bf0c3628..9e45fd4c6 100644 --- a/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py +++ b/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py @@ -1,4 +1,4 @@ -from infection_monkey.credential_collectors import Credentials, LMHash, NTHash, Password, Username +from infection_monkey.credential_collectors import LMHash, NTHash, Password, Username from infection_monkey.credential_collectors.mimikatz_collector.mimikatz_cred_collector import ( MimikatzCredentialCollector, ) @@ -28,27 +28,57 @@ def test_empty_results(monkeypatch): def test_pypykatz_result_parsing(monkeypatch): + win_creds = [WindowsCredentials(username="user", password="secret", ntlm_hash="", lm_hash="")] + patch_pypykatz(win_creds, monkeypatch) + + # Expected credentials + username = Username("user") + password = Password("secret") + + collected = MimikatzCredentialCollector().collect_credentials() + assert len(list(collected)) == 1 + assert list(collected)[0].identities[0].__dict__ == username.__dict__ + assert list(collected)[0].secrets[0].__dict__ == password.__dict__ + + +def test_pypykatz_result_parsing_duplicates(monkeypatch): win_creds = [ WindowsCredentials(username="user", password="secret", ntlm_hash="", lm_hash=""), - WindowsCredentials(username="", password="", ntlm_hash="ntlm_hash", lm_hash="lm_hash"), WindowsCredentials(username="user", password="secret", ntlm_hash="", lm_hash=""), + ] + patch_pypykatz(win_creds, monkeypatch) + + collected = MimikatzCredentialCollector().collect_credentials() + assert len(list(collected)) == 2 + + +def test_pypykatz_result_parsing_defaults(monkeypatch): + win_creds = [ WindowsCredentials(username="user2", password="secret2", lm_hash="lm_hash"), ] patch_pypykatz(win_creds, monkeypatch) # Expected credentials - username = Username("user") - username2 = Username("user2") - password = Password("secret") - password2 = Password("secret2") - nt_hash = NTHash(nt_hash="ntlm_hash") - lm_hash = LMHash(lm_hash="lm_hash") + username = Username("user2") + password = Password("secret2") + lm_hash = LMHash("lm_hash") - expected = [ - Credentials(identities=[username], secrets=[password]), - Credentials(identities=[], secrets=[lm_hash, nt_hash]), - Credentials(identities=[username], secrets=[password]), - Credentials(identities=[username2], secrets=[password2, lm_hash]), - ] collected = MimikatzCredentialCollector().collect_credentials() - assert expected == collected + assert list(collected)[0].identities[0].__dict__ == username.__dict__ + assert list(collected)[0].secrets[0].__dict__ == password.__dict__ + assert list(collected)[0].secrets[1].__dict__ == lm_hash.__dict__ + + +def test_pypykatz_result_parsing_no_identities(monkeypatch): + win_creds = [ + WindowsCredentials(username="", password="", ntlm_hash="ntlm_hash", lm_hash="lm_hash"), + ] + patch_pypykatz(win_creds, monkeypatch) + + # Expected credentials + nt_hash = NTHash("ntlm_hash") + lm_hash = LMHash("lm_hash") + + collected = MimikatzCredentialCollector().collect_credentials() + assert list(collected)[0].secrets[0].__dict__ == lm_hash.__dict__ + assert list(collected)[0].secrets[1].__dict__ == nt_hash.__dict__ From 811434ff22a4acc81b902960e498f573433371d8 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 15 Feb 2022 18:41:19 +0200 Subject: [PATCH 17/25] Agent: improved type hints in mimikatz_cred_collector.py --- .../mimikatz_collector/mimikatz_cred_collector.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py index 9463b3d4f..c4e27a33c 100644 --- a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py +++ b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py @@ -1,4 +1,4 @@ -from typing import List +from typing import Iterable from infection_monkey.credential_collectors import ( Credentials, @@ -13,12 +13,12 @@ from .windows_credentials import WindowsCredentials class MimikatzCredentialCollector(ICredentialCollector): - def collect_credentials(self, options=None) -> List[Credentials]: + def collect_credentials(self, options=None) -> Iterable[Credentials]: creds = pypykatz_handler.get_windows_creds() return MimikatzCredentialCollector._to_credentials(creds) @staticmethod - def _to_credentials(win_creds: List[WindowsCredentials]) -> [Credentials]: + def _to_credentials(win_creds: Iterable[WindowsCredentials]) -> [Credentials]: all_creds = [] for win_cred in win_creds: identities = [] From ebd5642b52f9b3385148478edc8bb369ae368b4a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 15 Feb 2022 12:23:57 -0500 Subject: [PATCH 18/25] Agent: Refactor credentials and credential_components as dataclasses Using frozen dataclasses for Credentials and ICredentialComponents automatically creates a useful __eq__() function that allows us to easily compare credentials-related objects. --- .../credential_components/lm_hash.py | 9 ++-- .../credential_components/nt_hash.py | 9 ++-- .../credential_components/password.py | 9 ++-- .../credential_components/username.py | 9 ++-- .../credential_collectors/credentials.py | 11 +++-- .../test_mimikatz_collector.py | 43 +++++++++---------- 6 files changed, 46 insertions(+), 44 deletions(-) diff --git a/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py b/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py index 603422dca..4236a5247 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py @@ -1,9 +1,10 @@ +from dataclasses import dataclass, field + from ..credential_type import CredentialType from .i_credential_component import ICredentialComponent +@dataclass(frozen=True) class LMHash(ICredentialComponent): - type = CredentialType.LM_HASH - - def __init__(self, lm_hash: str): - self.lm_hash = lm_hash + type: CredentialType = field(default=CredentialType.LM_HASH, init=False) + lm_hash: str diff --git a/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py b/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py index 9ca0f888d..6f90f37ff 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py @@ -1,9 +1,10 @@ +from dataclasses import dataclass, field + from ..credential_type import CredentialType from .i_credential_component import ICredentialComponent +@dataclass(frozen=True) class NTHash(ICredentialComponent): - type = CredentialType.NT_HASH - - def __init__(self, nt_hash: str): - self.nt_hash = nt_hash + type: CredentialType = field(default=CredentialType.NT_HASH, init=False) + nt_hash: str diff --git a/monkey/infection_monkey/credential_collectors/credential_components/password.py b/monkey/infection_monkey/credential_collectors/credential_components/password.py index cf546aac6..e3ed03fcf 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/password.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/password.py @@ -1,9 +1,10 @@ +from dataclasses import dataclass, field + from ..credential_type import CredentialType from .i_credential_component import ICredentialComponent +@dataclass(frozen=True) class Password(ICredentialComponent): - type = CredentialType.PASSWORD - - def __init__(self, password: str): - self.password = password + type: CredentialType = field(default=CredentialType.PASSWORD, init=False) + password: str diff --git a/monkey/infection_monkey/credential_collectors/credential_components/username.py b/monkey/infection_monkey/credential_collectors/credential_components/username.py index 578551489..d822e0e20 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/username.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/username.py @@ -1,9 +1,10 @@ +from dataclasses import dataclass, field + from ..credential_type import CredentialType from .i_credential_component import ICredentialComponent +@dataclass(frozen=True) class Username(ICredentialComponent): - type = CredentialType.USERNAME - - def __init__(self, username: str): - self.username = username + type: CredentialType = field(default=CredentialType.USERNAME, init=False) + username: str diff --git a/monkey/infection_monkey/credential_collectors/credentials.py b/monkey/infection_monkey/credential_collectors/credentials.py index 1e3d05844..6688e393f 100644 --- a/monkey/infection_monkey/credential_collectors/credentials.py +++ b/monkey/infection_monkey/credential_collectors/credentials.py @@ -1,11 +1,10 @@ -from typing import Iterable +from dataclasses import dataclass +from typing import Tuple from .credential_components.i_credential_component import ICredentialComponent +@dataclass(frozen=True) class Credentials: - def __init__( - self, identities: Iterable[ICredentialComponent], secrets: Iterable[ICredentialComponent] - ): - self.identities = tuple(identities) - self.secrets = tuple(secrets) + identities: Tuple[ICredentialComponent] + secrets: Tuple[ICredentialComponent] diff --git a/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py b/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py index 9e45fd4c6..00589eb4b 100644 --- a/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py +++ b/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py @@ -1,4 +1,4 @@ -from infection_monkey.credential_collectors import LMHash, NTHash, Password, Username +from infection_monkey.credential_collectors import Credentials, LMHash, NTHash, Password, Username from infection_monkey.credential_collectors.mimikatz_collector.mimikatz_cred_collector import ( MimikatzCredentialCollector, ) @@ -18,27 +18,26 @@ def patch_pypykatz(win_creds: [WindowsCredentials], monkeypatch): def test_empty_results(monkeypatch): win_creds = [WindowsCredentials(username="", password="", ntlm_hash="", lm_hash="")] patch_pypykatz(win_creds, monkeypatch) - expected = [] - collected = MimikatzCredentialCollector().collect_credentials() - assert expected == collected + expected_credentials = [] + collected_credentials = MimikatzCredentialCollector().collect_credentials() + assert expected_credentials == collected_credentials patch_pypykatz([], monkeypatch) - collected = MimikatzCredentialCollector().collect_credentials() - assert [] == collected + collected_credentials = MimikatzCredentialCollector().collect_credentials() + assert not collected_credentials def test_pypykatz_result_parsing(monkeypatch): win_creds = [WindowsCredentials(username="user", password="secret", ntlm_hash="", lm_hash="")] patch_pypykatz(win_creds, monkeypatch) - # Expected credentials username = Username("user") password = Password("secret") + expected_credentials = Credentials([username], [password]) - collected = MimikatzCredentialCollector().collect_credentials() - assert len(list(collected)) == 1 - assert list(collected)[0].identities[0].__dict__ == username.__dict__ - assert list(collected)[0].secrets[0].__dict__ == password.__dict__ + collected_credentials = list(MimikatzCredentialCollector().collect_credentials()) + assert len(collected_credentials) == 1 + assert collected_credentials[0] == expected_credentials def test_pypykatz_result_parsing_duplicates(monkeypatch): @@ -48,8 +47,8 @@ def test_pypykatz_result_parsing_duplicates(monkeypatch): ] patch_pypykatz(win_creds, monkeypatch) - collected = MimikatzCredentialCollector().collect_credentials() - assert len(list(collected)) == 2 + collected_credentials = list(MimikatzCredentialCollector().collect_credentials()) + assert len(collected_credentials) == 2 def test_pypykatz_result_parsing_defaults(monkeypatch): @@ -62,11 +61,11 @@ def test_pypykatz_result_parsing_defaults(monkeypatch): username = Username("user2") password = Password("secret2") lm_hash = LMHash("lm_hash") + expected_credentials = Credentials([username], [password, lm_hash]) - collected = MimikatzCredentialCollector().collect_credentials() - assert list(collected)[0].identities[0].__dict__ == username.__dict__ - assert list(collected)[0].secrets[0].__dict__ == password.__dict__ - assert list(collected)[0].secrets[1].__dict__ == lm_hash.__dict__ + collected_credentials = list(MimikatzCredentialCollector().collect_credentials()) + assert len(collected_credentials) == 1 + assert collected_credentials[0] == expected_credentials def test_pypykatz_result_parsing_no_identities(monkeypatch): @@ -75,10 +74,10 @@ def test_pypykatz_result_parsing_no_identities(monkeypatch): ] patch_pypykatz(win_creds, monkeypatch) - # Expected credentials - nt_hash = NTHash("ntlm_hash") lm_hash = LMHash("lm_hash") + nt_hash = NTHash("ntlm_hash") + expected_credentials = Credentials([], [lm_hash, nt_hash]) - collected = MimikatzCredentialCollector().collect_credentials() - assert list(collected)[0].secrets[0].__dict__ == lm_hash.__dict__ - assert list(collected)[0].secrets[1].__dict__ == nt_hash.__dict__ + collected_credentials = list(MimikatzCredentialCollector().collect_credentials()) + assert len(collected_credentials) == 1 + assert collected_credentials[0] == expected_credentials From 86f2c7b08c6b0c5c00a4ec7d796f9ad3be9d2ba2 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 15 Feb 2022 13:28:38 -0500 Subject: [PATCH 19/25] UT: Parametrize test_mimikatz_collector.test_empty_results() --- .../mimikatz_collector/test_mimikatz_collector.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py b/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py index 00589eb4b..d34228821 100644 --- a/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py +++ b/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py @@ -1,3 +1,5 @@ +import pytest + from infection_monkey.credential_collectors import Credentials, LMHash, NTHash, Password, Username from infection_monkey.credential_collectors.mimikatz_collector.mimikatz_cred_collector import ( MimikatzCredentialCollector, @@ -15,15 +17,12 @@ def patch_pypykatz(win_creds: [WindowsCredentials], monkeypatch): ) -def test_empty_results(monkeypatch): - win_creds = [WindowsCredentials(username="", password="", ntlm_hash="", lm_hash="")] +@pytest.mark.parametrize( + "win_creds", [([WindowsCredentials(username="", password="", ntlm_hash="", lm_hash="")]), ([])] +) +def test_empty_results(monkeypatch, win_creds): patch_pypykatz(win_creds, monkeypatch) - expected_credentials = [] - collected_credentials = MimikatzCredentialCollector().collect_credentials() - assert expected_credentials == collected_credentials - - patch_pypykatz([], monkeypatch) - collected_credentials = MimikatzCredentialCollector().collect_credentials() + collected_credentials = list(MimikatzCredentialCollector().collect_credentials()) assert not collected_credentials From 236b545816db8544ce88d89ae18e9f26c89b58ee Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 15 Feb 2022 13:30:13 -0500 Subject: [PATCH 20/25] UT: Extract function collect_credentials() to reduce code duplication --- .../test_mimikatz_collector.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py b/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py index d34228821..49af1d003 100644 --- a/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py +++ b/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py @@ -1,3 +1,5 @@ +from typing import List + import pytest from infection_monkey.credential_collectors import Credentials, LMHash, NTHash, Password, Username @@ -17,12 +19,16 @@ def patch_pypykatz(win_creds: [WindowsCredentials], monkeypatch): ) +def collect_credentials() -> List[Credentials]: + return list(MimikatzCredentialCollector().collect_credentials()) + + @pytest.mark.parametrize( "win_creds", [([WindowsCredentials(username="", password="", ntlm_hash="", lm_hash="")]), ([])] ) def test_empty_results(monkeypatch, win_creds): patch_pypykatz(win_creds, monkeypatch) - collected_credentials = list(MimikatzCredentialCollector().collect_credentials()) + collected_credentials = collect_credentials() assert not collected_credentials @@ -34,7 +40,7 @@ def test_pypykatz_result_parsing(monkeypatch): password = Password("secret") expected_credentials = Credentials([username], [password]) - collected_credentials = list(MimikatzCredentialCollector().collect_credentials()) + collected_credentials = collect_credentials() assert len(collected_credentials) == 1 assert collected_credentials[0] == expected_credentials @@ -46,7 +52,7 @@ def test_pypykatz_result_parsing_duplicates(monkeypatch): ] patch_pypykatz(win_creds, monkeypatch) - collected_credentials = list(MimikatzCredentialCollector().collect_credentials()) + collected_credentials = collect_credentials() assert len(collected_credentials) == 2 @@ -62,7 +68,7 @@ def test_pypykatz_result_parsing_defaults(monkeypatch): lm_hash = LMHash("lm_hash") expected_credentials = Credentials([username], [password, lm_hash]) - collected_credentials = list(MimikatzCredentialCollector().collect_credentials()) + collected_credentials = collect_credentials() assert len(collected_credentials) == 1 assert collected_credentials[0] == expected_credentials @@ -77,6 +83,6 @@ def test_pypykatz_result_parsing_no_identities(monkeypatch): nt_hash = NTHash("ntlm_hash") expected_credentials = Credentials([], [lm_hash, nt_hash]) - collected_credentials = list(MimikatzCredentialCollector().collect_credentials()) + collected_credentials = collect_credentials() assert len(collected_credentials) == 1 assert collected_credentials[0] == expected_credentials From c39fb6746dae5b67ac3541fc8a2beb52c00bf98d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 15 Feb 2022 13:47:01 -0500 Subject: [PATCH 21/25] Agent: Rename ICredentialComponent.type -> credential_type "type" is built-in function in Python. To avoid confusion or a potential name collision, this commit renames the ICredentialComponent.type field to ICredentialComponent.credential_type --- .../credential_components/i_credential_component.py | 2 +- .../credential_collectors/credential_components/lm_hash.py | 2 +- .../credential_collectors/credential_components/nt_hash.py | 2 +- .../credential_collectors/credential_components/password.py | 2 +- .../credential_collectors/credential_components/username.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py b/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py index 97b3e35c4..2a2c38f00 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py @@ -6,5 +6,5 @@ from infection_monkey.credential_collectors.credential_type import CredentialTyp class ICredentialComponent(ABC): @property @abstractmethod - def type(self) -> CredentialType: + def credential_type(self) -> CredentialType: pass diff --git a/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py b/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py index 4236a5247..03869142e 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py @@ -6,5 +6,5 @@ from .i_credential_component import ICredentialComponent @dataclass(frozen=True) class LMHash(ICredentialComponent): - type: CredentialType = field(default=CredentialType.LM_HASH, init=False) + credential_type: CredentialType = field(default=CredentialType.LM_HASH, init=False) lm_hash: str diff --git a/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py b/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py index 6f90f37ff..81b92093b 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py @@ -6,5 +6,5 @@ from .i_credential_component import ICredentialComponent @dataclass(frozen=True) class NTHash(ICredentialComponent): - type: CredentialType = field(default=CredentialType.NT_HASH, init=False) + credential_type: CredentialType = field(default=CredentialType.NT_HASH, init=False) nt_hash: str diff --git a/monkey/infection_monkey/credential_collectors/credential_components/password.py b/monkey/infection_monkey/credential_collectors/credential_components/password.py index e3ed03fcf..26fee38f5 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/password.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/password.py @@ -6,5 +6,5 @@ from .i_credential_component import ICredentialComponent @dataclass(frozen=True) class Password(ICredentialComponent): - type: CredentialType = field(default=CredentialType.PASSWORD, init=False) + credential_type: CredentialType = field(default=CredentialType.PASSWORD, init=False) password: str diff --git a/monkey/infection_monkey/credential_collectors/credential_components/username.py b/monkey/infection_monkey/credential_collectors/credential_components/username.py index d822e0e20..23bfd56ff 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/username.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/username.py @@ -6,5 +6,5 @@ from .i_credential_component import ICredentialComponent @dataclass(frozen=True) class Username(ICredentialComponent): - type: CredentialType = field(default=CredentialType.USERNAME, init=False) + credential_type: CredentialType = field(default=CredentialType.USERNAME, init=False) username: str From 569159b11a5b2f749b072810dce0aa70b40c6380 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 15 Feb 2022 14:07:59 -0500 Subject: [PATCH 22/25] Agent: Move the definition of ICredentialCollector to i_puppet Low-level components plug into high-level components. i_puppet defines all of the interfaces that puppets can use, while the concrete implementations of these things rely on the definitions in i_puppet. --- .../credential_collectors/__init__.py | 2 -- .../credential_components/lm_hash.py | 3 +-- .../credential_components/nt_hash.py | 3 +-- .../credential_components/password.py | 3 +-- .../credential_components/username.py | 3 +-- .../mimikatz_collector/mimikatz_cred_collector.py | 11 +++-------- monkey/infection_monkey/i_puppet/__init__.py | 6 ++++++ .../i_puppet/credential_collection/__init__.py | 4 ++++ .../credential_collection}/credential_type.py | 0 .../credential_collection}/credentials.py | 2 +- .../credential_collection}/i_credential_collector.py | 0 .../credential_collection}/i_credential_component.py | 2 +- .../mimikatz_collector/test_mimikatz_collector.py | 3 ++- 13 files changed, 21 insertions(+), 21 deletions(-) create mode 100644 monkey/infection_monkey/i_puppet/credential_collection/__init__.py rename monkey/infection_monkey/{credential_collectors => i_puppet/credential_collection}/credential_type.py (100%) rename monkey/infection_monkey/{credential_collectors => i_puppet/credential_collection}/credentials.py (70%) rename monkey/infection_monkey/{credential_collectors => i_puppet/credential_collection}/i_credential_collector.py (100%) rename monkey/infection_monkey/{credential_collectors/credential_components => i_puppet/credential_collection}/i_credential_component.py (67%) diff --git a/monkey/infection_monkey/credential_collectors/__init__.py b/monkey/infection_monkey/credential_collectors/__init__.py index 7265bbc00..76ebc4d87 100644 --- a/monkey/infection_monkey/credential_collectors/__init__.py +++ b/monkey/infection_monkey/credential_collectors/__init__.py @@ -1,6 +1,4 @@ -from .i_credential_collector import ICredentialCollector from .credential_components.nt_hash import NTHash from .credential_components.lm_hash import LMHash from .credential_components.password import Password from .credential_components.username import Username -from .credentials import Credentials diff --git a/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py b/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py index 03869142e..7706540a3 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/lm_hash.py @@ -1,7 +1,6 @@ from dataclasses import dataclass, field -from ..credential_type import CredentialType -from .i_credential_component import ICredentialComponent +from infection_monkey.i_puppet import CredentialType, ICredentialComponent @dataclass(frozen=True) diff --git a/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py b/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py index 81b92093b..e6932c4c5 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/nt_hash.py @@ -1,7 +1,6 @@ from dataclasses import dataclass, field -from ..credential_type import CredentialType -from .i_credential_component import ICredentialComponent +from infection_monkey.i_puppet import CredentialType, ICredentialComponent @dataclass(frozen=True) diff --git a/monkey/infection_monkey/credential_collectors/credential_components/password.py b/monkey/infection_monkey/credential_collectors/credential_components/password.py index 26fee38f5..701c9fcde 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/password.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/password.py @@ -1,7 +1,6 @@ from dataclasses import dataclass, field -from ..credential_type import CredentialType -from .i_credential_component import ICredentialComponent +from infection_monkey.i_puppet import CredentialType, ICredentialComponent @dataclass(frozen=True) diff --git a/monkey/infection_monkey/credential_collectors/credential_components/username.py b/monkey/infection_monkey/credential_collectors/credential_components/username.py index 23bfd56ff..208849061 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/username.py +++ b/monkey/infection_monkey/credential_collectors/credential_components/username.py @@ -1,7 +1,6 @@ from dataclasses import dataclass, field -from ..credential_type import CredentialType -from .i_credential_component import ICredentialComponent +from infection_monkey.i_puppet import CredentialType, ICredentialComponent @dataclass(frozen=True) diff --git a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py index c4e27a33c..e1f94c4dd 100644 --- a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py +++ b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py @@ -1,13 +1,8 @@ from typing import Iterable -from infection_monkey.credential_collectors import ( - Credentials, - ICredentialCollector, - LMHash, - NTHash, - Password, - Username, -) +from infection_monkey.credential_collectors import LMHash, NTHash, Password, Username +from infection_monkey.i_puppet.credential_collection import Credentials, ICredentialCollector + from . import pypykatz_handler from .windows_credentials import WindowsCredentials diff --git a/monkey/infection_monkey/i_puppet/__init__.py b/monkey/infection_monkey/i_puppet/__init__.py index c4e6b5b1c..d6422ebc2 100644 --- a/monkey/infection_monkey/i_puppet/__init__.py +++ b/monkey/infection_monkey/i_puppet/__init__.py @@ -10,3 +10,9 @@ from .i_puppet import ( UnknownPluginError, ) from .i_fingerprinter import IFingerprinter +from .credential_collection import ( + Credentials, + CredentialType, + ICredentialCollector, + ICredentialComponent, +) diff --git a/monkey/infection_monkey/i_puppet/credential_collection/__init__.py b/monkey/infection_monkey/i_puppet/credential_collection/__init__.py new file mode 100644 index 000000000..8bfa68b38 --- /dev/null +++ b/monkey/infection_monkey/i_puppet/credential_collection/__init__.py @@ -0,0 +1,4 @@ +from .i_credential_collector import ICredentialCollector +from .credentials import Credentials +from .i_credential_component import ICredentialComponent +from .credential_type import CredentialType diff --git a/monkey/infection_monkey/credential_collectors/credential_type.py b/monkey/infection_monkey/i_puppet/credential_collection/credential_type.py similarity index 100% rename from monkey/infection_monkey/credential_collectors/credential_type.py rename to monkey/infection_monkey/i_puppet/credential_collection/credential_type.py diff --git a/monkey/infection_monkey/credential_collectors/credentials.py b/monkey/infection_monkey/i_puppet/credential_collection/credentials.py similarity index 70% rename from monkey/infection_monkey/credential_collectors/credentials.py rename to monkey/infection_monkey/i_puppet/credential_collection/credentials.py index 6688e393f..d5591f6d7 100644 --- a/monkey/infection_monkey/credential_collectors/credentials.py +++ b/monkey/infection_monkey/i_puppet/credential_collection/credentials.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from typing import Tuple -from .credential_components.i_credential_component import ICredentialComponent +from .i_credential_component import ICredentialComponent @dataclass(frozen=True) diff --git a/monkey/infection_monkey/credential_collectors/i_credential_collector.py b/monkey/infection_monkey/i_puppet/credential_collection/i_credential_collector.py similarity index 100% rename from monkey/infection_monkey/credential_collectors/i_credential_collector.py rename to monkey/infection_monkey/i_puppet/credential_collection/i_credential_collector.py diff --git a/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py b/monkey/infection_monkey/i_puppet/credential_collection/i_credential_component.py similarity index 67% rename from monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py rename to monkey/infection_monkey/i_puppet/credential_collection/i_credential_component.py index 2a2c38f00..d1c005886 100644 --- a/monkey/infection_monkey/credential_collectors/credential_components/i_credential_component.py +++ b/monkey/infection_monkey/i_puppet/credential_collection/i_credential_component.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod -from infection_monkey.credential_collectors.credential_type import CredentialType +from .credential_type import CredentialType class ICredentialComponent(ABC): diff --git a/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py b/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py index 49af1d003..8380229b5 100644 --- a/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py +++ b/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py @@ -2,13 +2,14 @@ from typing import List import pytest -from infection_monkey.credential_collectors import Credentials, LMHash, NTHash, Password, Username +from infection_monkey.credential_collectors import LMHash, NTHash, Password, Username from infection_monkey.credential_collectors.mimikatz_collector.mimikatz_cred_collector import ( MimikatzCredentialCollector, ) from infection_monkey.credential_collectors.mimikatz_collector.windows_credentials import ( WindowsCredentials, ) +from infection_monkey.i_puppet import Credentials def patch_pypykatz(win_creds: [WindowsCredentials], monkeypatch): From 0583cab8e0d4b461d5168f86e64b4ce5939616d0 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 15 Feb 2022 14:17:28 -0500 Subject: [PATCH 23/25] Agent: Rename mimikatz_cred_collector.py to match the class name --- ...imikatz_cred_collector.py => mimikatz_credential_collector.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename monkey/infection_monkey/credential_collectors/mimikatz_collector/{mimikatz_cred_collector.py => mimikatz_credential_collector.py} (100%) diff --git a/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_credential_collector.py similarity index 100% rename from monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_cred_collector.py rename to monkey/infection_monkey/credential_collectors/mimikatz_collector/mimikatz_credential_collector.py From 879abf3df04576ec8ff4d6e036d8f250641eecc8 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 15 Feb 2022 14:21:07 -0500 Subject: [PATCH 24/25] Agent: Export MimikatzCredentialCollector from credential_collectors --- monkey/infection_monkey/credential_collectors/__init__.py | 1 + .../credential_collectors/mimikatz_collector/__init__.py | 1 + .../{mimikatz_collector => }/test_mimikatz_collector.py | 7 +++++-- 3 files changed, 7 insertions(+), 2 deletions(-) rename monkey/tests/unit_tests/infection_monkey/credential_collectors/{mimikatz_collector => }/test_mimikatz_collector.py (94%) diff --git a/monkey/infection_monkey/credential_collectors/__init__.py b/monkey/infection_monkey/credential_collectors/__init__.py index 76ebc4d87..a9d22a4c4 100644 --- a/monkey/infection_monkey/credential_collectors/__init__.py +++ b/monkey/infection_monkey/credential_collectors/__init__.py @@ -2,3 +2,4 @@ from .credential_components.nt_hash import NTHash from .credential_components.lm_hash import LMHash from .credential_components.password import Password from .credential_components.username import Username +from .mimikatz_collector import MimikatzCredentialCollector diff --git a/monkey/infection_monkey/credential_collectors/mimikatz_collector/__init__.py b/monkey/infection_monkey/credential_collectors/mimikatz_collector/__init__.py index e69de29bb..c6a8f1a91 100644 --- a/monkey/infection_monkey/credential_collectors/mimikatz_collector/__init__.py +++ b/monkey/infection_monkey/credential_collectors/mimikatz_collector/__init__.py @@ -0,0 +1 @@ +from .mimikatz_credential_collector import MimikatzCredentialCollector diff --git a/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py b/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_mimikatz_collector.py similarity index 94% rename from monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py rename to monkey/tests/unit_tests/infection_monkey/credential_collectors/test_mimikatz_collector.py index 8380229b5..b33d4e097 100644 --- a/monkey/tests/unit_tests/infection_monkey/credential_collectors/mimikatz_collector/test_mimikatz_collector.py +++ b/monkey/tests/unit_tests/infection_monkey/credential_collectors/test_mimikatz_collector.py @@ -2,9 +2,12 @@ from typing import List import pytest -from infection_monkey.credential_collectors import LMHash, NTHash, Password, Username -from infection_monkey.credential_collectors.mimikatz_collector.mimikatz_cred_collector import ( +from infection_monkey.credential_collectors import ( + LMHash, MimikatzCredentialCollector, + NTHash, + Password, + Username, ) from infection_monkey.credential_collectors.mimikatz_collector.windows_credentials import ( WindowsCredentials, From a9bb2dee70e5b4eee62f61a1bda98f8d50010435 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 15 Feb 2022 14:26:15 -0500 Subject: [PATCH 25/25] Agent: Renumber the CredentialType Enum --- .../i_puppet/credential_collection/credential_type.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/i_puppet/credential_collection/credential_type.py b/monkey/infection_monkey/i_puppet/credential_collection/credential_type.py index b437e0b87..98e6c0097 100644 --- a/monkey/infection_monkey/i_puppet/credential_collection/credential_type.py +++ b/monkey/infection_monkey/i_puppet/credential_collection/credential_type.py @@ -2,7 +2,7 @@ from enum import Enum class CredentialType(Enum): - USERNAME = 2 - PASSWORD = 3 - NT_HASH = 4 - LM_HASH = 5 + USERNAME = 1 + PASSWORD = 2 + NT_HASH = 3 + LM_HASH = 4