forked from p15670423/monkey
Merge pull request #2201 from guardicore/2176-publish-credentials-stolen-in-mimikatz
CredentialsStolenEvent in MimikatzCredentialCollector
This commit is contained in:
commit
bc0c46bfb9
|
@ -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)
|
||||||
|
|
|
@ -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],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue