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 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.model import USERNAME_PREFIX
@ -11,12 +13,34 @@ from .windows_credentials import WindowsCredentials
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):
def __init__(self, event_queue: IEventQueue):
self._event_queue = event_queue
def collect_credentials(self, options=None) -> Sequence[Credentials]:
logger.info("Attempting to collect windows credentials with pypykatz.")
windows_credentials = pypykatz_handler.get_windows_creds()
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
def _to_credentials(windows_credentials: Sequence[WindowsCredentials]) -> Sequence[Credentials]:
@ -49,3 +73,11 @@ class MimikatzCredentialCollector(ICredentialCollector):
credentials.append(Credentials(identity, None))
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"
T1145_ATTACK_TECHNIQUE_TAG = "attack-t1145"
SSH_COLLECTOR_EVENT_TAG = {
SSH_COLLECTOR_EVENT_TAGS = frozenset(
(
SSH_CREDENTIAL_COLLECTOR_TAG,
T1003_ATTACK_TECHNIQUE_TAG,
T1005_ATTACK_TECHNIQUE_TAG,
T1145_ATTACK_TECHNIQUE_TAG,
}
)
)
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):
credentials_stolen_event = CredentialsStolenEvent(
tags=frozenset(SSH_COLLECTOR_EVENT_TAG),
tags=SSH_COLLECTOR_EVENT_TAGS,
stolen_credentials=[collected_credentials],
)

View File

@ -1,6 +1,6 @@
from dataclasses import dataclass
from ipaddress import IPv4Address
from typing import FrozenSet, Union
from typing import Callable, FrozenSet, Union
from uuid import UUID
import pytest
@ -31,7 +31,13 @@ class TestEvent2(AbstractEvent):
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):
fn.call_count += 1
fn.call_types.add(event.__class__)
@ -44,68 +50,64 @@ def new_subscriber() -> EventSubscriber:
return fn
@pytest.fixture
def subscriber() -> EventSubscriber:
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)
def test_subscribe_all(event_queue: IEventQueue, event_queue_subscriber: EventSubscriber):
event_queue.subscribe_all_events(event_queue_subscriber)
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({"secret_tag"})))
event_queue.publish(TestEvent2())
assert subscriber.call_count == 4
assert TestEvent1 in subscriber.call_types
assert TestEvent2 in subscriber.call_types
assert event_queue_subscriber.call_count == 4
assert TestEvent1 in event_queue_subscriber.call_types
assert TestEvent2 in event_queue_subscriber.call_types
@pytest.mark.parametrize("type_to_subscribe", [TestEvent1, TestEvent2])
def test_subscribe_types(event_queue: IEventQueue, subscriber: EventSubscriber, type_to_subscribe):
event_queue.subscribe_type(type_to_subscribe, subscriber)
def test_subscribe_types(
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(TestEvent2())
assert subscriber.call_count == 1
assert type_to_subscribe in subscriber.call_types
assert event_queue_subscriber.call_count == 1
assert type_to_subscribe in event_queue_subscriber.call_types
def test_subscribe_tags_single_type(event_queue: IEventQueue, subscriber: EventSubscriber):
event_queue.subscribe_tag(EVENT_TAG_1, subscriber)
def test_subscribe_tags_single_type(
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(TestEvent2(tags=frozenset({EVENT_TAG_2})))
assert subscriber.call_count == 1
assert len(subscriber.call_types) == 1
assert TestEvent1 in subscriber.call_types
assert EVENT_TAG_1 in subscriber.call_tags
assert event_queue_subscriber.call_count == 1
assert len(event_queue_subscriber.call_types) == 1
assert TestEvent1 in event_queue_subscriber.call_types
assert EVENT_TAG_1 in event_queue_subscriber.call_tags
def test_subscribe_tags_multiple_types(event_queue: IEventQueue, subscriber: EventSubscriber):
event_queue.subscribe_tag(EVENT_TAG_2, subscriber)
def test_subscribe_tags_multiple_types(
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(TestEvent2(tags=frozenset({EVENT_TAG_2})))
assert subscriber.call_count == 2
assert len(subscriber.call_types) == 2
assert TestEvent1 in subscriber.call_types
assert TestEvent2 in subscriber.call_types
assert {EVENT_TAG_1, EVENT_TAG_2}.issubset(subscriber.call_tags)
assert event_queue_subscriber.call_count == 2
assert len(event_queue_subscriber.call_types) == 2
assert TestEvent1 in event_queue_subscriber.call_types
assert TestEvent2 in event_queue_subscriber.call_types
assert {EVENT_TAG_1, EVENT_TAG_2}.issubset(event_queue_subscriber.call_tags)
def test_type_tag_collision(event_queue: IEventQueue, subscriber: EventSubscriber):
event_queue.subscribe_type(TestEvent1, subscriber)
def test_type_tag_collision(event_queue: IEventQueue, event_queue_subscriber: EventSubscriber):
event_queue.subscribe_type(TestEvent1, event_queue_subscriber)
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 unittest.mock import MagicMock
import pytest
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.mimikatz_collector.mimikatz_credential_collector import ( # noqa: E501
MIMIKATZ_EVENT_TAGS,
)
from infection_monkey.credential_collectors.mimikatz_collector.windows_credentials import (
WindowsCredentials,
)
@ -18,7 +24,8 @@ def patch_pypykatz(win_creds: [WindowsCredentials], monkeypatch):
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(
@ -110,3 +117,46 @@ def test_pypykatz_result_parsing_no_secrets(monkeypatch):
collected_credentials = collect_credentials()
assert len(collected_credentials) == 1
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