Merge pull request #1715 from guardicore/1695-credential-collectors

Agent: define credential collector, credentials interfaces
This commit is contained in:
Mike Salvatore 2022-02-15 14:34:47 -05:00 committed by GitHub
commit 976c46cf86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 224 additions and 30 deletions

View File

@ -0,0 +1,5 @@
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

View File

@ -0,0 +1,9 @@
from dataclasses import dataclass, field
from infection_monkey.i_puppet import CredentialType, ICredentialComponent
@dataclass(frozen=True)
class LMHash(ICredentialComponent):
credential_type: CredentialType = field(default=CredentialType.LM_HASH, init=False)
lm_hash: str

View File

@ -0,0 +1,9 @@
from dataclasses import dataclass, field
from infection_monkey.i_puppet import CredentialType, ICredentialComponent
@dataclass(frozen=True)
class NTHash(ICredentialComponent):
credential_type: CredentialType = field(default=CredentialType.NT_HASH, init=False)
nt_hash: str

View File

@ -0,0 +1,9 @@
from dataclasses import dataclass, field
from infection_monkey.i_puppet import CredentialType, ICredentialComponent
@dataclass(frozen=True)
class Password(ICredentialComponent):
credential_type: CredentialType = field(default=CredentialType.PASSWORD, init=False)
password: str

View File

@ -0,0 +1,9 @@
from dataclasses import dataclass, field
from infection_monkey.i_puppet import CredentialType, ICredentialComponent
@dataclass(frozen=True)
class Username(ICredentialComponent):
credential_type: CredentialType = field(default=CredentialType.USERNAME, init=False)
username: str

View File

@ -0,0 +1 @@
from .mimikatz_credential_collector import MimikatzCredentialCollector

View File

@ -0,0 +1,39 @@
from typing import Iterable
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
class MimikatzCredentialCollector(ICredentialCollector):
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: Iterable[WindowsCredentials]) -> [Credentials]:
all_creds = []
for win_cred in win_creds:
identities = []
secrets = []
if win_cred.username:
identity = Username(win_cred.username)
identities.append(identity)
if win_cred.password:
password = Password(win_cred.password)
secrets.append(password)
if win_cred.lm_hash:
lm_hash = LMHash(lm_hash=win_cred.lm_hash)
secrets.append(lm_hash)
if win_cred.ntlm_hash:
lm_hash = NTHash(nt_hash=win_cred.ntlm_hash)
secrets.append(lm_hash)
if identities != [] or secrets != []:
all_creds.append(Credentials(identities, secrets))
return all_creds

View File

@ -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",

View File

@ -10,3 +10,9 @@ from .i_puppet import (
UnknownPluginError,
)
from .i_fingerprinter import IFingerprinter
from .credential_collection import (
Credentials,
CredentialType,
ICredentialCollector,
ICredentialComponent,
)

View File

@ -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

View File

@ -0,0 +1,8 @@
from enum import Enum
class CredentialType(Enum):
USERNAME = 1
PASSWORD = 2
NT_HASH = 3
LM_HASH = 4

View File

@ -0,0 +1,10 @@
from dataclasses import dataclass
from typing import Tuple
from .i_credential_component import ICredentialComponent
@dataclass(frozen=True)
class Credentials:
identities: Tuple[ICredentialComponent]
secrets: Tuple[ICredentialComponent]

View File

@ -0,0 +1,10 @@
from abc import ABC, abstractmethod
from typing import Iterable, Mapping, Optional
from .credentials import Credentials
class ICredentialCollector(ABC):
@abstractmethod
def collect_credentials(self, options: Optional[Mapping]) -> Iterable[Credentials]:
pass

View File

@ -0,0 +1,10 @@
from abc import ABC, abstractmethod
from .credential_type import CredentialType
class ICredentialComponent(ABC):
@property
@abstractmethod
def credential_type(self) -> CredentialType:
pass

View File

@ -1,25 +0,0 @@
from typing import List
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():
creds = pypykatz_handler.get_windows_creds()
return MimikatzCredentialCollector.cred_list_to_cred_dict(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

View File

@ -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,
)

View File

@ -0,0 +1,92 @@
from typing import List
import pytest
from infection_monkey.credential_collectors import (
LMHash,
MimikatzCredentialCollector,
NTHash,
Password,
Username,
)
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):
monkeypatch.setattr(
"infection_monkey.credential_collectors"
".mimikatz_collector.pypykatz_handler.get_windows_creds",
lambda: win_creds,
)
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 = 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)
username = Username("user")
password = Password("secret")
expected_credentials = Credentials([username], [password])
collected_credentials = collect_credentials()
assert len(collected_credentials) == 1
assert collected_credentials[0] == expected_credentials
def test_pypykatz_result_parsing_duplicates(monkeypatch):
win_creds = [
WindowsCredentials(username="user", password="secret", ntlm_hash="", lm_hash=""),
WindowsCredentials(username="user", password="secret", ntlm_hash="", lm_hash=""),
]
patch_pypykatz(win_creds, monkeypatch)
collected_credentials = collect_credentials()
assert len(collected_credentials) == 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("user2")
password = Password("secret2")
lm_hash = LMHash("lm_hash")
expected_credentials = Credentials([username], [password, lm_hash])
collected_credentials = collect_credentials()
assert len(collected_credentials) == 1
assert collected_credentials[0] == expected_credentials
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)
lm_hash = LMHash("lm_hash")
nt_hash = NTHash("ntlm_hash")
expected_credentials = Credentials([], [lm_hash, nt_hash])
collected_credentials = collect_credentials()
assert len(collected_credentials) == 1
assert collected_credentials[0] == expected_credentials

View File

@ -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,
)