diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py index 602dd338a..4b0c78c21 100644 --- a/monkey/infection_monkey/exploit/HostExploiter.py +++ b/monkey/infection_monkey/exploit/HostExploiter.py @@ -4,6 +4,7 @@ from abc import abstractmethod from datetime import datetime from typing import Dict +from common.event_queue import IEventQueue from common.utils.exceptions import FailedExploitationError from infection_monkey.i_puppet import ExploiterResultData from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger @@ -31,6 +32,7 @@ class HostExploiter: self.exploit_attempts = [] self.host = None self.telemetry_messenger = None + self.event_queue = None self.options = {} self.exploit_result = {} @@ -58,6 +60,7 @@ class HostExploiter: host, current_depth: int, telemetry_messenger: ITelemetryMessenger, + event_queue: IEventQueue, agent_repository: IAgentRepository, options: Dict, interrupt: threading.Event, @@ -65,6 +68,7 @@ class HostExploiter: self.host = host self.current_depth = current_depth self.telemetry_messenger = telemetry_messenger + self.event_queue = event_queue self.agent_repository = agent_repository self.options = options self.interrupt = interrupt diff --git a/monkey/infection_monkey/exploit/exploiter_wrapper.py b/monkey/infection_monkey/exploit/exploiter_wrapper.py index 540e0b4a4..8e9d4f22f 100644 --- a/monkey/infection_monkey/exploit/exploiter_wrapper.py +++ b/monkey/infection_monkey/exploit/exploiter_wrapper.py @@ -1,6 +1,7 @@ import threading from typing import Dict, Type +from common.event_queue import IEventQueue from infection_monkey.model import VictimHost from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger @@ -21,10 +22,12 @@ class ExploiterWrapper: self, exploit_class: Type[HostExploiter], telemetry_messenger: ITelemetryMessenger, + event_queue: IEventQueue, agent_repository: IAgentRepository, ): self._exploit_class = exploit_class self._telemetry_messenger = telemetry_messenger + self._event_queue = event_queue self._agent_repository = agent_repository def exploit_host( @@ -35,18 +38,23 @@ class ExploiterWrapper: host, current_depth, self._telemetry_messenger, + self._event_queue, self._agent_repository, options, interrupt, ) def __init__( - self, telemetry_messenger: ITelemetryMessenger, agent_repository: IAgentRepository + self, + telemetry_messenger: ITelemetryMessenger, + event_queue: IEventQueue, + agent_repository: IAgentRepository, ): self._telemetry_messenger = telemetry_messenger + self._event_queue = event_queue self._agent_repository = agent_repository def wrap(self, exploit_class: Type[HostExploiter]): return ExploiterWrapper.Inner( - exploit_class, self._telemetry_messenger, self._agent_repository + exploit_class, self._telemetry_messenger, self._event_queue, self._agent_repository ) diff --git a/monkey/infection_monkey/exploit/zerologon.py b/monkey/infection_monkey/exploit/zerologon.py index 276b0c529..f43a61069 100644 --- a/monkey/infection_monkey/exploit/zerologon.py +++ b/monkey/infection_monkey/exploit/zerologon.py @@ -9,7 +9,7 @@ import os import re import tempfile from binascii import unhexlify -from typing import Dict, List, Optional, Tuple +from typing import Dict, List, Optional, Sequence, Tuple import impacket from impacket.dcerpc.v5 import epm, nrpc, rpcrt, transport @@ -17,6 +17,7 @@ from impacket.dcerpc.v5.dtypes import NULL from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT from common.credentials import Credentials, LMHash, NTHash, Username +from common.events import CredentialsStolenEvent from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.tools.wmi_tools import WmiTools from infection_monkey.exploit.zerologon_utils.dump_secrets import DumpSecrets @@ -30,6 +31,19 @@ from infection_monkey.utils.threading import interruptible_iter logger = logging.getLogger(__name__) +ZEROLOGON_EXPLOITER_TAG = "zerologon-exploiter" +T1003_ATTACK_TECHNIQUE_TAG = "attack-t1003" +T1098_ATTACK_TECHNIQUE_TAG = "attack-t1098" + + +ZEROLOGON_EVENT_TAGS = frozenset( + { + ZEROLOGON_EXPLOITER_TAG, + T1003_ATTACK_TECHNIQUE_TAG, + T1098_ATTACK_TECHNIQUE_TAG, + } +) + class ZerologonExploiter(HostExploiter): _EXPLOITED_SERVICE = "Netlogon" @@ -284,14 +298,20 @@ class ZerologonExploiter(HostExploiter): def send_extracted_creds_as_credential_telemetry( self, user: str, lmhash: str, nthash: str ) -> None: - self.telemetry_messenger.send_telemetry( - CredentialsTelem( - [ - Credentials(Username(user), LMHash(lmhash)), - Credentials(Username(user), NTHash(nthash)), - ] - ) + extracted_credentials = [ + Credentials(Username(user), LMHash(lmhash)), + Credentials(Username(user), NTHash(nthash)), + ] + + self.telemetry_messenger.send_telemetry(CredentialsTelem(extracted_credentials)) + self._publish_credentials_stolen_event(extracted_credentials) + + def _publish_credentials_stolen_event(self, extracted_credentials: Sequence[Credentials]): + credentials_stolen_event = CredentialsStolenEvent( + tags=ZEROLOGON_EVENT_TAGS, + stolen_credentials=extracted_credentials, ) + self.event_queue.publish(credentials_stolen_event) def get_original_pwd_nthash(self, username: str, user_pwd_hashes: List[str]) -> str: if not self.save_HKLM_keys_locally(username, user_pwd_hashes): diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 48cc70c67..24e085e87 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -66,9 +66,6 @@ from infection_monkey.puppet.puppet import Puppet from infection_monkey.system_singleton import SystemSingleton from infection_monkey.telemetry.attack.t1106_telem import T1106Telem from infection_monkey.telemetry.attack.t1107_telem import T1107Telem -from infection_monkey.telemetry.messengers.credentials_intercepting_telemetry_messenger import ( - CredentialsInterceptingTelemetryMessenger, -) from infection_monkey.telemetry.messengers.exploit_intercepting_telemetry_messenger import ( ExploitInterceptingTelemetryMessenger, ) @@ -214,11 +211,8 @@ class InfectionMonkey: victim_host_factory = self._build_victim_host_factory(local_network_interfaces) - telemetry_messenger = CredentialsInterceptingTelemetryMessenger( - ExploitInterceptingTelemetryMessenger( - self._telemetry_messenger, self._monkey_inbound_tunnel - ), - propagation_credentials_repository, + telemetry_messenger = ExploitInterceptingTelemetryMessenger( + self._telemetry_messenger, self._monkey_inbound_tunnel ) self._master = AutomatedMaster( @@ -278,7 +272,7 @@ class InfectionMonkey: agent_repository = CachingAgentRepository( f"https://{self._control_client.server_address}", self._control_client.proxies ) - exploit_wrapper = ExploiterWrapper(self._telemetry_messenger, agent_repository) + exploit_wrapper = ExploiterWrapper(self._telemetry_messenger, event_queue, agent_repository) puppet.load_plugin( "HadoopExploiter", exploit_wrapper.wrap(HadoopExploiter), PluginType.EXPLOITER @@ -296,13 +290,9 @@ class InfectionMonkey: "MSSQLExploiter", exploit_wrapper.wrap(MSSQLExploiter), PluginType.EXPLOITER ) - zerologon_telemetry_messenger = CredentialsInterceptingTelemetryMessenger( - self._telemetry_messenger, propagation_credentials_repository - ) - zerologon_wrapper = ExploiterWrapper(zerologon_telemetry_messenger, agent_repository) puppet.load_plugin( "ZerologonExploiter", - zerologon_wrapper.wrap(ZerologonExploiter), + exploit_wrapper.wrap(ZerologonExploiter), PluginType.EXPLOITER, ) diff --git a/monkey/infection_monkey/telemetry/messengers/credentials_intercepting_telemetry_messenger.py b/monkey/infection_monkey/telemetry/messengers/credentials_intercepting_telemetry_messenger.py deleted file mode 100644 index 25500644b..000000000 --- a/monkey/infection_monkey/telemetry/messengers/credentials_intercepting_telemetry_messenger.py +++ /dev/null @@ -1,40 +0,0 @@ -from functools import singledispatch - -from infection_monkey.credential_repository import IPropagationCredentialsRepository -from infection_monkey.telemetry.credentials_telem import CredentialsTelem -from infection_monkey.telemetry.i_telem import ITelem -from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger - - -class CredentialsInterceptingTelemetryMessenger(ITelemetryMessenger): - def __init__( - self, - telemetry_messenger: ITelemetryMessenger, - credentials_store: IPropagationCredentialsRepository, - ): - self._telemetry_messenger = telemetry_messenger - self._credentials_store = credentials_store - - def send_telemetry(self, telemetry: ITelem): - _send_telemetry(telemetry, self._telemetry_messenger, self._credentials_store) - - -# Note: We can use @singledispatchmethod instead of @singledispatch if we migrate to Python 3.8 or -# later. -@singledispatch -def _send_telemetry( - telemetry: ITelem, - telemetry_messenger: ITelemetryMessenger, - credentials_store: IPropagationCredentialsRepository, -): - telemetry_messenger.send_telemetry(telemetry) - - -@_send_telemetry.register -def _( - telemetry: CredentialsTelem, - telemetry_messenger: ITelemetryMessenger, - credentials_store: IPropagationCredentialsRepository, -): - credentials_store.add_credentials(telemetry.credentials) - telemetry_messenger.send_telemetry(telemetry) diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py b/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py index c5d428f47..a764de832 100644 --- a/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py +++ b/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py @@ -36,6 +36,7 @@ def powershell_arguments(http_and_https_both_enabled_host): "options": options, "current_depth": 2, "telemetry_messenger": MagicMock(), + "event_queue": MagicMock(), "agent_repository": mock_agent_repository, "interrupt": threading.Event(), } diff --git a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_credentials_intercepting_telemetry_messenger.py b/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_credentials_intercepting_telemetry_messenger.py deleted file mode 100644 index 81bd9e30a..000000000 --- a/monkey/tests/unit_tests/infection_monkey/telemetry/messengers/test_credentials_intercepting_telemetry_messenger.py +++ /dev/null @@ -1,52 +0,0 @@ -from unittest.mock import MagicMock - -from common.credentials import Credentials, Password, SSHKeypair, Username -from infection_monkey.telemetry.credentials_telem import CredentialsTelem -from infection_monkey.telemetry.messengers.credentials_intercepting_telemetry_messenger import ( - CredentialsInterceptingTelemetryMessenger, -) - -TELEM_CREDENTIALS = [ - Credentials( - Username("user1"), - SSHKeypair(public_key="some_public_key", private_key="some_private_key"), - ), - Credentials(Username("root"), Password("password")), -] - - -class MockCredentialsTelem(CredentialsTelem): - def __init(self, credentials): - super().__init__(credentials) - - def get_data(self): - return {} - - -def test_credentials_generic_telemetry(TestTelem): - mock_telemetry_messenger = MagicMock() - mock_credentials_repository = MagicMock() - - telemetry_messenger = CredentialsInterceptingTelemetryMessenger( - mock_telemetry_messenger, mock_credentials_repository - ) - - telemetry_messenger.send_telemetry(TestTelem()) - - assert mock_telemetry_messenger.send_telemetry.called - assert not mock_credentials_repository.add_credentials.called - - -def test_successful_intercepting_credentials_telemetry(): - mock_telemetry_messenger = MagicMock() - mock_credentials_repository = MagicMock() - mock_empty_credentials_telem = MockCredentialsTelem(TELEM_CREDENTIALS) - - telemetry_messenger = CredentialsInterceptingTelemetryMessenger( - mock_telemetry_messenger, mock_credentials_repository - ) - - telemetry_messenger.send_telemetry(mock_empty_credentials_telem) - - assert mock_telemetry_messenger.send_telemetry.called - assert mock_credentials_repository.add_credentials.called