Merge pull request #2201 from guardicore/2176-publish-credentials-stolen-in-mimikatz

CredentialsStolenEvent in MimikatzCredentialCollector
This commit is contained in:
Mike Salvatore 2022-08-18 06:37:40 -04:00 committed by GitHub
commit bc0c46bfb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 132 additions and 46 deletions

View File

@ -2,6 +2,8 @@ import logging
from typing import Sequence from typing import Sequence
from common.credentials import Credentials, LMHash, NTHash, Password, Username from common.credentials import Credentials, LMHash, NTHash, Password, Username
from common.event_queue import IEventQueue
from common.events import CredentialsStolenEvent
from infection_monkey.i_puppet import ICredentialCollector from infection_monkey.i_puppet import ICredentialCollector
from infection_monkey.model import USERNAME_PREFIX from infection_monkey.model import USERNAME_PREFIX
@ -11,12 +13,34 @@ from .windows_credentials import WindowsCredentials
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
MIMIKATZ_CREDENTIAL_COLLECTOR_TAG = "mimikatz-credentials-collector"
T1003_ATTACK_TECHNIQUE_TAG = "attack-t1003"
T1005_ATTACK_TECHNIQUE_TAG = "attack-t1005"
MIMIKATZ_EVENT_TAGS = frozenset(
(
MIMIKATZ_CREDENTIAL_COLLECTOR_TAG,
T1003_ATTACK_TECHNIQUE_TAG,
T1005_ATTACK_TECHNIQUE_TAG,
)
)
class MimikatzCredentialCollector(ICredentialCollector): class MimikatzCredentialCollector(ICredentialCollector):
def __init__(self, event_queue: IEventQueue):
self._event_queue = event_queue
def collect_credentials(self, options=None) -> Sequence[Credentials]: def collect_credentials(self, options=None) -> Sequence[Credentials]:
logger.info("Attempting to collect windows credentials with pypykatz.") logger.info("Attempting to collect windows credentials with pypykatz.")
windows_credentials = pypykatz_handler.get_windows_creds() windows_credentials = pypykatz_handler.get_windows_creds()
logger.info(f"Pypykatz gathered {len(windows_credentials)} credentials.") logger.info(f"Pypykatz gathered {len(windows_credentials)} credentials.")
return MimikatzCredentialCollector._to_credentials(windows_credentials)
collected_credentials = MimikatzCredentialCollector._to_credentials(windows_credentials)
self._publish_credentials_stolen_event(collected_credentials)
return collected_credentials
@staticmethod @staticmethod
def _to_credentials(windows_credentials: Sequence[WindowsCredentials]) -> Sequence[Credentials]: def _to_credentials(windows_credentials: Sequence[WindowsCredentials]) -> Sequence[Credentials]:
@ -49,3 +73,11 @@ class MimikatzCredentialCollector(ICredentialCollector):
credentials.append(Credentials(identity, None)) credentials.append(Credentials(identity, None))
return credentials return credentials
def _publish_credentials_stolen_event(self, collected_credentials: Sequence[Credentials]):
credentials_stolen_event = CredentialsStolenEvent(
tags=MIMIKATZ_EVENT_TAGS,
stolen_credentials=collected_credentials,
)
self._event_queue.publish(credentials_stolen_event)

View File

@ -20,12 +20,14 @@ T1003_ATTACK_TECHNIQUE_TAG = "attack-t1003"
T1005_ATTACK_TECHNIQUE_TAG = "attack-t1005" T1005_ATTACK_TECHNIQUE_TAG = "attack-t1005"
T1145_ATTACK_TECHNIQUE_TAG = "attack-t1145" T1145_ATTACK_TECHNIQUE_TAG = "attack-t1145"
SSH_COLLECTOR_EVENT_TAG = { SSH_COLLECTOR_EVENT_TAGS = frozenset(
SSH_CREDENTIAL_COLLECTOR_TAG, (
T1003_ATTACK_TECHNIQUE_TAG, SSH_CREDENTIAL_COLLECTOR_TAG,
T1005_ATTACK_TECHNIQUE_TAG, T1003_ATTACK_TECHNIQUE_TAG,
T1145_ATTACK_TECHNIQUE_TAG, T1005_ATTACK_TECHNIQUE_TAG,
} T1145_ATTACK_TECHNIQUE_TAG,
)
)
def get_ssh_info( def get_ssh_info(
@ -165,7 +167,7 @@ def to_credentials(ssh_info: Iterable[Dict]) -> Sequence[Credentials]:
def _publish_credentials_stolen_event(collected_credentials: Credentials, event_queue: IEventQueue): def _publish_credentials_stolen_event(collected_credentials: Credentials, event_queue: IEventQueue):
credentials_stolen_event = CredentialsStolenEvent( credentials_stolen_event = CredentialsStolenEvent(
tags=frozenset(SSH_COLLECTOR_EVENT_TAG), tags=SSH_COLLECTOR_EVENT_TAGS,
stolen_credentials=[collected_credentials], stolen_credentials=[collected_credentials],
) )

View File

@ -1,6 +1,6 @@
from dataclasses import dataclass from dataclasses import dataclass
from ipaddress import IPv4Address from ipaddress import IPv4Address
from typing import FrozenSet, Union from typing import Callable, FrozenSet, Union
from uuid import UUID from uuid import UUID
import pytest import pytest
@ -31,7 +31,13 @@ class TestEvent2(AbstractEvent):
tags: FrozenSet = frozenset() tags: FrozenSet = frozenset()
def new_subscriber() -> EventSubscriber: @pytest.fixture
def event_queue() -> IEventQueue:
return PyPubSubEventQueue(Publisher())
@pytest.fixture
def event_queue_subscriber() -> Callable[[AbstractEvent], None]:
def fn(event: AbstractEvent): def fn(event: AbstractEvent):
fn.call_count += 1 fn.call_count += 1
fn.call_types.add(event.__class__) fn.call_types.add(event.__class__)
@ -44,68 +50,64 @@ def new_subscriber() -> EventSubscriber:
return fn return fn
@pytest.fixture def test_subscribe_all(event_queue: IEventQueue, event_queue_subscriber: EventSubscriber):
def subscriber() -> EventSubscriber: event_queue.subscribe_all_events(event_queue_subscriber)
return new_subscriber()
@pytest.fixture
def event_queue() -> IEventQueue:
return PyPubSubEventQueue(Publisher())
def test_subscribe_all(event_queue: IEventQueue, subscriber: EventSubscriber):
event_queue.subscribe_all_events(subscriber)
event_queue.publish(TestEvent1(tags=frozenset({EVENT_TAG_1, EVENT_TAG_2}))) event_queue.publish(TestEvent1(tags=frozenset({EVENT_TAG_1, EVENT_TAG_2})))
event_queue.publish(TestEvent1(tags=frozenset({EVENT_TAG_2}))) event_queue.publish(TestEvent1(tags=frozenset({EVENT_TAG_2})))
event_queue.publish(TestEvent1(tags=frozenset({"secret_tag"}))) event_queue.publish(TestEvent1(tags=frozenset({"secret_tag"})))
event_queue.publish(TestEvent2()) event_queue.publish(TestEvent2())
assert subscriber.call_count == 4 assert event_queue_subscriber.call_count == 4
assert TestEvent1 in subscriber.call_types assert TestEvent1 in event_queue_subscriber.call_types
assert TestEvent2 in subscriber.call_types assert TestEvent2 in event_queue_subscriber.call_types
@pytest.mark.parametrize("type_to_subscribe", [TestEvent1, TestEvent2]) @pytest.mark.parametrize("type_to_subscribe", [TestEvent1, TestEvent2])
def test_subscribe_types(event_queue: IEventQueue, subscriber: EventSubscriber, type_to_subscribe): def test_subscribe_types(
event_queue.subscribe_type(type_to_subscribe, subscriber) event_queue: IEventQueue, event_queue_subscriber: EventSubscriber, type_to_subscribe
):
event_queue.subscribe_type(type_to_subscribe, event_queue_subscriber)
event_queue.publish(TestEvent1()) event_queue.publish(TestEvent1())
event_queue.publish(TestEvent2()) event_queue.publish(TestEvent2())
assert subscriber.call_count == 1 assert event_queue_subscriber.call_count == 1
assert type_to_subscribe in subscriber.call_types assert type_to_subscribe in event_queue_subscriber.call_types
def test_subscribe_tags_single_type(event_queue: IEventQueue, subscriber: EventSubscriber): def test_subscribe_tags_single_type(
event_queue.subscribe_tag(EVENT_TAG_1, subscriber) event_queue: IEventQueue, event_queue_subscriber: EventSubscriber
):
event_queue.subscribe_tag(EVENT_TAG_1, event_queue_subscriber)
event_queue.publish(TestEvent1(tags=frozenset({EVENT_TAG_1, EVENT_TAG_2}))) event_queue.publish(TestEvent1(tags=frozenset({EVENT_TAG_1, EVENT_TAG_2})))
event_queue.publish(TestEvent2(tags=frozenset({EVENT_TAG_2}))) event_queue.publish(TestEvent2(tags=frozenset({EVENT_TAG_2})))
assert subscriber.call_count == 1 assert event_queue_subscriber.call_count == 1
assert len(subscriber.call_types) == 1 assert len(event_queue_subscriber.call_types) == 1
assert TestEvent1 in subscriber.call_types assert TestEvent1 in event_queue_subscriber.call_types
assert EVENT_TAG_1 in subscriber.call_tags assert EVENT_TAG_1 in event_queue_subscriber.call_tags
def test_subscribe_tags_multiple_types(event_queue: IEventQueue, subscriber: EventSubscriber): def test_subscribe_tags_multiple_types(
event_queue.subscribe_tag(EVENT_TAG_2, subscriber) event_queue: IEventQueue, event_queue_subscriber: EventSubscriber
):
event_queue.subscribe_tag(EVENT_TAG_2, event_queue_subscriber)
event_queue.publish(TestEvent1(tags=frozenset({EVENT_TAG_1, EVENT_TAG_2}))) event_queue.publish(TestEvent1(tags=frozenset({EVENT_TAG_1, EVENT_TAG_2})))
event_queue.publish(TestEvent2(tags=frozenset({EVENT_TAG_2}))) event_queue.publish(TestEvent2(tags=frozenset({EVENT_TAG_2})))
assert subscriber.call_count == 2 assert event_queue_subscriber.call_count == 2
assert len(subscriber.call_types) == 2 assert len(event_queue_subscriber.call_types) == 2
assert TestEvent1 in subscriber.call_types assert TestEvent1 in event_queue_subscriber.call_types
assert TestEvent2 in subscriber.call_types assert TestEvent2 in event_queue_subscriber.call_types
assert {EVENT_TAG_1, EVENT_TAG_2}.issubset(subscriber.call_tags) assert {EVENT_TAG_1, EVENT_TAG_2}.issubset(event_queue_subscriber.call_tags)
def test_type_tag_collision(event_queue: IEventQueue, subscriber: EventSubscriber): def test_type_tag_collision(event_queue: IEventQueue, event_queue_subscriber: EventSubscriber):
event_queue.subscribe_type(TestEvent1, subscriber) event_queue.subscribe_type(TestEvent1, event_queue_subscriber)
event_queue.publish(TestEvent2(tags=frozenset({TestEvent1.__name__}))) event_queue.publish(TestEvent2(tags=frozenset({TestEvent1.__name__})))
assert subscriber.call_count == 0 assert event_queue_subscriber.call_count == 0

View File

@ -1,9 +1,15 @@
from typing import Sequence from typing import Sequence
from unittest.mock import MagicMock
import pytest import pytest
from common.credentials import Credentials, LMHash, NTHash, Password, Username from common.credentials import Credentials, LMHash, NTHash, Password, Username
from common.event_queue import IEventQueue
from common.events import CredentialsStolenEvent
from infection_monkey.credential_collectors import MimikatzCredentialCollector from infection_monkey.credential_collectors import MimikatzCredentialCollector
from infection_monkey.credential_collectors.mimikatz_collector.mimikatz_credential_collector import ( # noqa: E501
MIMIKATZ_EVENT_TAGS,
)
from infection_monkey.credential_collectors.mimikatz_collector.windows_credentials import ( from infection_monkey.credential_collectors.mimikatz_collector.windows_credentials import (
WindowsCredentials, WindowsCredentials,
) )
@ -18,7 +24,8 @@ def patch_pypykatz(win_creds: [WindowsCredentials], monkeypatch):
def collect_credentials() -> Sequence[Credentials]: def collect_credentials() -> Sequence[Credentials]:
return MimikatzCredentialCollector().collect_credentials() mock_event_queue = MagicMock(spec=IEventQueue)
return MimikatzCredentialCollector(mock_event_queue).collect_credentials()
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -110,3 +117,46 @@ def test_pypykatz_result_parsing_no_secrets(monkeypatch):
collected_credentials = collect_credentials() collected_credentials = collect_credentials()
assert len(collected_credentials) == 1 assert len(collected_credentials) == 1
assert collected_credentials == expected_credentials assert collected_credentials == expected_credentials
def test_mimikatz_credentials_stolen_event_published(monkeypatch):
mock_event_queue = MagicMock(spec=IEventQueue)
patch_pypykatz([], monkeypatch)
mimikatz_credential_collector = MimikatzCredentialCollector(mock_event_queue)
mimikatz_credential_collector.collect_credentials()
mock_event_queue.publish.assert_called_once()
mock_event_queue_call_args = mock_event_queue.publish.call_args[0][0]
assert isinstance(mock_event_queue_call_args, CredentialsStolenEvent)
def test_mimikatz_credentials_stolen_event_tags(monkeypatch):
mock_event_queue = MagicMock(spec=IEventQueue)
patch_pypykatz([], monkeypatch)
mimikatz_credential_collector = MimikatzCredentialCollector(mock_event_queue)
mimikatz_credential_collector.collect_credentials()
mock_event_queue_call_args = mock_event_queue.publish.call_args[0][0]
assert mock_event_queue_call_args.tags == MIMIKATZ_EVENT_TAGS
def test_mimikatz_credentials_stolen_event_stolen_credentials(monkeypatch):
mock_event_queue = MagicMock(spec=IEventQueue)
win_creds = [
WindowsCredentials(
username="user2", password="secret2", lm_hash="0182BD0BD4444BF8FC83B5D9042EED2E"
),
]
patch_pypykatz(win_creds, monkeypatch)
mimikatz_credential_collector = MimikatzCredentialCollector(mock_event_queue)
collected_credentials = mimikatz_credential_collector.collect_credentials()
mock_event_queue_call_args = mock_event_queue.publish.call_args[0][0]
assert mock_event_queue_call_args.stolen_credentials == collected_credentials