diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py index bb5d7f87d..6c7128677 100644 --- a/monkey/infection_monkey/exploit/HostExploiter.py +++ b/monkey/infection_monkey/exploit/HostExploiter.py @@ -2,7 +2,7 @@ import logging import threading from abc import abstractmethod from datetime import datetime -from typing import Dict +from typing import Dict, Sequence from common.event_queue import IAgentEventQueue from common.utils.exceptions import FailedExploitationError @@ -35,6 +35,7 @@ class HostExploiter: self.event_queue = None self.options = {} self.exploit_result = {} + self.servers = [] def set_start_time(self): self.exploit_info["started"] = datetime.now().isoformat() @@ -58,6 +59,7 @@ class HostExploiter: def exploit_host( self, host, + servers: Sequence[str], current_depth: int, telemetry_messenger: ITelemetryMessenger, event_queue: IAgentEventQueue, @@ -66,6 +68,7 @@ class HostExploiter: interrupt: threading.Event, ): self.host = host + self.servers = servers self.current_depth = current_depth self.telemetry_messenger = telemetry_messenger self.event_queue = event_queue diff --git a/monkey/infection_monkey/exploit/exploiter_wrapper.py b/monkey/infection_monkey/exploit/exploiter_wrapper.py index 8b69ce9d5..ac65a1d8d 100644 --- a/monkey/infection_monkey/exploit/exploiter_wrapper.py +++ b/monkey/infection_monkey/exploit/exploiter_wrapper.py @@ -1,5 +1,5 @@ import threading -from typing import Dict, Type +from typing import Dict, Sequence, Type from common.event_queue import IAgentEventQueue from infection_monkey.model import VictimHost @@ -31,11 +31,17 @@ class ExploiterWrapper: self._agent_binary_repository = agent_binary_repository def exploit_host( - self, host: VictimHost, current_depth: int, options: Dict, interrupt: threading.Event + self, + host: VictimHost, + servers: Sequence[str], + current_depth: int, + options: Dict, + interrupt: threading.Event, ): exploiter = self._exploit_class() return exploiter.exploit_host( host, + servers, current_depth, self._telemetry_messenger, self._event_queue, diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index 2c0ceaa73..1b7c54470 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -104,7 +104,7 @@ class HadoopExploiter(WebRCE): def _build_command(self, path, http_path): # Build command to execute - monkey_cmd = build_monkey_commandline(self.host, self.current_depth + 1) + monkey_cmd = build_monkey_commandline(self.servers, self.current_depth + 1) if self.host.is_windows(): base_command = HADOOP_WINDOWS_COMMAND else: diff --git a/monkey/infection_monkey/exploit/log4shell.py b/monkey/infection_monkey/exploit/log4shell.py index fc925091b..399a2706e 100644 --- a/monkey/infection_monkey/exploit/log4shell.py +++ b/monkey/infection_monkey/exploit/log4shell.py @@ -115,7 +115,7 @@ class Log4ShellExploiter(WebRCE): def _build_command(self, path: PurePath, http_path) -> str: # Build command to execute - monkey_cmd = build_monkey_commandline(self.host, self.current_depth + 1, location=path) + monkey_cmd = build_monkey_commandline(self.servers, self.current_depth + 1, location=path) if self.host.is_windows(): base_command = LOG4SHELL_WINDOWS_COMMAND else: diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index b037c782a..6fd8e27cb 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -179,7 +179,7 @@ class MSSQLExploiter(HostExploiter): def _build_agent_launch_command(self, agent_path_on_victim: PureWindowsPath) -> str: agent_args = build_monkey_commandline( - self.host, self.current_depth + 1, agent_path_on_victim + self.servers, self.current_depth + 1, agent_path_on_victim ) return f"{agent_path_on_victim} {DROPPER_ARG} {agent_args}" diff --git a/monkey/infection_monkey/exploit/powershell.py b/monkey/infection_monkey/exploit/powershell.py index d6b626f9f..ff0f0db4e 100644 --- a/monkey/infection_monkey/exploit/powershell.py +++ b/monkey/infection_monkey/exploit/powershell.py @@ -15,7 +15,7 @@ from infection_monkey.exploit.powershell_utils.powershell_client import ( PowerShellClient, ) from infection_monkey.exploit.tools.helpers import get_agent_dst_path, get_random_file_suffix -from infection_monkey.model import DROPPER_ARG, RUN_MONKEY, VictimHost +from infection_monkey.model import DROPPER_ARG, RUN_MONKEY from infection_monkey.utils.commands import build_monkey_commandline from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.threading import interruptible_iter @@ -169,7 +169,7 @@ class PowerShellExploiter(HostExploiter): def _run_monkey_executable_on_victim(self, executable_path): monkey_execution_command = build_monkey_execution_command( - self.host, self.current_depth + 1, executable_path + self.servers, self.current_depth + 1, executable_path ) logger.info( @@ -179,9 +179,9 @@ class PowerShellExploiter(HostExploiter): self._client.execute_cmd_as_detached_process(monkey_execution_command) -def build_monkey_execution_command(host: VictimHost, depth: int, executable_path: str) -> str: +def build_monkey_execution_command(servers: List[str], depth: int, executable_path: str) -> str: monkey_params = build_monkey_commandline( - target_host=host, + servers, depth=depth, location=executable_path, ) diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index 272f150eb..abf8b4f47 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -91,14 +91,14 @@ class SMBExploiter(HostExploiter): cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % { "dropper_path": remote_full_path } + build_monkey_commandline( - self.host, + self.servers, self.current_depth + 1, str(dest_path), ) else: cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % { "monkey_path": remote_full_path - } + build_monkey_commandline(self.host, self.current_depth + 1) + } + build_monkey_commandline(self.servers, self.current_depth + 1) smb_conn = None for str_bind_format, port in SMBExploiter.KNOWN_PROTOCOLS.values(): diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 69b29c813..3ff128203 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -245,7 +245,7 @@ class SSHExploiter(HostExploiter): try: cmdline = f"{monkey_path_on_victim} {MONKEY_ARG}" - cmdline += build_monkey_commandline(self.host, self.current_depth + 1) + cmdline += build_monkey_commandline(self.servers, self.current_depth + 1) cmdline += " > /dev/null 2>&1 &" ssh.exec_command(cmdline, timeout=SSH_EXEC_TIMEOUT) diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index 4083aa928..f4cbd7948 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -370,14 +370,16 @@ class WebRCE(HostExploiter): default_path = self.get_default_dropper_path() if default_path is False: return False - monkey_cmd = build_monkey_commandline(self.host, self.current_depth + 1, default_path) + monkey_cmd = build_monkey_commandline( + self.servers, self.current_depth + 1, default_path + ) command = RUN_MONKEY % { "monkey_path": path, "monkey_type": DROPPER_ARG, "parameters": monkey_cmd, } else: - monkey_cmd = build_monkey_commandline(self.host, self.current_depth + 1) + monkey_cmd = build_monkey_commandline(self.servers, self.current_depth + 1) command = RUN_MONKEY % { "monkey_path": path, "monkey_type": MONKEY_ARG, diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index 6c5b189f7..8cfd27a3d 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -103,14 +103,14 @@ class WmiExploiter(HostExploiter): cmdline = DROPPER_CMDLINE_WINDOWS % { "dropper_path": remote_full_path } + build_monkey_commandline( - self.host, + self.servers, self.current_depth + 1, DROPPER_TARGET_PATH_WIN64, ) else: cmdline = MONKEY_CMDLINE_WINDOWS % { "monkey_path": remote_full_path - } + build_monkey_commandline(self.host, self.current_depth + 1) + } + build_monkey_commandline(self.servers, self.current_depth + 1) # execute the remote monkey result = WmiTools.get_object(wmi_connection, "Win32_Process").Create( diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index 45b329ad1..6959e6bf1 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -2,7 +2,7 @@ import logging import threading import time from ipaddress import IPv4Interface -from typing import Any, Callable, Collection, List, Optional +from typing import Any, Callable, Collection, List, Optional, Sequence from common.agent_configuration import CustomPBAConfiguration, PluginConfiguration from common.utils import Timer @@ -35,6 +35,7 @@ class AutomatedMaster(IMaster): def __init__( self, current_depth: Optional[int], + servers: Sequence[str], puppet: IPuppet, telemetry_messenger: ITelemetryMessenger, victim_host_factory: VictimHostFactory, @@ -43,6 +44,7 @@ class AutomatedMaster(IMaster): credentials_store: IPropagationCredentialsRepository, ): self._current_depth = current_depth + self._servers = servers self._puppet = puppet self._telemetry_messenger = telemetry_messenger self._control_channel = control_channel @@ -175,7 +177,7 @@ class AutomatedMaster(IMaster): logger.info(f"Current depth is {current_depth}") if maximum_depth_reached(config.propagation.maximum_depth, current_depth): - self._propagator.propagate(config.propagation, current_depth, self._stop) + self._propagator.propagate(config.propagation, current_depth, self._servers, self._stop) else: logger.info("Skipping propagation: maximum depth reached") diff --git a/monkey/infection_monkey/master/exploiter.py b/monkey/infection_monkey/master/exploiter.py index 0c743674c..501e46e9a 100644 --- a/monkey/infection_monkey/master/exploiter.py +++ b/monkey/infection_monkey/master/exploiter.py @@ -53,6 +53,7 @@ class Exploiter: exploitation_config: ExploitationConfiguration, hosts_to_exploit: Queue, current_depth: int, + servers: Sequence[str], results_callback: Callback, scan_completed: Event, stop: Event, @@ -67,6 +68,7 @@ class Exploiter: exploiters_to_run, hosts_to_exploit, current_depth, + servers, results_callback, scan_completed, stop, @@ -103,6 +105,7 @@ class Exploiter: exploiters_to_run: Sequence[PluginConfiguration], hosts_to_exploit: Queue, current_depth: int, + servers: Sequence[str], results_callback: Callback, scan_completed: Event, stop: Event, @@ -113,7 +116,7 @@ class Exploiter: try: victim_host = hosts_to_exploit.get(timeout=QUEUE_TIMEOUT) self._run_all_exploiters( - exploiters_to_run, victim_host, current_depth, results_callback, stop + exploiters_to_run, victim_host, current_depth, servers, results_callback, stop ) except queue.Empty: if _all_hosts_have_been_processed(scan_completed, hosts_to_exploit): @@ -130,6 +133,7 @@ class Exploiter: exploiters_to_run: Sequence[PluginConfiguration], victim_host: VictimHost, current_depth: int, + servers: Sequence[str], results_callback: Callback, stop: Event, ): @@ -147,7 +151,7 @@ class Exploiter: continue exploiter_results = self._run_exploiter( - exploiter_name, exploiter.options, victim_host, current_depth, stop + exploiter_name, exploiter.options, victim_host, current_depth, servers, stop ) results_callback(exploiter_name, victim_host, exploiter_results) @@ -160,6 +164,7 @@ class Exploiter: options: Dict, victim_host: VictimHost, current_depth: int, + servers: Sequence[str], stop: Event, ) -> ExploiterResultData: logger.debug(f"Attempting to use {exploiter_name} on {victim_host.ip_addr}") @@ -169,7 +174,7 @@ class Exploiter: try: return self._puppet.exploit_host( - exploiter_name, victim_host, current_depth, options, stop + exploiter_name, victim_host, current_depth, servers, options, stop ) except Exception as ex: msg = ( diff --git a/monkey/infection_monkey/master/propagator.py b/monkey/infection_monkey/master/propagator.py index 03a5d5ec7..74a0a41ca 100644 --- a/monkey/infection_monkey/master/propagator.py +++ b/monkey/infection_monkey/master/propagator.py @@ -47,7 +47,11 @@ class Propagator: self._hosts_to_exploit: Queue = Queue() def propagate( - self, propagation_config: PropagationConfiguration, current_depth: int, stop: Event + self, + propagation_config: PropagationConfiguration, + current_depth: int, + servers: Sequence[str], + stop: Event, ): logger.info("Attempting to propagate") @@ -66,7 +70,13 @@ class Propagator: exploit_thread = create_daemon_thread( target=self._exploit_hosts, name="PropagatorExploitThread", - args=(propagation_config.exploitation, current_depth, network_scan_completed, stop), + args=( + propagation_config.exploitation, + current_depth, + servers, + network_scan_completed, + stop, + ), ) scan_thread.start() @@ -167,6 +177,7 @@ class Propagator: self, exploitation_config: ExploitationConfiguration, current_depth: int, + servers: Sequence[str], network_scan_completed: Event, stop: Event, ): @@ -176,6 +187,7 @@ class Propagator: exploitation_config, self._hosts_to_exploit, current_depth, + servers, self._process_exploit_attempts, network_scan_completed, stop, diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 966d27842..f8b523b66 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -154,7 +154,8 @@ class InfectionMonkey: logger.debug(f"Default server set to: {self._control_client.server_address}") else: raise Exception( - f"Failed to connect to the island via any known server address: {self._opts.servers}" + f"Failed to connect to the island via " + f"any known server address: {self._opts.servers}" ) self._control_client.wakeup(parent=self._opts.parent) @@ -219,6 +220,7 @@ class InfectionMonkey: self._master = AutomatedMaster( self._current_depth, + self._opts.servers, puppet, telemetry_messenger, victim_host_factory, diff --git a/monkey/infection_monkey/puppet/puppet.py b/monkey/infection_monkey/puppet/puppet.py index ec77ffa35..5f522ffe9 100644 --- a/monkey/infection_monkey/puppet/puppet.py +++ b/monkey/infection_monkey/puppet/puppet.py @@ -70,11 +70,12 @@ class Puppet(IPuppet): name: str, host: VictimHost, current_depth: int, + servers: Sequence[str], options: Dict, interrupt: threading.Event, ) -> ExploiterResultData: exploiter = self._plugin_registry.get_plugin(name, PluginType.EXPLOITER) - return exploiter.exploit_host(host, current_depth, options, interrupt) + return exploiter.exploit_host(host, servers, current_depth, options, interrupt) def run_payload(self, name: str, options: Dict, interrupt: threading.Event): payload = self._plugin_registry.get_plugin(name, PluginType.PAYLOAD) 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 bf388d6a9..e01a545d2 100644 --- a/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py +++ b/monkey/tests/unit_tests/infection_monkey/exploit/test_powershell.py @@ -6,7 +6,6 @@ import pytest from infection_monkey.exploit import powershell from infection_monkey.exploit.tools.helpers import AGENT_BINARY_PATH_WIN64 -from infection_monkey.model.host import VictimHost # Use the path_win32api_get_user_name fixture for all tests in this module pytestmark = pytest.mark.usefixtures("patch_win32api_get_user_name") @@ -16,6 +15,8 @@ PASSWORD_LIST = ["pass1", "pass2"] LM_HASH_LIST = ["bogo_lm_1"] NT_HASH_LIST = ["bogo_nt_1", "bogo_nt_2"] +bogus_servers = ["1.1.1.1:5000", "2.2.2.2:5007"] + mock_agent_binary_repository = MagicMock() mock_agent_binary_repository.get_agent_binary.return_value = BytesIO(b"BINARY_EXECUTABLE") @@ -33,6 +34,7 @@ def powershell_arguments(http_and_https_both_enabled_host): } arguments = { "host": http_and_https_both_enabled_host, + "servers": bogus_servers, "options": options, "current_depth": 2, "telemetry_messenger": MagicMock(), @@ -179,11 +181,10 @@ def test_login_attempts_correctly_reported(monkeypatch, powershell_exploiter, po def test_build_monkey_execution_command(): - host = VictimHost("127.0.0.1") depth = 2 executable_path = "/tmp/test-monkey" - cmd = powershell.build_monkey_execution_command(host, depth, executable_path) + cmd = powershell.build_monkey_execution_command(bogus_servers, depth, executable_path) assert f"-d {depth}" in cmd assert executable_path in cmd diff --git a/monkey/tests/unit_tests/infection_monkey/master/test_automated_master.py b/monkey/tests/unit_tests/infection_monkey/master/test_automated_master.py index 9029ce480..1ebcc886f 100644 --- a/monkey/tests/unit_tests/infection_monkey/master/test_automated_master.py +++ b/monkey/tests/unit_tests/infection_monkey/master/test_automated_master.py @@ -14,7 +14,7 @@ INTERVAL = 0.001 def test_terminate_without_start(): - m = AutomatedMaster(None, None, None, None, MagicMock(), [], MagicMock()) + m = AutomatedMaster(None, [], None, None, None, MagicMock(), [], MagicMock()) # Test that call to terminate does not raise exception m.terminate() @@ -34,7 +34,7 @@ def test_stop_if_cant_get_config_from_island(monkeypatch): monkeypatch.setattr( "infection_monkey.master.automated_master.CHECK_FOR_TERMINATE_INTERVAL_SEC", INTERVAL ) - m = AutomatedMaster(None, None, None, None, cc, [], MagicMock()) + m = AutomatedMaster(None, [], None, None, None, cc, [], MagicMock()) m.start() assert cc.get_config.call_count == CHECK_FOR_CONFIG_COUNT @@ -73,7 +73,7 @@ def test_stop_if_cant_get_stop_signal_from_island(monkeypatch, sleep_and_return_ "infection_monkey.master.automated_master.CHECK_FOR_TERMINATE_INTERVAL_SEC", INTERVAL ) - m = AutomatedMaster(None, None, None, None, cc, [], MagicMock()) + m = AutomatedMaster(None, [], None, None, None, cc, [], MagicMock()) m.start() assert cc.should_agent_stop.call_count == CHECK_FOR_STOP_AGENT_COUNT diff --git a/monkey/tests/unit_tests/infection_monkey/master/test_exploiter.py b/monkey/tests/unit_tests/infection_monkey/master/test_exploiter.py index 45288fbb7..d62b40161 100644 --- a/monkey/tests/unit_tests/infection_monkey/master/test_exploiter.py +++ b/monkey/tests/unit_tests/infection_monkey/master/test_exploiter.py @@ -66,7 +66,7 @@ def hosts_to_exploit(hosts): def enqueue_hosts(hosts: Iterable[VictimHost]): - q = Queue() + q: Queue = Queue() for h in hosts: q.put(h) @@ -85,6 +85,7 @@ def get_host_exploit_combos_from_call_args_list(call_args_list): CREDENTIALS_FOR_PROPAGATION = {"usernames": ["m0nk3y", "user"], "passwords": ["1234", "pword"]} +SERVERS = ["127.0.0.1:5000", "10.10.10.10:5007"] def get_credentials_for_propagation(): @@ -98,7 +99,7 @@ def run_exploiters(exploiter_config, hosts_to_exploit, callback, scan_completed, scan_completed.set() e = Exploiter(puppet, num_workers, get_credentials_for_propagation) - e.exploit_hosts(exploiter_config, hosts, 1, callback, scan_completed, stop) + e.exploit_hosts(exploiter_config, hosts, 1, SERVERS, callback, scan_completed, stop) return inner @@ -124,7 +125,7 @@ def test_credentials_passed_to_exploiter(run_exploiters): run_exploiters(mock_puppet, 1) for call_args in mock_puppet.exploit_host.call_args_list: - assert call_args[0][3].get("credentials") == CREDENTIALS_FOR_PROPAGATION + assert call_args[0][4].get("credentials") == CREDENTIALS_FOR_PROPAGATION def test_stop_after_callback(exploiter_config, callback, scan_completed, stop, hosts_to_exploit): @@ -143,7 +144,9 @@ def test_stop_after_callback(exploiter_config, callback, scan_completed, stop, h # Intentionally NOT setting scan_completed.set(); _callback() will set stop e = Exploiter(MockPuppet(), callback_barrier_count + 2, get_credentials_for_propagation) - e.exploit_hosts(exploiter_config, hosts_to_exploit, 1, stoppable_callback, scan_completed, stop) + e.exploit_hosts( + exploiter_config, hosts_to_exploit, 1, SERVERS, stoppable_callback, scan_completed, stop + ) assert stoppable_callback.call_count == 2 diff --git a/monkey/tests/unit_tests/infection_monkey/master/test_propagator.py b/monkey/tests/unit_tests/infection_monkey/master/test_propagator.py index 53136e755..8f1b51274 100644 --- a/monkey/tests/unit_tests/infection_monkey/master/test_propagator.py +++ b/monkey/tests/unit_tests/infection_monkey/master/test_propagator.py @@ -110,6 +110,8 @@ os_windows = "windows" os_linux = "linux" +SERVERS = ["127.0.0.1:5000", "10.10.10.10:5007"] + @pytest.fixture def mock_ip_scanner(): @@ -134,6 +136,7 @@ class StubExploiter: exploiters_to_run, hosts_to_exploit, current_depth, + servers, results_callback, scan_completed, stop, @@ -171,7 +174,7 @@ def test_scan_result_processing( subnets=["10.0.0.1", "10.0.0.2", "10.0.0.3"], ) propagation_config = get_propagation_config(default_agent_configuration, targets) - p.propagate(propagation_config, 1, Event()) + p.propagate(propagation_config, 1, SERVERS, Event()) assert len(telemetry_messenger_spy.telemetries) == 3 @@ -204,6 +207,7 @@ class MockExploiter: exploiters_to_run, hosts_to_exploit, current_depth, + servers, results_callback, scan_completed, stop, @@ -269,7 +273,7 @@ def test_exploiter_result_processing( subnets=["10.0.0.1", "10.0.0.2", "10.0.0.3"], ) propagation_config = get_propagation_config(default_agent_configuration, targets) - p.propagate(propagation_config, 1, Event()) + p.propagate(propagation_config, 1, SERVERS, Event()) exploit_telems = [t for t in telemetry_messenger_spy.telemetries if isinstance(t, ExploitTelem)] assert len(exploit_telems) == 4 @@ -310,7 +314,7 @@ def test_scan_target_generation( subnets=["10.0.0.0/29", "172.10.20.30"], ) propagation_config = get_propagation_config(default_agent_configuration, targets) - p.propagate(propagation_config, 1, Event()) + p.propagate(propagation_config, 1, SERVERS, Event()) expected_ip_scan_list = [ "10.0.0.0",