From 77804caab5ce53e2cb7d8a0fbe9f20aa581fe046 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Mon, 20 Jun 2022 16:29:53 +0300 Subject: [PATCH 01/66] Agent: Add from_dict method to AgentConfiguration Creating AgentConfiguration object from dictionary makes sense because it doesn't couple the configuration to any specific serialization methods. Also, the json sent from the island doesn't match the config structure because it stores config in a dict under "config" key. --- monkey/common/configuration/agent_configuration.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/monkey/common/configuration/agent_configuration.py b/monkey/common/configuration/agent_configuration.py index 15b338fe0..a636e3d95 100644 --- a/monkey/common/configuration/agent_configuration.py +++ b/monkey/common/configuration/agent_configuration.py @@ -24,6 +24,10 @@ class AgentConfiguration: payloads: List[PluginConfiguration] propagation: PropagationConfiguration + @staticmethod + def from_dict(_dict: dict): + return AgentConfigurationSchema().load(_dict) + class AgentConfigurationSchema(Schema): keep_tunnel_open_time = fields.Float() From d8ac441c5963cba8f5335ab29dc1ed6461e1fed8 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 21 Jun 2022 15:49:33 +0300 Subject: [PATCH 02/66] Agent: Fix configuration retrieval in _run_simulation --- monkey/infection_monkey/master/automated_master.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index f79fd5f12..909d36de6 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -141,7 +141,7 @@ class AutomatedMaster(IMaster): try: config = AutomatedMaster._try_communicate_with_island( self._control_channel.get_config, CHECK_FOR_CONFIG_COUNT - )["config"] + ) except IslandCommunicationError as e: logger.error(f"An error occurred while fetching configuration: {e}") return From e83995d96284e7bb77d5883ffc0f562b5d73a004 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 21 Jun 2022 15:50:37 +0300 Subject: [PATCH 03/66] UT: Add a new fixture for config object --- .../monkey_configs/default_config.py | 127 ++++++++++++++++++ monkey/tests/unit_tests/conftest.py | 3 + 2 files changed, 130 insertions(+) create mode 100644 monkey/tests/data_for_tests/monkey_configs/default_config.py diff --git a/monkey/tests/data_for_tests/monkey_configs/default_config.py b/monkey/tests/data_for_tests/monkey_configs/default_config.py new file mode 100644 index 000000000..84d1f54e2 --- /dev/null +++ b/monkey/tests/data_for_tests/monkey_configs/default_config.py @@ -0,0 +1,127 @@ +from common import OperatingSystems +from common.configuration import AgentConfigurationSchema + +flat_config = { + "keep_tunnel_open_time": 30, + "post_breach_actions": [ + {"name": "CommunicateAsBackdoorUser", "options": {}}, + {"name": "ModifyShellStartupFiles", "options": {}}, + {"name": "HiddenFiles", "options": {}}, + {"name": "TrapCommand", "options": {}}, + {"name": "ChangeSetuidSetgid", "options": {}}, + {"name": "ScheduleJobs", "options": {}}, + {"name": "Timestomping", "options": {}}, + {"name": "AccountDiscovery", "options": {}}, + {"name": "ProcessListCollection", "options": {}}, + ], + "credential_collectors": [ + {"name": "MimikatzCollector", "options": {}}, + {"name": "SSHCollector", "options": {}}, + ], + "payloads": [ + { + "name": "ransomware", + "options": { + "encryption": { + "enabled": True, + "directories": {"linux_target_dir": "", "windows_target_dir": ""}, + }, + "other_behaviors": {"readme": True}, + }, + } + ], + "custom_pbas": { + "linux_command": "", + "linux_filename": "", + "windows_command": "", + "windows_filename": "", + }, + "propagation": { + "network_scan": { + "tcp": { + "timeout": 3000, + "ports": [ + 22, + 80, + 135, + 443, + 445, + 2222, + 3306, + 3389, + 5985, + 5986, + 7001, + 8008, + 8080, + 8088, + 8983, + 9200, + 9600, + ], + }, + "icmp": {"timeout": 1000}, + "fingerprinters": [ + {"name": "elastic", "options": {}}, + { + "name": "http", + "options": {"http_ports": [80, 443, 7001, 8008, 8080, 8983, 9200, 9600]}, + }, + {"name": "mssql", "options": {}}, + {"name": "smb", "options": {}}, + {"name": "ssh", "options": {}}, + ], + "targets": { + "blocked_ips": [], + "inaccessible_subnets": [], + "local_network_scan": True, + "subnets": [], + }, + }, + "maximum_depth": 2, + "exploitation": { + "options": {"http_ports": [80, 443, 7001, 8008, 8080, 8983, 9200, 9600]}, + "brute_force": [ + { + "name": "MSSQLExploiter", + "options": {}, + "supported_os": [OperatingSystems.WINDOWS.name], + }, + { + "name": "PowerShellExploiter", + "options": {}, + "supported_os": [OperatingSystems.WINDOWS.name], + }, + { + "name": "SSHExploiter", + "options": {}, + "supported_os": [OperatingSystems.LINUX.name], + }, + { + "name": "SmbExploiter", + "options": {"smb_download_timeout": 30}, + "supported_os": [OperatingSystems.WINDOWS.name], + }, + { + "name": "WmiExploiter", + "options": {"smb_download_timeout": 30}, + "supported_os": [OperatingSystems.WINDOWS.name], + }, + ], + "vulnerability": [ + { + "name": "HadoopExploiter", + "options": {}, + "supported_os": [OperatingSystems.LINUX.name, OperatingSystems.WINDOWS.name], + }, + { + "name": "Log4ShellExploiter", + "options": {}, + "supported_os": [OperatingSystems.LINUX.name, OperatingSystems.WINDOWS.name], + }, + ], + }, + }, +} + +DEFAULT_CONFIG = AgentConfigurationSchema().load(flat_config) diff --git a/monkey/tests/unit_tests/conftest.py b/monkey/tests/unit_tests/conftest.py index 3634e52b9..c54ccce23 100644 --- a/monkey/tests/unit_tests/conftest.py +++ b/monkey/tests/unit_tests/conftest.py @@ -5,6 +5,9 @@ from typing import Callable, Dict import pytest from _pytest.monkeypatch import MonkeyPatch +from tests.data_for_tests.monkey_configs.default_config import DEFAULT_CONFIG + +from common.configuration import AgentConfiguration MONKEY_BASE_PATH = str(Path(__file__).parent.parent.parent) sys.path.insert(0, MONKEY_BASE_PATH) From 6b406ef68685aec5d5a0b5ecb411a415356d808d Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 21 Jun 2022 15:57:57 +0300 Subject: [PATCH 04/66] Agent: Change configuration to object in control channel --- monkey/infection_monkey/i_control_channel.py | 8 +++++--- monkey/infection_monkey/master/control_channel.py | 5 +++-- monkey/infection_monkey/monkey.py | 3 ++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/i_control_channel.py b/monkey/infection_monkey/i_control_channel.py index 33539417c..b90308018 100644 --- a/monkey/infection_monkey/i_control_channel.py +++ b/monkey/infection_monkey/i_control_channel.py @@ -1,5 +1,7 @@ import abc +from common.configuration import AgentConfiguration + class IControlChannel(metaclass=abc.ABCMeta): @abc.abstractmethod @@ -11,10 +13,10 @@ class IControlChannel(metaclass=abc.ABCMeta): """ @abc.abstractmethod - def get_config(self) -> dict: + def get_config(self) -> AgentConfiguration: """ - :return: A dictionary containing Agent Configuration - :rtype: dict + :return: An AgentConfiguration object + :rtype: AgentConfiguration """ pass diff --git a/monkey/infection_monkey/master/control_channel.py b/monkey/infection_monkey/master/control_channel.py index c93af43e0..8567a301f 100644 --- a/monkey/infection_monkey/master/control_channel.py +++ b/monkey/infection_monkey/master/control_channel.py @@ -5,6 +5,7 @@ from typing import Mapping import requests from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT +from common.configuration import AgentConfiguration from infection_monkey.custom_types import PropagationCredentials from infection_monkey.i_control_channel import IControlChannel, IslandCommunicationError @@ -47,7 +48,7 @@ class ControlChannel(IControlChannel): ) as e: raise IslandCommunicationError(e) - def get_config(self) -> dict: + def get_config(self) -> AgentConfiguration: try: response = requests.get( # noqa: DUO123 f"https://{self._control_channel_server}/api/agent", @@ -57,7 +58,7 @@ class ControlChannel(IControlChannel): ) response.raise_for_status() - return json.loads(response.content.decode()) + return AgentConfiguration.from_dict(json.loads(response.text)["config"]) except ( json.JSONDecodeError, requests.exceptions.ConnectionError, diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 466de6664..afcf460b2 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -172,8 +172,9 @@ class InfectionMonkey: ) config = control_channel.get_config() + keep_tunnel_open_time = control_channel.get_config().keep_tunnel_open_time self._monkey_inbound_tunnel = self._control_client.create_control_tunnel( - config["config"]["keep_tunnel_open_time"] + keep_tunnel_open_time ) if self._monkey_inbound_tunnel and should_propagate(config, self._current_depth): self._inbound_tunnel_opened = True From ffe8c3451b9bb1a01f0b8d94afedbc81809dacea Mon Sep 17 00:00:00 2001 From: vakarisz Date: Tue, 21 Jun 2022 16:15:54 +0300 Subject: [PATCH 05/66] Agent: Change scanners to use the config object --- monkey/common/configuration/__init__.py | 12 +++ .../master/automated_master.py | 2 +- monkey/infection_monkey/master/ip_scanner.py | 31 ++++--- monkey/infection_monkey/master/propagator.py | 29 +++--- .../master/test_ip_scanner.py | 51 +++++----- .../master/test_propagator.py | 92 ++++++++++--------- 6 files changed, 130 insertions(+), 87 deletions(-) diff --git a/monkey/common/configuration/__init__.py b/monkey/common/configuration/__init__.py index 2246e27f2..107e5a491 100644 --- a/monkey/common/configuration/__init__.py +++ b/monkey/common/configuration/__init__.py @@ -2,6 +2,18 @@ from .agent_configuration import ( AgentConfiguration, AgentConfigurationSchema, ) +from .agent_sub_configurations import ( + CustomPBAConfiguration, + PluginConfiguration, + ScanTargetConfiguration, + ICMPScanConfiguration, + TCPScanConfiguration, + NetworkScanConfiguration, + ExploitationOptionsConfiguration, + ExploiterConfiguration, + ExploitationConfiguration, + PropagationConfiguration, +) from .default_agent_configuration import ( DEFAULT_AGENT_CONFIGURATION_JSON, build_default_agent_configuration, diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index 909d36de6..573299629 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -174,7 +174,7 @@ class AutomatedMaster(IMaster): logger.info(f"Current depth is {current_depth}") if should_propagate(self._control_channel.get_config(), self._current_depth): - self._propagator.propagate(config["propagation"], current_depth, self._stop) + self._propagator.propagate(config.propagation, current_depth, self._stop) else: logger.info("Skipping propagation: maximum depth reached") diff --git a/monkey/infection_monkey/master/ip_scanner.py b/monkey/infection_monkey/master/ip_scanner.py index 8c0ea5caa..14391765f 100644 --- a/monkey/infection_monkey/master/ip_scanner.py +++ b/monkey/infection_monkey/master/ip_scanner.py @@ -3,8 +3,13 @@ import queue import threading from queue import Queue from threading import Event -from typing import Any, Callable, Dict, List +from typing import Callable, Dict, List +from common.configuration.agent_sub_configurations import ( + NetworkScanConfiguration, + PluginConfiguration, + ScanTargetConfiguration, +) from infection_monkey.i_puppet import ( FingerprintData, IPuppet, @@ -30,7 +35,7 @@ class IPScanner: def scan( self, addresses_to_scan: List[NetworkAddress], - options: Dict, + options: ScanTargetConfiguration, results_callback: Callback, stop: Event, ): @@ -49,12 +54,16 @@ class IPScanner: ) def _scan_addresses( - self, addresses: Queue, options: Dict, results_callback: Callback, stop: Event + self, + addresses: Queue, + options: NetworkScanConfiguration, + results_callback: Callback, + stop: Event, ): - logger.debug(f"Starting scan thread -- Thread ID: {threading.get_ident()}") - icmp_timeout = options["icmp"]["timeout_ms"] / 1000 - tcp_timeout = options["tcp"]["timeout_ms"] / 1000 - tcp_ports = options["tcp"]["ports"] + logger.debug(f"Starting scan .read -- Thread ID: {threading.get_ident()}") + icmp_timeout = options.icmp.timeout + tcp_timeout = options.tcp.timeout + tcp_ports = options.tcp.ports try: while not stop.is_set(): @@ -66,7 +75,7 @@ class IPScanner: fingerprint_data = {} if IPScanner.port_scan_found_open_port(port_scan_data): - fingerprinters = options["fingerprinters"] + fingerprinters = options.fingerprinters fingerprint_data = self._run_fingerprinters( address.ip, fingerprinters, ping_scan_data, port_scan_data, stop ) @@ -90,7 +99,7 @@ class IPScanner: def _run_fingerprinters( self, ip: str, - fingerprinters: List[Dict[str, Any]], + fingerprinters: List[PluginConfiguration], ping_scan_data: PingScanData, port_scan_data: Dict[int, PortScanData], stop: Event, @@ -98,8 +107,8 @@ class IPScanner: fingerprint_data = {} for f in interruptible_iter(fingerprinters, stop): - fingerprint_data[f["name"]] = self._puppet.fingerprint( - f["name"], ip, ping_scan_data, port_scan_data, f["options"] + fingerprint_data[f.name] = self._puppet.fingerprint( + f.name, ip, ping_scan_data, port_scan_data, f.options ) return fingerprint_data diff --git a/monkey/infection_monkey/master/propagator.py b/monkey/infection_monkey/master/propagator.py index be4d6caf2..a74cf7c86 100644 --- a/monkey/infection_monkey/master/propagator.py +++ b/monkey/infection_monkey/master/propagator.py @@ -3,6 +3,8 @@ from queue import Queue from threading import Event from typing import Dict, List +from common.configuration import PropagationConfiguration,\ + NetworkScanConfiguration, ScanTargetConfiguration from infection_monkey.i_puppet import ( ExploiterResultData, FingerprintData, @@ -39,14 +41,18 @@ class Propagator: self._local_network_interfaces = local_network_interfaces self._hosts_to_exploit = None - def propagate(self, propagation_config: Dict, current_depth: int, stop: Event): + def propagate( + self, propagation_config: PropagationConfiguration, current_depth: int, stop: Event + ): logger.info("Attempting to propagate") network_scan_completed = Event() self._hosts_to_exploit = Queue() scan_thread = create_daemon_thread( - target=self._scan_network, name="PropagatorScanThread", args=(propagation_config, stop) + target=self._scan_network, + name="PropagatorScanThread", + args=(propagation_config.network_scan, stop), ) exploit_thread = create_daemon_thread( target=self._exploit_hosts, @@ -64,22 +70,21 @@ class Propagator: logger.info("Finished attempting to propagate") - def _scan_network(self, propagation_config: Dict, stop: Event): + def _scan_network(self, scan_config: NetworkScanConfiguration, stop: Event): logger.info("Starting network scan") - target_config = propagation_config["targets"] - scan_config = propagation_config["network_scan"] - - addresses_to_scan = self._compile_scan_target_list(target_config) + addresses_to_scan = self._compile_scan_target_list(scan_config.targets) self._ip_scanner.scan(addresses_to_scan, scan_config, self._process_scan_results, stop) logger.info("Finished network scan") - def _compile_scan_target_list(self, target_config: Dict) -> List[NetworkAddress]: - ranges_to_scan = target_config["subnet_scan_list"] - inaccessible_subnets = target_config["inaccessible_subnets"] - blocklisted_ips = target_config["blocked_ips"] - enable_local_network_scan = target_config["local_network_scan"] + def _compile_scan_target_list( + self, target_config: ScanTargetConfiguration + ) -> List[NetworkAddress]: + ranges_to_scan = target_config.subnets + inaccessible_subnets = target_config.inaccessible_subnets + blocklisted_ips = target_config.blocked_ips + enable_local_network_scan = target_config.local_network_scan return compile_scan_target_list( self._local_network_interfaces, diff --git a/monkey/tests/unit_tests/infection_monkey/master/test_ip_scanner.py b/monkey/tests/unit_tests/infection_monkey/master/test_ip_scanner.py index 9fafdaee2..069f931b9 100644 --- a/monkey/tests/unit_tests/infection_monkey/master/test_ip_scanner.py +++ b/monkey/tests/unit_tests/infection_monkey/master/test_ip_scanner.py @@ -5,6 +5,12 @@ from unittest.mock import MagicMock import pytest from tests.unit_tests.infection_monkey.master.mock_puppet import MockPuppet +from common.configuration.agent_sub_configurations import ( + ICMPScanConfiguration, + NetworkScanConfiguration, + PluginConfiguration, + TCPScanConfiguration, +) from infection_monkey.i_puppet import FingerprintData, PortScanData, PortStatus from infection_monkey.master import IPScanner from infection_monkey.network import NetworkAddress @@ -14,28 +20,31 @@ LINUX_OS = "linux" @pytest.fixture -def scan_config(): - return { - "tcp": { - "timeout_ms": 3000, - "ports": [ - 22, - 445, - 3389, - 443, - 8008, - 3306, - ], - }, - "icmp": { - "timeout_ms": 1000, - }, - "fingerprinters": [ - {"name": "HTTPFinger", "options": {}}, - {"name": "SMBFinger", "options": {}}, - {"name": "SSHFinger", "options": {}}, +def scan_config(default_agent_config): + tcp_config = TCPScanConfiguration( + timeout=3, + ports=[ + 22, + 445, + 3389, + 443, + 8008, + 3306, ], - } + ) + icmp_config = ICMPScanConfiguration(timeout=1) + fingerprinter_config = [ + PluginConfiguration(name="HTTPFinger", options={}), + PluginConfiguration(name="SMBFinger", options={}), + PluginConfiguration(name="SSHFinger", options={}), + ] + scan_config = NetworkScanConfiguration( + tcp_config, + icmp_config, + fingerprinter_config, + default_agent_config.propagation.network_scan.targets, + ) + return scan_config @pytest.fixture 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 3746e65eb..a2581de60 100644 --- a/monkey/tests/unit_tests/infection_monkey/master/test_propagator.py +++ b/monkey/tests/unit_tests/infection_monkey/master/test_propagator.py @@ -3,6 +3,11 @@ from unittest.mock import MagicMock import pytest +from common.configuration.agent_sub_configurations import ( + NetworkScanConfiguration, + PropagationConfiguration, + ScanTargetConfiguration, +) from infection_monkey.i_puppet import ( ExploiterResultData, FingerprintData, @@ -135,24 +140,35 @@ class StubExploiter: pass -def test_scan_result_processing(telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory): +def get_propagation_config(default_agent_config, scan_target_config: ScanTargetConfiguration): + network_scan = NetworkScanConfiguration( + default_agent_config.propagation.network_scan.tcp, + default_agent_config.propagation.network_scan.icmp, + default_agent_config.propagation.network_scan.fingerprinters, + scan_target_config, + ) + propagation_config = PropagationConfiguration( + default_agent_config.propagation.maximum_depth, + network_scan, + default_agent_config.propagation.exploitation, + ) + return propagation_config + + +def test_scan_result_processing( + telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory, default_agent_config +): p = Propagator( telemetry_messenger_spy, mock_ip_scanner, StubExploiter(), mock_victim_host_factory, [] ) - p.propagate( - { - "targets": { - "subnet_scan_list": ["10.0.0.1", "10.0.0.2", "10.0.0.3"], - "local_network_scan": False, - "inaccessible_subnets": [], - "blocked_ips": [], - }, - "network_scan": {}, # This is empty since MockIPscanner ignores it - "exploiters": {}, # This is empty since StubExploiter ignores it - }, - 1, - Event(), + targets = ScanTargetConfiguration( + blocked_ips=[], + inaccessible_subnets=[], + local_network_scan=False, + subnets=["10.0.0.1", "10.0.0.2", "10.0.0.3"], ) + propagation_config = get_propagation_config(default_agent_config, targets) + p.propagate(propagation_config, 1, Event()) assert len(telemetry_messenger_spy.telemetries) == 3 @@ -237,25 +253,20 @@ class MockExploiter: def test_exploiter_result_processing( - telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory + telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory, default_agent_config ): p = Propagator( telemetry_messenger_spy, mock_ip_scanner, MockExploiter(), mock_victim_host_factory, [] ) - p.propagate( - { - "targets": { - "subnet_scan_list": ["10.0.0.1", "10.0.0.2", "10.0.0.3"], - "local_network_scan": False, - "inaccessible_subnets": [], - "blocked_ips": [], - }, - "network_scan": {}, # This is empty since MockIPscanner ignores it - "exploiters": {}, # This is empty since MockExploiter ignores it - }, - 1, - Event(), + + targets = ScanTargetConfiguration( + blocked_ips=[], + inaccessible_subnets=[], + local_network_scan=False, + subnets=["10.0.0.1", "10.0.0.2", "10.0.0.3"], ) + propagation_config = get_propagation_config(default_agent_config, targets) + p.propagate(propagation_config, 1, Event()) exploit_telems = [t for t in telemetry_messenger_spy.telemetries if isinstance(t, ExploitTelem)] assert len(exploit_telems) == 4 @@ -278,7 +289,9 @@ def test_exploiter_result_processing( assert data["propagation_result"] -def test_scan_target_generation(telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory): +def test_scan_target_generation( + telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory, default_agent_config +): local_network_interfaces = [NetworkInterface("10.0.0.9", "/29")] p = Propagator( telemetry_messenger_spy, @@ -287,20 +300,15 @@ def test_scan_target_generation(telemetry_messenger_spy, mock_ip_scanner, mock_v mock_victim_host_factory, local_network_interfaces, ) - p.propagate( - { - "targets": { - "subnet_scan_list": ["10.0.0.0/29", "172.10.20.30"], - "local_network_scan": True, - "blocked_ips": ["10.0.0.3"], - "inaccessible_subnets": ["10.0.0.128/30", "10.0.0.8/29"], - }, - "network_scan": {}, # This is empty since MockIPscanner ignores it - "exploiters": {}, # This is empty since MockExploiter ignores it - }, - 1, - Event(), + targets = ScanTargetConfiguration( + blocked_ips=["10.0.0.3"], + inaccessible_subnets=["10.0.0.128/30", "10.0.0.8/29"], + local_network_scan=True, + subnets=["10.0.0.0/29", "172.10.20.30"], ) + propagation_config = get_propagation_config(default_agent_config, targets) + p.propagate(propagation_config, 1, Event()) + expected_ip_scan_list = [ "10.0.0.0", "10.0.0.1", From 095e49b5436144145730713c25e71e10f2b8b0c5 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Wed, 22 Jun 2022 17:47:27 +0300 Subject: [PATCH 06/66] Agent: Use deserialized config in automated_master.py --- monkey/infection_monkey/master/automated_master.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index 573299629..684d60003 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -150,7 +150,7 @@ class AutomatedMaster(IMaster): target=self._run_plugins, name="CredentialCollectorThread", args=( - config["credential_collectors"], + config.credential_collectors, "credential collector", self._collect_credentials, ), @@ -158,7 +158,7 @@ class AutomatedMaster(IMaster): pba_thread = create_daemon_thread( target=self._run_pbas, name="PBAThread", - args=(config["post_breach_actions"].items(), self._run_pba, config["custom_pbas"]), + args=(config.post_breach_actions, self._run_pba, config.custom_pbas), ) credential_collector_thread.start() @@ -181,7 +181,7 @@ class AutomatedMaster(IMaster): payload_thread = create_daemon_thread( target=self._run_plugins, name="PayloadThread", - args=(config["payloads"].items(), "payload", self._run_payload), + args=(config.payloads.items(), "payload", self._run_payload), ) payload_thread.start() payload_thread.join() From 9286e8690008c62054bda04b533f3bd52a791130 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Wed, 22 Jun 2022 17:52:34 +0300 Subject: [PATCH 07/66] Agent: Use deserialized in exploiter.py and propagator.py --- monkey/infection_monkey/master/exploiter.py | 34 ++++++++++------- monkey/infection_monkey/master/propagator.py | 13 ++++--- .../infection_monkey/master/test_exploiter.py | 38 +++++++++++++------ 3 files changed, 55 insertions(+), 30 deletions(-) diff --git a/monkey/infection_monkey/master/exploiter.py b/monkey/infection_monkey/master/exploiter.py index 35a212482..46b6799a0 100644 --- a/monkey/infection_monkey/master/exploiter.py +++ b/monkey/infection_monkey/master/exploiter.py @@ -5,9 +5,13 @@ from copy import deepcopy from itertools import chain from queue import Queue from threading import Event -from typing import Callable, Dict, List, Mapping +from typing import Callable, Dict, List from common import OperatingSystems +from common.configuration.agent_sub_configurations import ( + ExploitationConfiguration, + ExploiterConfiguration, +) from infection_monkey.custom_types import PropagationCredentials from infection_monkey.i_puppet import ExploiterResultData, IPuppet from infection_monkey.model import VictimHost @@ -46,7 +50,7 @@ class Exploiter: def exploit_hosts( self, - exploiter_config: Dict, + exploiter_config: ExploitationConfiguration, hosts_to_exploit: Queue, current_depth: int, results_callback: Callback, @@ -56,7 +60,7 @@ class Exploiter: exploiters_to_run = self._process_exploiter_config(exploiter_config) logger.debug( "Agent is configured to run the following exploiters in order: " - f"{', '.join([e['name'] for e in exploiters_to_run])}" + f"{', '.join([e.name for e in exploiters_to_run])}" ) exploit_args = ( @@ -75,24 +79,28 @@ class Exploiter: ) @staticmethod - def _process_exploiter_config(exploiter_config: Mapping) -> List[Mapping]: + def _process_exploiter_config( + exploiter_config: ExploitationConfiguration, + ) -> List[ExploiterConfiguration]: # Run vulnerability exploiters before brute force exploiters to minimize the effect of # account lockout due to invalid credentials - ordered_exploiters = chain( - exploiter_config["vulnerability"], exploiter_config["brute_force"] - ) + ordered_exploiters = chain(exploiter_config.vulnerability, exploiter_config.brute_force) exploiters_to_run = list(deepcopy(ordered_exploiters)) + extended_exploiters = [] for exploiter in exploiters_to_run: # This order allows exploiter-specific options to # override general options for all exploiters. - exploiter["options"] = {**exploiter_config["options"], **exploiter["options"]} + options = {**exploiter_config.options.__dict__, **exploiter.options} + extended_exploiters.append( + ExploiterConfiguration(exploiter.name, options, exploiter.supported_os) + ) - return exploiters_to_run + return extended_exploiters def _exploit_hosts_on_queue( self, - exploiters_to_run: List[Dict], + exploiters_to_run: List[ExploiterConfiguration], hosts_to_exploit: Queue, current_depth: int, results_callback: Callback, @@ -119,7 +127,7 @@ class Exploiter: def _run_all_exploiters( self, - exploiters_to_run: List[Dict], + exploiters_to_run: List[ExploiterConfiguration], victim_host: VictimHost, current_depth: int, results_callback: Callback, @@ -127,7 +135,7 @@ class Exploiter: ): for exploiter in interruptible_iter(exploiters_to_run, stop): - exploiter_name = exploiter["name"] + exploiter_name = exploiter.name victim_os = victim_host.os.get("type") # We want to try all exploiters if the victim's OS is unknown @@ -140,7 +148,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, stop ) results_callback(exploiter_name, victim_host, exploiter_results) diff --git a/monkey/infection_monkey/master/propagator.py b/monkey/infection_monkey/master/propagator.py index a74cf7c86..64edae2ec 100644 --- a/monkey/infection_monkey/master/propagator.py +++ b/monkey/infection_monkey/master/propagator.py @@ -1,10 +1,13 @@ import logging from queue import Queue from threading import Event -from typing import Dict, List +from typing import List -from common.configuration import PropagationConfiguration,\ - NetworkScanConfiguration, ScanTargetConfiguration +from common.configuration import ( + NetworkScanConfiguration, + PropagationConfiguration, + ScanTargetConfiguration, +) from infection_monkey.i_puppet import ( ExploiterResultData, FingerprintData, @@ -139,14 +142,14 @@ class Propagator: def _exploit_hosts( self, - propagation_config: Dict, + propagation_config: PropagationConfiguration, current_depth: int, network_scan_completed: Event, stop: Event, ): logger.info("Exploiting victims") - exploiter_config = propagation_config["exploiters"] + exploiter_config = propagation_config.exploitation self._exploiter.exploit_hosts( exploiter_config, self._hosts_to_exploit, 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 f5d9c2da4..2af0f3809 100644 --- a/monkey/tests/unit_tests/infection_monkey/master/test_exploiter.py +++ b/monkey/tests/unit_tests/infection_monkey/master/test_exploiter.py @@ -8,6 +8,10 @@ import pytest from tests.unit_tests.infection_monkey.master.mock_puppet import MockPuppet from common import OperatingSystems +from common.configuration.agent_sub_configurations import ( + ExploitationConfiguration, + ExploiterConfiguration, +) from infection_monkey.master import Exploiter from infection_monkey.model import VictimHost @@ -35,18 +39,28 @@ def callback(): @pytest.fixture -def exploiter_config(): - return { - "options": {"dropper_path_linux": "/tmp/monkey"}, - "brute_force": [ - {"name": "MSSQLExploiter", "options": {"timeout": 10}}, - {"name": "SSHExploiter", "options": {}}, - {"name": "WmiExploiter", "options": {"timeout": 10}}, - ], - "vulnerability": [ - {"name": "ZerologonExploiter", "options": {}}, - ], - } +def exploiter_config(default_agent_config): + brute_force = [ + ExploiterConfiguration( + name="MSSQLExploiter", options={"timeout": 10} + ), + ExploiterConfiguration( + name="SSHExploiter", options={} + ), + ExploiterConfiguration( + name="WmiExploiter", options={"timeout": 10} + ), + ] + vulnerability = [ + ExploiterConfiguration( + name="ZerologonExploiter", options={} + ) + ] + return ExploitationConfiguration( + options=default_agent_config.propagation.exploitation.options, + brute_force=brute_force, + vulnerability=vulnerability, + ) @pytest.fixture From 86ed174d743e0a1f27912b1800584919280861b2 Mon Sep 17 00:00:00 2001 From: vakaris_zilius Date: Thu, 23 Jun 2022 15:06:48 +0000 Subject: [PATCH 08/66] Agent: Usa agent config object instead of dict in option_parsing.py --- monkey/infection_monkey/master/option_parsing.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/master/option_parsing.py b/monkey/infection_monkey/master/option_parsing.py index c35bf6303..c9262c5c9 100644 --- a/monkey/infection_monkey/master/option_parsing.py +++ b/monkey/infection_monkey/master/option_parsing.py @@ -1,13 +1,12 @@ -from typing import Dict - +from common.configuration import CustomPBAConfiguration from infection_monkey.utils.environment import is_windows_os -def custom_pba_is_enabled(pba_options: Dict) -> bool: +def custom_pba_is_enabled(pba_options: CustomPBAConfiguration) -> bool: if not is_windows_os(): - if pba_options["linux_command"]: + if pba_options.linux_command: return True else: - if pba_options["windows_command"]: + if pba_options.windows_command: return True return False From ab67853192e4f87eb5145f2d4fc797ddb0c9e333 Mon Sep 17 00:00:00 2001 From: vakaris_zilius Date: Thu, 23 Jun 2022 15:07:33 +0000 Subject: [PATCH 09/66] Agent: Usa agent config object instead of dict automated_master.py --- .../master/automated_master.py | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index 684d60003..90223daad 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -1,8 +1,9 @@ import logging import threading import time -from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional, Tuple +from typing import Any, Callable, Dict, List, Optional, Tuple +from common.configuration import PluginConfiguration from common.utils import Timer from infection_monkey.credential_store import ICredentialsStore from infection_monkey.i_control_channel import IControlChannel, IslandCommunicationError @@ -188,17 +189,17 @@ class AutomatedMaster(IMaster): pba_thread.join() - def _collect_credentials(self, collector: str): - credentials = self._puppet.run_credential_collector(collector, {}) + def _collect_credentials(self, collector: PluginConfiguration): + credentials = self._puppet.run_credential_collector(collector.name, collector.options) if credentials: self._telemetry_messenger.send_telemetry(CredentialsTelem(credentials)) else: logger.debug(f"No credentials were collected by {collector}") - def _run_pba(self, pba: Tuple[str, Dict]): - name = pba[0] - options = pba[1] + def _run_pba(self, pba: PluginConfiguration): + name = pba.name + options = pba.options for pba_data in self._puppet.run_pba(name, options): self._telemetry_messenger.send_telemetry(PostBreachTelem(pba_data)) @@ -210,15 +211,22 @@ class AutomatedMaster(IMaster): self._puppet.run_payload(name, options, self._stop) def _run_pbas( - self, plugins: Iterable[Any], callback: Callable[[Any], None], custom_pba_options: Mapping + self, + plugins: List[PluginConfiguration], + callback: Callable[[Any], None], + custom_pba_options: Dict, ): self._run_plugins(plugins, "post-breach action", callback) if custom_pba_is_enabled(custom_pba_options): - self._run_plugins([("CustomPBA", custom_pba_options)], "post-breach action", callback) + self._run_plugins( + [PluginConfiguration(name="CustomPBA", options=custom_pba_options)], + "post-breach action", + callback, + ) def _run_plugins( - self, plugins: Iterable[Any], plugin_type: str, callback: Callable[[Any], None] + self, plugins: List[PluginConfiguration], plugin_type: str, callback: Callable[[Any], None] ): logger.info(f"Running {plugin_type}s") logger.debug(f"Found {len(plugins)} {plugin_type}(s) to run") From 0f848eb28439f56e34d3952e8f30c16fc8be7aef Mon Sep 17 00:00:00 2001 From: vakaris_zilius Date: Thu, 23 Jun 2022 15:08:20 +0000 Subject: [PATCH 10/66] Agent: Usa agent config object instead of dict should_propagate --- monkey/infection_monkey/utils/propagation.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/utils/propagation.py b/monkey/infection_monkey/utils/propagation.py index 004bafdd2..9cfccab51 100644 --- a/monkey/infection_monkey/utils/propagation.py +++ b/monkey/infection_monkey/utils/propagation.py @@ -1,2 +1,5 @@ -def should_propagate(config: dict, current_depth: int) -> bool: - return config["config"]["depth"] > current_depth +from common.configuration import AgentConfiguration + + +def should_propagate(config: AgentConfiguration, current_depth: int) -> bool: + return config.propagation.maximum_depth > current_depth From aff54232e97b52dcf00b3b6aa00df5abf0332870 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 23 Jun 2022 14:44:42 -0400 Subject: [PATCH 11/66] Agent: Remove redundant call to control_channel.get_config() --- monkey/infection_monkey/monkey.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index afcf460b2..cf9283526 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -172,9 +172,8 @@ class InfectionMonkey: ) config = control_channel.get_config() - keep_tunnel_open_time = control_channel.get_config().keep_tunnel_open_time self._monkey_inbound_tunnel = self._control_client.create_control_tunnel( - keep_tunnel_open_time + config.keep_tunnel_open_time ) if self._monkey_inbound_tunnel and should_propagate(config, self._current_depth): self._inbound_tunnel_opened = True From bba7139be69eddfb797a467966aac39692d1e859 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 23 Jun 2022 14:46:03 -0400 Subject: [PATCH 12/66] Agent: Add missing return type hint to _try_communicate_with_island() --- monkey/infection_monkey/master/automated_master.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index 90223daad..6fe9188d3 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -112,7 +112,7 @@ class AutomatedMaster(IMaster): time.sleep(CHECK_FOR_TERMINATE_INTERVAL_SEC) @staticmethod - def _try_communicate_with_island(fn: Callable[[], Any], max_tries: int): + def _try_communicate_with_island(fn: Callable[[], Any], max_tries: int) -> Any: tries = 0 while tries < max_tries: try: From 6e951ed65d6d6ba026395ec487a7b41b2dda0bc1 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 23 Jun 2022 14:56:24 -0400 Subject: [PATCH 13/66] UT: Remove supported_os from default_config.py "supported_os" was removed from the schema in d079d74b --- .../tests/data_for_tests/monkey_configs/default_config.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/monkey/tests/data_for_tests/monkey_configs/default_config.py b/monkey/tests/data_for_tests/monkey_configs/default_config.py index 84d1f54e2..e232d122d 100644 --- a/monkey/tests/data_for_tests/monkey_configs/default_config.py +++ b/monkey/tests/data_for_tests/monkey_configs/default_config.py @@ -1,4 +1,3 @@ -from common import OperatingSystems from common.configuration import AgentConfigurationSchema flat_config = { @@ -85,39 +84,32 @@ flat_config = { { "name": "MSSQLExploiter", "options": {}, - "supported_os": [OperatingSystems.WINDOWS.name], }, { "name": "PowerShellExploiter", "options": {}, - "supported_os": [OperatingSystems.WINDOWS.name], }, { "name": "SSHExploiter", "options": {}, - "supported_os": [OperatingSystems.LINUX.name], }, { "name": "SmbExploiter", "options": {"smb_download_timeout": 30}, - "supported_os": [OperatingSystems.WINDOWS.name], }, { "name": "WmiExploiter", "options": {"smb_download_timeout": 30}, - "supported_os": [OperatingSystems.WINDOWS.name], }, ], "vulnerability": [ { "name": "HadoopExploiter", "options": {}, - "supported_os": [OperatingSystems.LINUX.name, OperatingSystems.WINDOWS.name], }, { "name": "Log4ShellExploiter", "options": {}, - "supported_os": [OperatingSystems.LINUX.name, OperatingSystems.WINDOWS.name], }, ], }, From 81d3300ec79b5c6423f8a5904a256fd1b5989649 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 23 Jun 2022 15:43:34 -0400 Subject: [PATCH 14/66] Agent: Remove print() that was added by mistake --- monkey/infection_monkey/master/exploiter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/infection_monkey/master/exploiter.py b/monkey/infection_monkey/master/exploiter.py index 46b6799a0..c8bb57299 100644 --- a/monkey/infection_monkey/master/exploiter.py +++ b/monkey/infection_monkey/master/exploiter.py @@ -139,7 +139,6 @@ class Exploiter: victim_os = victim_host.os.get("type") # We want to try all exploiters if the victim's OS is unknown - print(victim_os) if victim_os is not None and victim_os not in SUPPORTED_OS[exploiter_name]: logger.debug( f"Skipping {exploiter_name} because it does not support " From bff92ed7add225d67f67bf2f1fcc74f1a1938c20 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 23 Jun 2022 15:48:25 -0400 Subject: [PATCH 15/66] UT: Fix erroneously abbreviated fixture --- .../infection_monkey/master/test_exploiter.py | 22 +++++------------ .../master/test_ip_scanner.py | 4 ++-- .../master/test_propagator.py | 24 +++++++++---------- 3 files changed, 20 insertions(+), 30 deletions(-) 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 2af0f3809..cc7e497b6 100644 --- a/monkey/tests/unit_tests/infection_monkey/master/test_exploiter.py +++ b/monkey/tests/unit_tests/infection_monkey/master/test_exploiter.py @@ -39,25 +39,15 @@ def callback(): @pytest.fixture -def exploiter_config(default_agent_config): +def exploiter_config(default_agent_configuration): brute_force = [ - ExploiterConfiguration( - name="MSSQLExploiter", options={"timeout": 10} - ), - ExploiterConfiguration( - name="SSHExploiter", options={} - ), - ExploiterConfiguration( - name="WmiExploiter", options={"timeout": 10} - ), - ] - vulnerability = [ - ExploiterConfiguration( - name="ZerologonExploiter", options={} - ) + ExploiterConfiguration(name="MSSQLExploiter", options={"timeout": 10}), + ExploiterConfiguration(name="SSHExploiter", options={}), + ExploiterConfiguration(name="WmiExploiter", options={"timeout": 10}), ] + vulnerability = [ExploiterConfiguration(name="ZerologonExploiter", options={})] return ExploitationConfiguration( - options=default_agent_config.propagation.exploitation.options, + options=default_agent_configuration.propagation.exploitation.options, brute_force=brute_force, vulnerability=vulnerability, ) diff --git a/monkey/tests/unit_tests/infection_monkey/master/test_ip_scanner.py b/monkey/tests/unit_tests/infection_monkey/master/test_ip_scanner.py index 069f931b9..bf026510f 100644 --- a/monkey/tests/unit_tests/infection_monkey/master/test_ip_scanner.py +++ b/monkey/tests/unit_tests/infection_monkey/master/test_ip_scanner.py @@ -20,7 +20,7 @@ LINUX_OS = "linux" @pytest.fixture -def scan_config(default_agent_config): +def scan_config(default_agent_configuration): tcp_config = TCPScanConfiguration( timeout=3, ports=[ @@ -42,7 +42,7 @@ def scan_config(default_agent_config): tcp_config, icmp_config, fingerprinter_config, - default_agent_config.propagation.network_scan.targets, + default_agent_configuration.propagation.network_scan.targets, ) return scan_config 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 a2581de60..847d92bcd 100644 --- a/monkey/tests/unit_tests/infection_monkey/master/test_propagator.py +++ b/monkey/tests/unit_tests/infection_monkey/master/test_propagator.py @@ -140,23 +140,23 @@ class StubExploiter: pass -def get_propagation_config(default_agent_config, scan_target_config: ScanTargetConfiguration): +def get_propagation_config(default_agent_configuration, scan_target_config: ScanTargetConfiguration): network_scan = NetworkScanConfiguration( - default_agent_config.propagation.network_scan.tcp, - default_agent_config.propagation.network_scan.icmp, - default_agent_config.propagation.network_scan.fingerprinters, + default_agent_configuration.propagation.network_scan.tcp, + default_agent_configuration.propagation.network_scan.icmp, + default_agent_configuration.propagation.network_scan.fingerprinters, scan_target_config, ) propagation_config = PropagationConfiguration( - default_agent_config.propagation.maximum_depth, + default_agent_configuration.propagation.maximum_depth, network_scan, - default_agent_config.propagation.exploitation, + default_agent_configuration.propagation.exploitation, ) return propagation_config def test_scan_result_processing( - telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory, default_agent_config + telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory, default_agent_configuration ): p = Propagator( telemetry_messenger_spy, mock_ip_scanner, StubExploiter(), mock_victim_host_factory, [] @@ -167,7 +167,7 @@ def test_scan_result_processing( local_network_scan=False, subnets=["10.0.0.1", "10.0.0.2", "10.0.0.3"], ) - propagation_config = get_propagation_config(default_agent_config, targets) + propagation_config = get_propagation_config(default_agent_configuration, targets) p.propagate(propagation_config, 1, Event()) assert len(telemetry_messenger_spy.telemetries) == 3 @@ -253,7 +253,7 @@ class MockExploiter: def test_exploiter_result_processing( - telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory, default_agent_config + telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory, default_agent_configuration ): p = Propagator( telemetry_messenger_spy, mock_ip_scanner, MockExploiter(), mock_victim_host_factory, [] @@ -265,7 +265,7 @@ def test_exploiter_result_processing( local_network_scan=False, subnets=["10.0.0.1", "10.0.0.2", "10.0.0.3"], ) - propagation_config = get_propagation_config(default_agent_config, targets) + propagation_config = get_propagation_config(default_agent_configuration, targets) p.propagate(propagation_config, 1, Event()) exploit_telems = [t for t in telemetry_messenger_spy.telemetries if isinstance(t, ExploitTelem)] @@ -290,7 +290,7 @@ def test_exploiter_result_processing( def test_scan_target_generation( - telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory, default_agent_config + telemetry_messenger_spy, mock_ip_scanner, mock_victim_host_factory, default_agent_configuration ): local_network_interfaces = [NetworkInterface("10.0.0.9", "/29")] p = Propagator( @@ -306,7 +306,7 @@ def test_scan_target_generation( local_network_scan=True, subnets=["10.0.0.0/29", "172.10.20.30"], ) - propagation_config = get_propagation_config(default_agent_config, targets) + propagation_config = get_propagation_config(default_agent_configuration, targets) p.propagate(propagation_config, 1, Event()) expected_ip_scan_list = [ From 5a95aef94c74eb9175b66313de6da415b59a8dd7 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 23 Jun 2022 15:51:52 -0400 Subject: [PATCH 16/66] Agent: Remove unnecessary parameter --- monkey/infection_monkey/master/exploiter.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/monkey/infection_monkey/master/exploiter.py b/monkey/infection_monkey/master/exploiter.py index c8bb57299..60910226d 100644 --- a/monkey/infection_monkey/master/exploiter.py +++ b/monkey/infection_monkey/master/exploiter.py @@ -92,9 +92,7 @@ class Exploiter: # This order allows exploiter-specific options to # override general options for all exploiters. options = {**exploiter_config.options.__dict__, **exploiter.options} - extended_exploiters.append( - ExploiterConfiguration(exploiter.name, options, exploiter.supported_os) - ) + extended_exploiters.append(ExploiterConfiguration(exploiter.name, options)) return extended_exploiters From afeca66d92df422ee581a02ce67b0fc1fa78b355 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 23 Jun 2022 16:27:05 -0400 Subject: [PATCH 17/66] UT: Use AgentConfiguration in test_propagation.py --- .../utils/test_propagation.py | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_propagation.py b/monkey/tests/unit_tests/infection_monkey/utils/test_propagation.py index 37f7194a6..15c15d0f0 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_propagation.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_propagation.py @@ -1,11 +1,26 @@ +import pytest + +from common.configuration import AgentConfiguration, AgentConfigurationSchema from infection_monkey.utils.propagation import should_propagate -def get_config(max_depth): - return {"config": {"depth": max_depth}} +@pytest.fixture +def get_config(default_agent_configuration): + def _inner(max_depth): + # AgentConfiguration is a frozen dataclass, so we need to deserialize and reserialize to + # modify it. The benefit is that it's impossible to construct an invalid object. The + # downside is the extra steps required to change an object. Maybe we can come up with a + # better all-around solution. It depends how often we need to mutate these objects (probably + # only for tests). + agent_dict = AgentConfigurationSchema().dump(default_agent_configuration) + agent_dict["propagation"]["maximum_depth"] = max_depth + + return AgentConfiguration.from_dict(agent_dict) + + return _inner -def test_should_propagate_current_less_than_max(): +def test_should_propagate_current_less_than_max(get_config): max_depth = 2 current_depth = 1 @@ -14,7 +29,7 @@ def test_should_propagate_current_less_than_max(): assert should_propagate(config, current_depth) is True -def test_should_propagate_current_greater_than_max(): +def test_should_propagate_current_greater_than_max(get_config): max_depth = 2 current_depth = 3 @@ -23,7 +38,7 @@ def test_should_propagate_current_greater_than_max(): assert should_propagate(config, current_depth) is False -def test_should_propagate_current_equal_to_max(): +def test_should_propagate_current_equal_to_max(get_config): max_depth = 2 current_depth = max_depth From ad0f6946bdad1ffbb8038bc299acd14d673da195 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 23 Jun 2022 16:31:28 -0400 Subject: [PATCH 18/66] Agent: Decouple should_propagate() and AgentConfiguration --- .../master/automated_master.py | 2 +- monkey/infection_monkey/monkey.py | 4 +- monkey/infection_monkey/utils/propagation.py | 7 +-- .../utils/test_propagation.py | 45 +++++-------------- 4 files changed, 16 insertions(+), 42 deletions(-) diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index 6fe9188d3..ced26e58d 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -174,7 +174,7 @@ class AutomatedMaster(IMaster): current_depth = self._current_depth if self._current_depth is not None else 0 logger.info(f"Current depth is {current_depth}") - if should_propagate(self._control_channel.get_config(), self._current_depth): + if should_propagate(config.propagation.maximum_depth, self._current_depth): self._propagator.propagate(config.propagation, current_depth, self._stop) else: logger.info("Skipping propagation: maximum depth reached") diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index cf9283526..23a5a3ace 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -175,7 +175,9 @@ class InfectionMonkey: self._monkey_inbound_tunnel = self._control_client.create_control_tunnel( config.keep_tunnel_open_time ) - if self._monkey_inbound_tunnel and should_propagate(config, self._current_depth): + if self._monkey_inbound_tunnel and should_propagate( + config.propagation.maximum_depth, self._current_depth + ): self._inbound_tunnel_opened = True self._monkey_inbound_tunnel.start() diff --git a/monkey/infection_monkey/utils/propagation.py b/monkey/infection_monkey/utils/propagation.py index 9cfccab51..5036b5745 100644 --- a/monkey/infection_monkey/utils/propagation.py +++ b/monkey/infection_monkey/utils/propagation.py @@ -1,5 +1,2 @@ -from common.configuration import AgentConfiguration - - -def should_propagate(config: AgentConfiguration, current_depth: int) -> bool: - return config.propagation.maximum_depth > current_depth +def should_propagate(maximum_depth: int, current_depth: int) -> bool: + return maximum_depth > current_depth diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_propagation.py b/monkey/tests/unit_tests/infection_monkey/utils/test_propagation.py index 15c15d0f0..a4531f92d 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_propagation.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_propagation.py @@ -1,47 +1,22 @@ -import pytest - -from common.configuration import AgentConfiguration, AgentConfigurationSchema from infection_monkey.utils.propagation import should_propagate -@pytest.fixture -def get_config(default_agent_configuration): - def _inner(max_depth): - # AgentConfiguration is a frozen dataclass, so we need to deserialize and reserialize to - # modify it. The benefit is that it's impossible to construct an invalid object. The - # downside is the extra steps required to change an object. Maybe we can come up with a - # better all-around solution. It depends how often we need to mutate these objects (probably - # only for tests). - agent_dict = AgentConfigurationSchema().dump(default_agent_configuration) - agent_dict["propagation"]["maximum_depth"] = max_depth - - return AgentConfiguration.from_dict(agent_dict) - - return _inner - - -def test_should_propagate_current_less_than_max(get_config): - max_depth = 2 +def test_should_propagate_current_less_than_max(): + maximum_depth = 2 current_depth = 1 - config = get_config(max_depth) - - assert should_propagate(config, current_depth) is True + assert should_propagate(maximum_depth, current_depth) is True -def test_should_propagate_current_greater_than_max(get_config): - max_depth = 2 +def test_should_propagate_current_greater_than_max(): + maximum_depth = 2 current_depth = 3 - config = get_config(max_depth) - - assert should_propagate(config, current_depth) is False + assert should_propagate(maximum_depth, current_depth) is False -def test_should_propagate_current_equal_to_max(get_config): - max_depth = 2 - current_depth = max_depth +def test_should_propagate_current_equal_to_max(): + maximum_depth = 2 + current_depth = maximum_depth - config = get_config(max_depth) - - assert should_propagate(config, current_depth) is False + assert should_propagate(maximum_depth, current_depth) is False From 05f640d48777f4fbab3f9a59b105c68154e3a318 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 23 Jun 2022 16:36:01 -0400 Subject: [PATCH 19/66] Agent: Rename should_propagate -> maximum_depth_reached --- monkey/infection_monkey/master/automated_master.py | 4 ++-- monkey/infection_monkey/monkey.py | 4 ++-- monkey/infection_monkey/utils/propagation.py | 2 +- .../infection_monkey/utils/test_propagation.py | 14 +++++++------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index ced26e58d..de55ef904 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -14,7 +14,7 @@ from infection_monkey.network import NetworkInterface from infection_monkey.telemetry.credentials_telem import CredentialsTelem from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.telemetry.post_breach_telem import PostBreachTelem -from infection_monkey.utils.propagation import should_propagate +from infection_monkey.utils.propagation import maximum_depth_reached from infection_monkey.utils.threading import create_daemon_thread, interruptible_iter from . import Exploiter, IPScanner, Propagator @@ -174,7 +174,7 @@ class AutomatedMaster(IMaster): current_depth = self._current_depth if self._current_depth is not None else 0 logger.info(f"Current depth is {current_depth}") - if should_propagate(config.propagation.maximum_depth, self._current_depth): + if maximum_depth_reached(config.propagation.maximum_depth, self._current_depth): self._propagator.propagate(config.propagation, current_depth, self._stop) else: logger.info("Skipping propagation: maximum depth reached") diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 23a5a3ace..749803c7b 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -78,7 +78,7 @@ from infection_monkey.utils.monkey_dir import ( remove_monkey_dir, ) from infection_monkey.utils.monkey_log_path import get_agent_log_path -from infection_monkey.utils.propagation import should_propagate +from infection_monkey.utils.propagation import maximum_depth_reached from infection_monkey.utils.signal_handler import register_signal_handlers, reset_signal_handlers logger = logging.getLogger(__name__) @@ -175,7 +175,7 @@ class InfectionMonkey: self._monkey_inbound_tunnel = self._control_client.create_control_tunnel( config.keep_tunnel_open_time ) - if self._monkey_inbound_tunnel and should_propagate( + if self._monkey_inbound_tunnel and maximum_depth_reached( config.propagation.maximum_depth, self._current_depth ): self._inbound_tunnel_opened = True diff --git a/monkey/infection_monkey/utils/propagation.py b/monkey/infection_monkey/utils/propagation.py index 5036b5745..2da2e7bee 100644 --- a/monkey/infection_monkey/utils/propagation.py +++ b/monkey/infection_monkey/utils/propagation.py @@ -1,2 +1,2 @@ -def should_propagate(maximum_depth: int, current_depth: int) -> bool: +def maximum_depth_reached(maximum_depth: int, current_depth: int) -> bool: return maximum_depth > current_depth diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_propagation.py b/monkey/tests/unit_tests/infection_monkey/utils/test_propagation.py index a4531f92d..19b2c18b5 100644 --- a/monkey/tests/unit_tests/infection_monkey/utils/test_propagation.py +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_propagation.py @@ -1,22 +1,22 @@ -from infection_monkey.utils.propagation import should_propagate +from infection_monkey.utils.propagation import maximum_depth_reached -def test_should_propagate_current_less_than_max(): +def test_maximum_depth_reached__current_less_than_max(): maximum_depth = 2 current_depth = 1 - assert should_propagate(maximum_depth, current_depth) is True + assert maximum_depth_reached(maximum_depth, current_depth) is True -def test_should_propagate_current_greater_than_max(): +def test_maximum_depth_reached__current_greater_than_max(): maximum_depth = 2 current_depth = 3 - assert should_propagate(maximum_depth, current_depth) is False + assert maximum_depth_reached(maximum_depth, current_depth) is False -def test_should_propagate_current_equal_to_max(): +def test_maximum_depth_reached__current_equal_to_max(): maximum_depth = 2 current_depth = maximum_depth - assert should_propagate(maximum_depth, current_depth) is False + assert maximum_depth_reached(maximum_depth, current_depth) is False From 6d156b8fee02e924bf9b4299b3f73e093b5922a6 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 23 Jun 2022 16:54:27 -0400 Subject: [PATCH 20/66] Island: Return config timeouts in seconds The old config scheme stored timeouts as milliseconds, whereas the new one uses seconds. Seconds are more convenient because most python methods expecting timeouts are expecting floating-point seconds. --- monkey/infection_monkey/master/automated_master.py | 4 ++-- monkey/monkey_island/cc/services/config.py | 4 ++-- .../tests/unit_tests/monkey_island/cc/services/test_config.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index de55ef904..eeb0b48d7 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -23,8 +23,8 @@ from .option_parsing import custom_pba_is_enabled CHECK_ISLAND_FOR_STOP_COMMAND_INTERVAL_SEC = 5 CHECK_FOR_TERMINATE_INTERVAL_SEC = CHECK_ISLAND_FOR_STOP_COMMAND_INTERVAL_SEC / 5 SHUTDOWN_TIMEOUT = 5 -NUM_SCAN_THREADS = 16 -NUM_EXPLOIT_THREADS = 6 +NUM_SCAN_THREADS = 1 +NUM_EXPLOIT_THREADS = 1 CHECK_FOR_STOP_AGENT_COUNT = 5 CHECK_FOR_CONFIG_COUNT = 3 diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 69db1c2e1..1a8c7d006 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -460,7 +460,7 @@ class ConfigService: formatted_tcp_scan_config = {} - formatted_tcp_scan_config["timeout"] = config[flat_tcp_timeout_field] + formatted_tcp_scan_config["timeout"] = config[flat_tcp_timeout_field] / 1000 ports = ConfigService._union_tcp_and_http_ports( config[flat_tcp_ports_field], config[flat_http_ports_field] @@ -484,7 +484,7 @@ class ConfigService: flat_ping_timeout_field = "ping_scan_timeout" formatted_icmp_scan_config = {} - formatted_icmp_scan_config["timeout"] = config[flat_ping_timeout_field] + formatted_icmp_scan_config["timeout"] = config[flat_ping_timeout_field] / 1000 config.pop(flat_ping_timeout_field, None) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py index 85f3f4823..f170b0865 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_config.py @@ -99,7 +99,7 @@ def test_format_config_for_agent__propagation(): def test_format_config_for_agent__network_scan(): expected_network_scan_config = { "tcp": { - "timeout": 3000, + "timeout": 3.0, "ports": [ 22, 80, @@ -117,7 +117,7 @@ def test_format_config_for_agent__network_scan(): ], }, "icmp": { - "timeout": 1000, + "timeout": 1.0, }, "targets": { "blocked_ips": ["192.168.1.1", "192.168.1.100"], From 2ff2e5f597ece09d5f75a9fe9b4467bf44ac182b Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 24 Jun 2022 08:33:21 +0200 Subject: [PATCH 21/66] Agent: Fix running of payloads --- monkey/infection_monkey/master/automated_master.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index eeb0b48d7..17012620f 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -182,7 +182,7 @@ class AutomatedMaster(IMaster): payload_thread = create_daemon_thread( target=self._run_plugins, name="PayloadThread", - args=(config.payloads.items(), "payload", self._run_payload), + args=(config.payloads, "payload", self._run_payload), ) payload_thread.start() payload_thread.join() @@ -205,8 +205,8 @@ class AutomatedMaster(IMaster): self._telemetry_messenger.send_telemetry(PostBreachTelem(pba_data)) def _run_payload(self, payload: Tuple[str, Dict]): - name = payload[0] - options = payload[1] + name = payload.name + options = payload.options self._puppet.run_payload(name, options, self._stop) From f9445a2c76dbf58b9ad22d2a23a257713d45070c Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 24 Jun 2022 10:18:51 +0200 Subject: [PATCH 22/66] Agent: Use == to compare OperatingSystems enum --- monkey/infection_monkey/model/host.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/model/host.py b/monkey/infection_monkey/model/host.py index 6a1295e58..167bef246 100644 --- a/monkey/infection_monkey/model/host.py +++ b/monkey/infection_monkey/model/host.py @@ -17,7 +17,7 @@ class VictimHost(object): return self.__dict__ def is_windows(self) -> bool: - return OperatingSystems.WINDOWS in self.os["type"] + return OperatingSystems.WINDOWS == self.os["type"] def __hash__(self): return hash(self.ip_addr) From d59dd81f432edf8ba4aa007fc01d3af25194c182 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 24 Jun 2022 10:19:47 +0200 Subject: [PATCH 23/66] Agent: Use OperatingSystems in CachingAgentRepository --- monkey/infection_monkey/exploit/caching_agent_repository.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/exploit/caching_agent_repository.py b/monkey/infection_monkey/exploit/caching_agent_repository.py index 0f86bbd9d..c23aca287 100644 --- a/monkey/infection_monkey/exploit/caching_agent_repository.py +++ b/monkey/infection_monkey/exploit/caching_agent_repository.py @@ -5,6 +5,7 @@ from typing import Mapping import requests +from common import OperatingSystems from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT from . import IAgentRepository @@ -22,13 +23,13 @@ class CachingAgentRepository(IAgentRepository): self._proxies = proxies self._lock = threading.Lock() - def get_agent_binary(self, os: str, architecture: str = None) -> io.BytesIO: + def get_agent_binary(self, os: OperatingSystems, architecture: str = None) -> io.BytesIO: # If multiple calls to get_agent_binary() are made simultaneously before the result of # _download_binary_from_island() is cached, then multiple requests will be sent to the # island. Add a mutex in front of the call to _download_agent_binary_from_island() so # that only one request per OS will be sent to the island. with self._lock: - return io.BytesIO(self._download_binary_from_island(os)) + return io.BytesIO(self._download_binary_from_island(os.value)) @lru_cache(maxsize=None) def _download_binary_from_island(self, os: str) -> bytes: From fb67586a4c27e8616d99a0ad6f4f17dbed6c04d5 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 24 Jun 2022 10:51:58 +0200 Subject: [PATCH 24/66] Agent: Use OperatingSystems.value for urllib.parse.quote --- monkey/infection_monkey/exploit/tools/http_tools.py | 2 +- monkey/infection_monkey/transport/http.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/exploit/tools/http_tools.py b/monkey/infection_monkey/exploit/tools/http_tools.py index 92696a5b7..5b73251d9 100644 --- a/monkey/infection_monkey/exploit/tools/http_tools.py +++ b/monkey/infection_monkey/exploit/tools/http_tools.py @@ -57,6 +57,6 @@ class HTTPTools(object): httpd.start() lock.acquire() return ( - "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(host.os["type"])), + "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(host.os["type"].value)), httpd, ) diff --git a/monkey/infection_monkey/transport/http.py b/monkey/infection_monkey/transport/http.py index 63aaa0b36..7bcbcd87d 100644 --- a/monkey/infection_monkey/transport/http.py +++ b/monkey/infection_monkey/transport/http.py @@ -62,7 +62,7 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler): f.close() def send_head(self): - if self.path != "/" + urllib.parse.quote(self.victim_os): + if self.path != "/" + urllib.parse.quote(self.victim_os.value): self.send_error(500, "") return None, 0, 0 try: From b605f16c4f3983cc52931e265cae7ed688154c56 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 24 Jun 2022 11:11:19 +0200 Subject: [PATCH 25/66] Agent: Use == to compare OperatingSystems enum in Log4Shell --- monkey/infection_monkey/exploit/log4shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/log4shell.py b/monkey/infection_monkey/exploit/log4shell.py index ffbcdd0d6..cab4ed548 100644 --- a/monkey/infection_monkey/exploit/log4shell.py +++ b/monkey/infection_monkey/exploit/log4shell.py @@ -129,7 +129,7 @@ class Log4ShellExploiter(WebRCE): } def _build_java_class(self, exploit_command: str) -> bytes: - if OperatingSystems.LINUX in self.host.os["type"]: + if OperatingSystems.LINUX == self.host.os["type"]: return build_exploit_bytecode(exploit_command, LINUX_EXPLOIT_TEMPLATE_PATH) else: return build_exploit_bytecode(exploit_command, WINDOWS_EXPLOIT_TEMPLATE_PATH) From e1d5d25e9c425521dbcce682629a25f15dc3bf78 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Fri, 24 Jun 2022 11:18:39 +0200 Subject: [PATCH 26/66] Agent: Use OperatingSystem.WINDOWS in Powershell --- monkey/infection_monkey/exploit/powershell.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/powershell.py b/monkey/infection_monkey/exploit/powershell.py index 6ef72963e..c9991b0b4 100644 --- a/monkey/infection_monkey/exploit/powershell.py +++ b/monkey/infection_monkey/exploit/powershell.py @@ -2,6 +2,7 @@ import logging from pathlib import Path, PurePath from typing import List, Optional +from common import OperatingSystems from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions, get_auth_options from infection_monkey.exploit.powershell_utils.credentials import ( @@ -162,7 +163,7 @@ class PowerShellExploiter(HostExploiter): temp_monkey_binary_filepath.unlink() def _create_local_agent_file(self, binary_path): - agent_binary_bytes = self.agent_repository.get_agent_binary("windows") + agent_binary_bytes = self.agent_repository.get_agent_binary(OperatingSystems.WINDOWS) with open(binary_path, "wb") as f: f.write(agent_binary_bytes.getvalue()) From ffd3464d8a6b4589181563deb7404de76c21130d Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 07:22:07 -0400 Subject: [PATCH 27/66] Agent: Move enum to string conversion to _download_binary_from_island() --- .../infection_monkey/exploit/caching_agent_repository.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/exploit/caching_agent_repository.py b/monkey/infection_monkey/exploit/caching_agent_repository.py index c23aca287..5dd8a603c 100644 --- a/monkey/infection_monkey/exploit/caching_agent_repository.py +++ b/monkey/infection_monkey/exploit/caching_agent_repository.py @@ -29,12 +29,13 @@ class CachingAgentRepository(IAgentRepository): # island. Add a mutex in front of the call to _download_agent_binary_from_island() so # that only one request per OS will be sent to the island. with self._lock: - return io.BytesIO(self._download_binary_from_island(os.value)) + return io.BytesIO(self._download_binary_from_island(os)) @lru_cache(maxsize=None) - def _download_binary_from_island(self, os: str) -> bytes: + def _download_binary_from_island(self, os: OperatingSystems) -> bytes: + os_name = os.name.lower() response = requests.get( # noqa: DUO123 - f"{self._island_url}/api/agent-binaries/{os}", + f"{self._island_url}/api/agent-binaries/{os_name}", verify=False, proxies=self._proxies, timeout=MEDIUM_REQUEST_TIMEOUT, From 858eb2302c252a4473e16304197521780f175304 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 07:23:26 -0400 Subject: [PATCH 28/66] Agent: Rename os -> operating_system in caching_agent_repository The variable name "os" conflicts with the name of Python's `os` library. --- .../exploit/caching_agent_repository.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/exploit/caching_agent_repository.py b/monkey/infection_monkey/exploit/caching_agent_repository.py index 5dd8a603c..499e8401c 100644 --- a/monkey/infection_monkey/exploit/caching_agent_repository.py +++ b/monkey/infection_monkey/exploit/caching_agent_repository.py @@ -23,17 +23,20 @@ class CachingAgentRepository(IAgentRepository): self._proxies = proxies self._lock = threading.Lock() - def get_agent_binary(self, os: OperatingSystems, architecture: str = None) -> io.BytesIO: + def get_agent_binary( + self, operating_system: OperatingSystems, architecture: str = None + ) -> io.BytesIO: # If multiple calls to get_agent_binary() are made simultaneously before the result of # _download_binary_from_island() is cached, then multiple requests will be sent to the # island. Add a mutex in front of the call to _download_agent_binary_from_island() so # that only one request per OS will be sent to the island. with self._lock: - return io.BytesIO(self._download_binary_from_island(os)) + return io.BytesIO(self._download_binary_from_island(operating_system)) @lru_cache(maxsize=None) - def _download_binary_from_island(self, os: OperatingSystems) -> bytes: - os_name = os.name.lower() + def _download_binary_from_island(self, operating_system: OperatingSystems) -> bytes: + os_name = operating_system.name.lower() + response = requests.get( # noqa: DUO123 f"{self._island_url}/api/agent-binaries/{os_name}", verify=False, From 2eb1691030324244df6ac56169299d947bdf2588 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 07:34:10 -0400 Subject: [PATCH 29/66] Agent: Use operating_system.value in _download_binary_from_island() --- monkey/infection_monkey/exploit/caching_agent_repository.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/caching_agent_repository.py b/monkey/infection_monkey/exploit/caching_agent_repository.py index 499e8401c..7d3580258 100644 --- a/monkey/infection_monkey/exploit/caching_agent_repository.py +++ b/monkey/infection_monkey/exploit/caching_agent_repository.py @@ -35,7 +35,7 @@ class CachingAgentRepository(IAgentRepository): @lru_cache(maxsize=None) def _download_binary_from_island(self, operating_system: OperatingSystems) -> bytes: - os_name = operating_system.name.lower() + os_name = operating_system.value response = requests.get( # noqa: DUO123 f"{self._island_url}/api/agent-binaries/{os_name}", From a3db4142bf396e1669a924943af0d06adcf6458b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 07:36:58 -0400 Subject: [PATCH 30/66] Common: Add a docstring to OperatingSystems --- monkey/common/operating_systems.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/monkey/common/operating_systems.py b/monkey/common/operating_systems.py index 67f67da81..2ac2f64b3 100644 --- a/monkey/common/operating_systems.py +++ b/monkey/common/operating_systems.py @@ -2,5 +2,12 @@ from enum import Enum class OperatingSystems(Enum): + """ + An Enum representing all supported operating systems + + This Enum represents all operating systems that Infection Monkey supports. The value of each + member is the member's name in all lower-case characters. + """ + LINUX = "linux" WINDOWS = "windows" From 7b4daaa40f14a10c6dd74063cb533ec293e051a8 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 07:40:24 -0400 Subject: [PATCH 31/66] Agent: Change IAgentRepository to ccept OperatingSystems This was missed in d59dd81f and ffd3464d. --- monkey/infection_monkey/exploit/i_agent_repository.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/exploit/i_agent_repository.py b/monkey/infection_monkey/exploit/i_agent_repository.py index d825772a0..a00aac55a 100644 --- a/monkey/infection_monkey/exploit/i_agent_repository.py +++ b/monkey/infection_monkey/exploit/i_agent_repository.py @@ -1,6 +1,8 @@ import abc import io +from common import OperatingSystems + # TODO: The Island also has an IAgentRepository with a totally different interface. At the moment, # the Island and Agent have different needs, but at some point we should unify these. @@ -13,10 +15,12 @@ class IAgentRepository(metaclass=abc.ABCMeta): """ @abc.abstractmethod - def get_agent_binary(self, os: str, architecture: str = None) -> io.BytesIO: + def get_agent_binary( + self, operating_system: OperatingSystems, architecture: str = None + ) -> io.BytesIO: """ Retrieve the appropriate agent binary from the repository. - :param str os: The name of the operating system on which the agent binary will run + :param operating_system: The name of the operating system on which the agent binary will run :param str architecture: Reserved :return: A file-like object for the requested agent binary :rtype: io.BytesIO From 02cca3e12ac921129d5a7bf5f15d5e4437264120 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 07:42:01 -0400 Subject: [PATCH 32/66] Agent: Remove unnecessary type hints from IAgentRepository doctring --- monkey/infection_monkey/exploit/i_agent_repository.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/infection_monkey/exploit/i_agent_repository.py b/monkey/infection_monkey/exploit/i_agent_repository.py index a00aac55a..308cf5418 100644 --- a/monkey/infection_monkey/exploit/i_agent_repository.py +++ b/monkey/infection_monkey/exploit/i_agent_repository.py @@ -21,8 +21,7 @@ class IAgentRepository(metaclass=abc.ABCMeta): """ Retrieve the appropriate agent binary from the repository. :param operating_system: The name of the operating system on which the agent binary will run - :param str architecture: Reserved + :param architecture: Reserved :return: A file-like object for the requested agent binary - :rtype: io.BytesIO """ pass From 7bba7113071675155b0a35256ee033a0c50c6543 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 11:08:29 -0400 Subject: [PATCH 33/66] Agent: Revert scan/exploit thread numer change The number of scan and exploit threads was changed accidentally in 6d156b8f. --- monkey/infection_monkey/master/automated_master.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index 17012620f..e955311b5 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -23,8 +23,8 @@ from .option_parsing import custom_pba_is_enabled CHECK_ISLAND_FOR_STOP_COMMAND_INTERVAL_SEC = 5 CHECK_FOR_TERMINATE_INTERVAL_SEC = CHECK_ISLAND_FOR_STOP_COMMAND_INTERVAL_SEC / 5 SHUTDOWN_TIMEOUT = 5 -NUM_SCAN_THREADS = 1 -NUM_EXPLOIT_THREADS = 1 +NUM_SCAN_THREADS = 16 +NUM_EXPLOIT_THREADS = 6 CHECK_FOR_STOP_AGENT_COUNT = 5 CHECK_FOR_CONFIG_COUNT = 3 From e3cea20cd578e46d29ee049eddaf4ac28f042aff Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 11:42:24 -0400 Subject: [PATCH 34/66] UT: Move test_agent_configuration.py to configuration/ --- .../common/{ => configuration}/test_agent_configuration.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename monkey/tests/unit_tests/common/{ => configuration}/test_agent_configuration.py (100%) diff --git a/monkey/tests/unit_tests/common/test_agent_configuration.py b/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py similarity index 100% rename from monkey/tests/unit_tests/common/test_agent_configuration.py rename to monkey/tests/unit_tests/common/configuration/test_agent_configuration.py From 5c739716a92f2df2e8e1d6aef9f9e1422a7ea527 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 11:44:00 -0400 Subject: [PATCH 35/66] Common: Rename _dict -> dict_ --- monkey/common/configuration/agent_configuration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/common/configuration/agent_configuration.py b/monkey/common/configuration/agent_configuration.py index a636e3d95..36fa24b85 100644 --- a/monkey/common/configuration/agent_configuration.py +++ b/monkey/common/configuration/agent_configuration.py @@ -25,8 +25,8 @@ class AgentConfiguration: propagation: PropagationConfiguration @staticmethod - def from_dict(_dict: dict): - return AgentConfigurationSchema().load(_dict) + def from_dict(dict_: dict): + return AgentConfigurationSchema().load(dict_) class AgentConfigurationSchema(Schema): From 8605fd40ac35b3eddfc148d76bce36f8ab342a53 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 11:50:49 -0400 Subject: [PATCH 36/66] UT: Add a test for AgentConfiguration.from_dict() --- .../common/configuration/test_agent_configuration.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py b/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py index 7ea80cfc5..13cd106a8 100644 --- a/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py +++ b/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py @@ -1,3 +1,5 @@ +import json + from tests.common.example_agent_configuration import ( AGENT_CONFIGURATION, BLOCKED_IPS, @@ -178,3 +180,12 @@ def test_default_agent_configuration(): config = schema.loads(DEFAULT_AGENT_CONFIGURATION_JSON) assert isinstance(config, AgentConfiguration) + + +def test_from_dict(): + schema = AgentConfigurationSchema() + dict_ = json.loads(DEFAULT_AGENT_CONFIGURATION_JSON) + + config = AgentConfiguration.from_dict(dict_) + + assert schema.dump(config) == dict_ From 1f9a056b0be5e735559babecf6459dfbe12aaa9c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 11:51:33 -0400 Subject: [PATCH 37/66] Agent: Add AgentConfiguration.from_json() --- monkey/common/configuration/agent_configuration.py | 4 ++++ .../common/configuration/test_agent_configuration.py | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/monkey/common/configuration/agent_configuration.py b/monkey/common/configuration/agent_configuration.py index 36fa24b85..8ba9e998b 100644 --- a/monkey/common/configuration/agent_configuration.py +++ b/monkey/common/configuration/agent_configuration.py @@ -28,6 +28,10 @@ class AgentConfiguration: def from_dict(dict_: dict): return AgentConfigurationSchema().load(dict_) + @staticmethod + def from_json(config_json: dict): + return AgentConfigurationSchema().loads(config_json) + class AgentConfigurationSchema(Schema): keep_tunnel_open_time = fields.Float() diff --git a/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py b/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py index 13cd106a8..6156a0b69 100644 --- a/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py +++ b/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py @@ -189,3 +189,12 @@ def test_from_dict(): config = AgentConfiguration.from_dict(dict_) assert schema.dump(config) == dict_ + + +def test_from_json(): + schema = AgentConfigurationSchema() + dict_ = json.loads(DEFAULT_AGENT_CONFIGURATION_JSON) + + config = AgentConfiguration.from_json(DEFAULT_AGENT_CONFIGURATION_JSON) + + assert schema.dump(config) == dict_ From 28250daffeb5b4b2cf124012260bd856dea11696 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 12:19:31 -0400 Subject: [PATCH 38/66] Common: Add AgentConfiguration.to_json() --- monkey/common/configuration/agent_configuration.py | 6 ++++++ .../common/configuration/test_agent_configuration.py | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/monkey/common/configuration/agent_configuration.py b/monkey/common/configuration/agent_configuration.py index 8ba9e998b..a1f2eb70c 100644 --- a/monkey/common/configuration/agent_configuration.py +++ b/monkey/common/configuration/agent_configuration.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from typing import List @@ -32,6 +34,10 @@ class AgentConfiguration: def from_json(config_json: dict): return AgentConfigurationSchema().loads(config_json) + @staticmethod + def to_json(config: AgentConfiguration) -> str: + return AgentConfigurationSchema().dumps(config) + class AgentConfigurationSchema(Schema): keep_tunnel_open_time = fields.Float() diff --git a/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py b/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py index 6156a0b69..134a18a91 100644 --- a/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py +++ b/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py @@ -198,3 +198,11 @@ def test_from_json(): config = AgentConfiguration.from_json(DEFAULT_AGENT_CONFIGURATION_JSON) assert schema.dump(config) == dict_ + + +def test_to_json(): + config = AgentConfiguration.from_json(DEFAULT_AGENT_CONFIGURATION_JSON) + + assert json.loads(AgentConfiguration.to_json(config)) == json.loads( + DEFAULT_AGENT_CONFIGURATION_JSON + ) From e4eee6a5eb692255c4ba6e543601c226eb725eac Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 12:23:37 -0400 Subject: [PATCH 39/66] UT: Use from_dict() and from_json() in tests --- .../tests/data_for_tests/monkey_configs/default_config.py | 4 ++-- .../in_memory_agent_configuration_repository.py | 4 ++-- .../common/configuration/test_agent_configuration.py | 6 ++---- .../repository/test_file_agent_configuration_repository.py | 5 ++--- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/monkey/tests/data_for_tests/monkey_configs/default_config.py b/monkey/tests/data_for_tests/monkey_configs/default_config.py index e232d122d..6c0771af0 100644 --- a/monkey/tests/data_for_tests/monkey_configs/default_config.py +++ b/monkey/tests/data_for_tests/monkey_configs/default_config.py @@ -1,4 +1,4 @@ -from common.configuration import AgentConfigurationSchema +from common.configuration import AgentConfiguration flat_config = { "keep_tunnel_open_time": 30, @@ -116,4 +116,4 @@ flat_config = { }, } -DEFAULT_CONFIG = AgentConfigurationSchema().load(flat_config) +DEFAULT_CONFIG = AgentConfiguration.from_dict(flat_config) diff --git a/monkey/tests/monkey_island/in_memory_agent_configuration_repository.py b/monkey/tests/monkey_island/in_memory_agent_configuration_repository.py index e737d645c..b18465568 100644 --- a/monkey/tests/monkey_island/in_memory_agent_configuration_repository.py +++ b/monkey/tests/monkey_island/in_memory_agent_configuration_repository.py @@ -1,12 +1,12 @@ from tests.common.example_agent_configuration import AGENT_CONFIGURATION -from common.configuration.agent_configuration import AgentConfigurationSchema +from common.configuration.agent_configuration import AgentConfiguration from monkey_island.cc.repository import IAgentConfigurationRepository class InMemoryAgentConfigurationRepository(IAgentConfigurationRepository): def __init__(self): - self._configuration = AgentConfigurationSchema().load(AGENT_CONFIGURATION) + self._configuration = AgentConfiguration.from_dict(AGENT_CONFIGURATION) def get_configuration(self): return self._configuration diff --git a/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py b/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py index 134a18a91..23830d619 100644 --- a/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py +++ b/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py @@ -161,7 +161,7 @@ def test_propagation_configuration(): def test_agent_configuration(): schema = AgentConfigurationSchema() - config = schema.load(AGENT_CONFIGURATION) + config = AgentConfiguration.from_dict(AGENT_CONFIGURATION) config_dict = schema.dump(config) assert isinstance(config, AgentConfiguration) @@ -175,9 +175,7 @@ def test_agent_configuration(): def test_default_agent_configuration(): - schema = AgentConfigurationSchema() - - config = schema.loads(DEFAULT_AGENT_CONFIGURATION_JSON) + config = AgentConfiguration.from_json(DEFAULT_AGENT_CONFIGURATION_JSON) assert isinstance(config, AgentConfiguration) diff --git a/monkey/tests/unit_tests/monkey_island/cc/repository/test_file_agent_configuration_repository.py b/monkey/tests/unit_tests/monkey_island/cc/repository/test_file_agent_configuration_repository.py index 4ab111606..21dc4503b 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/repository/test_file_agent_configuration_repository.py +++ b/monkey/tests/unit_tests/monkey_island/cc/repository/test_file_agent_configuration_repository.py @@ -2,7 +2,7 @@ import pytest from tests.common.example_agent_configuration import AGENT_CONFIGURATION from tests.monkey_island import OpenErrorFileRepository, SingleFileRepository -from common.configuration import AgentConfigurationSchema +from common.configuration import AgentConfiguration from monkey_island.cc.repository import FileAgentConfigurationRepository, RetrievalError @@ -12,8 +12,7 @@ def repository(default_agent_configuration): def test_store_agent_config(repository): - schema = AgentConfigurationSchema() - agent_configuration = schema.load(AGENT_CONFIGURATION) + agent_configuration = AgentConfiguration.from_dict(AGENT_CONFIGURATION) repository.store_configuration(agent_configuration) retrieved_agent_configuration = repository.get_configuration() From 6a927266a4c81b89fc90c821590c089077d1a033 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 12:24:07 -0400 Subject: [PATCH 40/66] Island: Use {from,to}_json() in FileAgentConfigurationRepository --- .../cc/repository/file_agent_configuration_repository.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/repository/file_agent_configuration_repository.py b/monkey/monkey_island/cc/repository/file_agent_configuration_repository.py index b63ee817c..312e3921e 100644 --- a/monkey/monkey_island/cc/repository/file_agent_configuration_repository.py +++ b/monkey/monkey_island/cc/repository/file_agent_configuration_repository.py @@ -1,6 +1,6 @@ import io -from common.configuration import AgentConfiguration, AgentConfigurationSchema +from common.configuration import AgentConfiguration from monkey_island.cc import repository from monkey_island.cc.repository import ( IAgentConfigurationRepository, @@ -17,21 +17,20 @@ class FileAgentConfigurationRepository(IAgentConfigurationRepository): ): self._default_agent_configuration = default_agent_configuration self._file_repository = file_repository - self._schema = AgentConfigurationSchema() def get_configuration(self) -> AgentConfiguration: try: with self._file_repository.open_file(AGENT_CONFIGURATION_FILE_NAME) as f: configuration_json = f.read().decode() - return self._schema.loads(configuration_json) + return AgentConfiguration.from_json(configuration_json) except repository.FileNotFoundError: return self._default_agent_configuration except Exception as err: raise RetrievalError(f"Error retrieving the agent configuration: {err}") def store_configuration(self, agent_configuration: AgentConfiguration): - configuration_json = self._schema.dumps(agent_configuration) + configuration_json = AgentConfiguration.to_json(agent_configuration) self._file_repository.save_file( AGENT_CONFIGURATION_FILE_NAME, io.BytesIO(configuration_json.encode()) From a1baaae76a90e9ed59146003d5701dfd27b0535a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 12:29:54 -0400 Subject: [PATCH 41/66] Common: Use from_json() in build_default_agent_configuration() --- monkey/common/configuration/default_agent_configuration.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/monkey/common/configuration/default_agent_configuration.py b/monkey/common/configuration/default_agent_configuration.py index c83169566..f3295a4b6 100644 --- a/monkey/common/configuration/default_agent_configuration.py +++ b/monkey/common/configuration/default_agent_configuration.py @@ -1,4 +1,4 @@ -from . import AgentConfiguration, AgentConfigurationSchema +from . import AgentConfiguration DEFAULT_AGENT_CONFIGURATION_JSON = """{ "keep_tunnel_open_time": 30, @@ -204,5 +204,4 @@ DEFAULT_AGENT_CONFIGURATION_JSON = """{ def build_default_agent_configuration() -> AgentConfiguration: - schema = AgentConfigurationSchema() - return schema.loads(DEFAULT_AGENT_CONFIGURATION_JSON) + return AgentConfiguration.from_json(DEFAULT_AGENT_CONFIGURATION_JSON) From 07d1d9c45afcf0d7793893bbb48c4af1cbfdda1c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 12:33:12 -0400 Subject: [PATCH 42/66] Island: Use {from,to}_json() in resources --- monkey/monkey_island/cc/resources/agent_configuration.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/monkey/monkey_island/cc/resources/agent_configuration.py b/monkey/monkey_island/cc/resources/agent_configuration.py index f0ad73cb8..fd49bd713 100644 --- a/monkey/monkey_island/cc/resources/agent_configuration.py +++ b/monkey/monkey_island/cc/resources/agent_configuration.py @@ -3,7 +3,7 @@ import json import marshmallow from flask import make_response, request -from common.configuration.agent_configuration import AgentConfigurationSchema +from common.configuration.agent_configuration import AgentConfiguration as AgentConfigurationObject from monkey_island.cc.repository import IAgentConfigurationRepository from monkey_island.cc.resources.AbstractResource import AbstractResource from monkey_island.cc.resources.request_authentication import jwt_required @@ -14,19 +14,18 @@ class AgentConfiguration(AbstractResource): def __init__(self, agent_configuration_repository: IAgentConfigurationRepository): self._agent_configuration_repository = agent_configuration_repository - self._schema = AgentConfigurationSchema() @jwt_required def get(self): configuration = self._agent_configuration_repository.get_configuration() - configuration_json = self._schema.dumps(configuration) + configuration_json = AgentConfigurationObject.to_json(configuration) return make_response(configuration_json, 200) @jwt_required def post(self): try: - configuration_object = self._schema.loads(request.data) + configuration_object = AgentConfigurationObject.from_json(request.data) self._agent_configuration_repository.store_configuration(configuration_object) return make_response({}, 200) except (marshmallow.exceptions.ValidationError, json.JSONDecodeError) as err: From 4c47eae70bd8b30063e284b01a297b35bd70d618 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 12:39:47 -0400 Subject: [PATCH 43/66] Common: Encapsulate AgentConfigurationSchema --- monkey/common/configuration/__init__.py | 1 - .../configuration/test_agent_configuration.py | 13 ++++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/monkey/common/configuration/__init__.py b/monkey/common/configuration/__init__.py index 107e5a491..1505f0e68 100644 --- a/monkey/common/configuration/__init__.py +++ b/monkey/common/configuration/__init__.py @@ -1,6 +1,5 @@ from .agent_configuration import ( AgentConfiguration, - AgentConfigurationSchema, ) from .agent_sub_configurations import ( CustomPBAConfiguration, diff --git a/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py b/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py index 23830d619..0de34e4a2 100644 --- a/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py +++ b/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py @@ -25,11 +25,8 @@ from tests.common.example_agent_configuration import ( WINDOWS_FILENAME, ) -from common.configuration import ( - DEFAULT_AGENT_CONFIGURATION_JSON, - AgentConfiguration, - AgentConfigurationSchema, -) +from common.configuration import DEFAULT_AGENT_CONFIGURATION_JSON, AgentConfiguration +from common.configuration.agent_configuration import AgentConfigurationSchema from common.configuration.agent_sub_configuration_schemas import ( CustomPBAConfigurationSchema, ExploitationConfigurationSchema, @@ -159,10 +156,8 @@ def test_propagation_configuration(): def test_agent_configuration(): - schema = AgentConfigurationSchema() - config = AgentConfiguration.from_dict(AGENT_CONFIGURATION) - config_dict = schema.dump(config) + config_json = AgentConfiguration.to_json(config) assert isinstance(config, AgentConfiguration) assert config.keep_tunnel_open_time == 30 @@ -171,7 +166,7 @@ def test_agent_configuration(): assert isinstance(config.credential_collectors[0], PluginConfiguration) assert isinstance(config.payloads[0], PluginConfiguration) assert isinstance(config.propagation, PropagationConfiguration) - assert config_dict == AGENT_CONFIGURATION + assert json.loads(config_json) == AGENT_CONFIGURATION def test_default_agent_configuration(): From ea02bec0b486384155c4a10681a27b3fb1248c36 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 12:42:09 -0400 Subject: [PATCH 44/66] Common: Remove circular dependency in agent_configuration.py --- monkey/common/configuration/agent_configuration.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/monkey/common/configuration/agent_configuration.py b/monkey/common/configuration/agent_configuration.py index a1f2eb70c..8c5b88646 100644 --- a/monkey/common/configuration/agent_configuration.py +++ b/monkey/common/configuration/agent_configuration.py @@ -3,7 +3,7 @@ from __future__ import annotations from dataclasses import dataclass from typing import List -from marshmallow import Schema, fields, post_load +from marshmallow import Schema, fields from .agent_sub_configuration_schemas import ( CustomPBAConfigurationSchema, @@ -28,11 +28,13 @@ class AgentConfiguration: @staticmethod def from_dict(dict_: dict): - return AgentConfigurationSchema().load(dict_) + config_dict = AgentConfigurationSchema().load(dict_) + return AgentConfiguration(**config_dict) @staticmethod def from_json(config_json: dict): - return AgentConfigurationSchema().loads(config_json) + config_dict = AgentConfigurationSchema().loads(config_json) + return AgentConfiguration(**config_dict) @staticmethod def to_json(config: AgentConfiguration) -> str: @@ -46,7 +48,3 @@ class AgentConfigurationSchema(Schema): credential_collectors = fields.List(fields.Nested(PluginConfigurationSchema)) payloads = fields.List(fields.Nested(PluginConfigurationSchema)) propagation = fields.Nested(PropagationConfigurationSchema) - - @post_load - def _make_agent_configuration(self, data, **kwargs): - return AgentConfiguration(**data) From fc9d854c72d7ba64fe721698912e80f02d3e9621 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 12:56:34 -0400 Subject: [PATCH 45/66] Common: Add validation to AgentConfiguration construction --- monkey/common/configuration/agent_configuration.py | 5 +++++ .../common/configuration/test_agent_configuration.py | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/monkey/common/configuration/agent_configuration.py b/monkey/common/configuration/agent_configuration.py index 8c5b88646..71c4bfd49 100644 --- a/monkey/common/configuration/agent_configuration.py +++ b/monkey/common/configuration/agent_configuration.py @@ -26,6 +26,11 @@ class AgentConfiguration: payloads: List[PluginConfiguration] propagation: PropagationConfiguration + def __post_init__(self): + # This will raise an exception if the object is invalid. Calling this in __post__init() + # makes it impossible to construct an invalid object + AgentConfigurationSchema().dump(self) + @staticmethod def from_dict(dict_: dict): config_dict = AgentConfigurationSchema().load(dict_) diff --git a/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py b/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py index 0de34e4a2..d4a285364 100644 --- a/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py +++ b/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py @@ -1,5 +1,6 @@ import json +import pytest from tests.common.example_agent_configuration import ( AGENT_CONFIGURATION, BLOCKED_IPS, @@ -169,6 +170,14 @@ def test_agent_configuration(): assert json.loads(config_json) == AGENT_CONFIGURATION +def test_incorrect_type(): + valid_config = AgentConfiguration.from_dict(AGENT_CONFIGURATION) + with pytest.raises(Exception): + valid_config_dict = valid_config.__dict__ + valid_config_dict["keep_tunnel_open_time"] = "not_a_float" + AgentConfiguration(**valid_config_dict) + + def test_default_agent_configuration(): config = AgentConfiguration.from_json(DEFAULT_AGENT_CONFIGURATION_JSON) From 94524d124c2d23fb243496adb275bcce46c5a1b4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 13:01:56 -0400 Subject: [PATCH 46/66] Common: Add InvalidConfigurationError --- monkey/common/configuration/__init__.py | 4 +--- monkey/common/configuration/agent_configuration.py | 4 ++++ monkey/common/utils/exceptions.py | 2 ++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/monkey/common/configuration/__init__.py b/monkey/common/configuration/__init__.py index 1505f0e68..06ce30b50 100644 --- a/monkey/common/configuration/__init__.py +++ b/monkey/common/configuration/__init__.py @@ -1,6 +1,4 @@ -from .agent_configuration import ( - AgentConfiguration, -) +from .agent_configuration import AgentConfiguration, InvalidConfigurationError from .agent_sub_configurations import ( CustomPBAConfiguration, PluginConfiguration, diff --git a/monkey/common/configuration/agent_configuration.py b/monkey/common/configuration/agent_configuration.py index 71c4bfd49..fe42e303d 100644 --- a/monkey/common/configuration/agent_configuration.py +++ b/monkey/common/configuration/agent_configuration.py @@ -17,6 +17,10 @@ from .agent_sub_configurations import ( ) +class InvalidConfigurationError(Exception): + pass + + @dataclass(frozen=True) class AgentConfiguration: keep_tunnel_open_time: float diff --git a/monkey/common/utils/exceptions.py b/monkey/common/utils/exceptions.py index 5935145e7..31cebca32 100644 --- a/monkey/common/utils/exceptions.py +++ b/monkey/common/utils/exceptions.py @@ -38,5 +38,7 @@ class DomainControllerNameFetchError(FailedExploitationError): """Raise on failed attempt to extract domain controller's name""" +# TODO: This has been replaced by common.configuration.InvalidConfigurationError. Use that error +# instead and remove this one. class InvalidConfigurationError(Exception): """Raise when configuration is invalid""" From dbd0d3e0dda0b64b9f10990922fb06556016eab5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 13:21:39 -0400 Subject: [PATCH 47/66] Common: Encapsulate MarshmallowError --- .../configuration/agent_configuration.py | 25 +++++++++++++++---- .../cc/resources/agent_configuration.py | 4 +-- .../configuration/test_agent_configuration.py | 24 ++++++++++++++++-- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/monkey/common/configuration/agent_configuration.py b/monkey/common/configuration/agent_configuration.py index fe42e303d..87ccce3b0 100644 --- a/monkey/common/configuration/agent_configuration.py +++ b/monkey/common/configuration/agent_configuration.py @@ -4,6 +4,7 @@ from dataclasses import dataclass from typing import List from marshmallow import Schema, fields +from marshmallow.exceptions import MarshmallowError from .agent_sub_configuration_schemas import ( CustomPBAConfigurationSchema, @@ -21,6 +22,11 @@ class InvalidConfigurationError(Exception): pass +INVALID_CONFIGURATION_ERROR_MESSAGE = ( + "Cannot construct an AgentConfiguration object with the supplied, invalid data:" +) + + @dataclass(frozen=True) class AgentConfiguration: keep_tunnel_open_time: float @@ -33,17 +39,26 @@ class AgentConfiguration: def __post_init__(self): # This will raise an exception if the object is invalid. Calling this in __post__init() # makes it impossible to construct an invalid object - AgentConfigurationSchema().dump(self) + try: + AgentConfigurationSchema().dump(self) + except Exception as err: + raise InvalidConfigurationError(f"{INVALID_CONFIGURATION_ERROR_MESSAGE}: {err}") @staticmethod def from_dict(dict_: dict): - config_dict = AgentConfigurationSchema().load(dict_) - return AgentConfiguration(**config_dict) + try: + config_dict = AgentConfigurationSchema().load(dict_) + return AgentConfiguration(**config_dict) + except MarshmallowError as err: + raise InvalidConfigurationError(f"{INVALID_CONFIGURATION_ERROR_MESSAGE}: {err}") @staticmethod def from_json(config_json: dict): - config_dict = AgentConfigurationSchema().loads(config_json) - return AgentConfiguration(**config_dict) + try: + config_dict = AgentConfigurationSchema().loads(config_json) + return AgentConfiguration(**config_dict) + except MarshmallowError as err: + raise InvalidConfigurationError(f"{INVALID_CONFIGURATION_ERROR_MESSAGE}: {err}") @staticmethod def to_json(config: AgentConfiguration) -> str: diff --git a/monkey/monkey_island/cc/resources/agent_configuration.py b/monkey/monkey_island/cc/resources/agent_configuration.py index fd49bd713..1bf311564 100644 --- a/monkey/monkey_island/cc/resources/agent_configuration.py +++ b/monkey/monkey_island/cc/resources/agent_configuration.py @@ -1,9 +1,9 @@ import json -import marshmallow from flask import make_response, request from common.configuration.agent_configuration import AgentConfiguration as AgentConfigurationObject +from common.configuration.agent_configuration import InvalidConfigurationError from monkey_island.cc.repository import IAgentConfigurationRepository from monkey_island.cc.resources.AbstractResource import AbstractResource from monkey_island.cc.resources.request_authentication import jwt_required @@ -28,7 +28,7 @@ class AgentConfiguration(AbstractResource): configuration_object = AgentConfigurationObject.from_json(request.data) self._agent_configuration_repository.store_configuration(configuration_object) return make_response({}, 200) - except (marshmallow.exceptions.ValidationError, json.JSONDecodeError) as err: + except (InvalidConfigurationError, json.JSONDecodeError) as err: return make_response( {"message": f"Invalid configuration supplied: {err}"}, 400, diff --git a/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py b/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py index d4a285364..f8c8f4c38 100644 --- a/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py +++ b/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py @@ -26,7 +26,11 @@ from tests.common.example_agent_configuration import ( WINDOWS_FILENAME, ) -from common.configuration import DEFAULT_AGENT_CONFIGURATION_JSON, AgentConfiguration +from common.configuration import ( + DEFAULT_AGENT_CONFIGURATION_JSON, + AgentConfiguration, + InvalidConfigurationError, +) from common.configuration.agent_configuration import AgentConfigurationSchema from common.configuration.agent_sub_configuration_schemas import ( CustomPBAConfigurationSchema, @@ -172,7 +176,7 @@ def test_agent_configuration(): def test_incorrect_type(): valid_config = AgentConfiguration.from_dict(AGENT_CONFIGURATION) - with pytest.raises(Exception): + with pytest.raises(InvalidConfigurationError): valid_config_dict = valid_config.__dict__ valid_config_dict["keep_tunnel_open_time"] = "not_a_float" AgentConfiguration(**valid_config_dict) @@ -193,6 +197,14 @@ def test_from_dict(): assert schema.dump(config) == dict_ +def test_from_dict__invalid_data(): + dict_ = json.loads(DEFAULT_AGENT_CONFIGURATION_JSON) + dict_["payloads"] = "payloads" + + with pytest.raises(InvalidConfigurationError): + AgentConfiguration.from_dict(dict_) + + def test_from_json(): schema = AgentConfigurationSchema() dict_ = json.loads(DEFAULT_AGENT_CONFIGURATION_JSON) @@ -202,6 +214,14 @@ def test_from_json(): assert schema.dump(config) == dict_ +def test_from_json__invalid_data(): + invalid_dict = json.loads(DEFAULT_AGENT_CONFIGURATION_JSON) + invalid_dict["payloads"] = "payloads" + + with pytest.raises(InvalidConfigurationError): + AgentConfiguration.from_json(json.dumps(invalid_dict)) + + def test_to_json(): config = AgentConfiguration.from_json(DEFAULT_AGENT_CONFIGURATION_JSON) From e2f365a1f9c8031b505577d9c15f4508c9397f72 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 13:22:41 -0400 Subject: [PATCH 48/66] Common: Rename dict_ -> config_dict --- monkey/common/configuration/agent_configuration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/common/configuration/agent_configuration.py b/monkey/common/configuration/agent_configuration.py index 87ccce3b0..e3d039ecc 100644 --- a/monkey/common/configuration/agent_configuration.py +++ b/monkey/common/configuration/agent_configuration.py @@ -45,9 +45,9 @@ class AgentConfiguration: raise InvalidConfigurationError(f"{INVALID_CONFIGURATION_ERROR_MESSAGE}: {err}") @staticmethod - def from_dict(dict_: dict): + def from_dict(config_dict: dict): try: - config_dict = AgentConfigurationSchema().load(dict_) + config_dict = AgentConfigurationSchema().load(config_dict) return AgentConfiguration(**config_dict) except MarshmallowError as err: raise InvalidConfigurationError(f"{INVALID_CONFIGURATION_ERROR_MESSAGE}: {err}") From 334d2a790f5fe9e3475005d1c8213d6b9afc7bec Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 13:24:02 -0400 Subject: [PATCH 49/66] Common: Rename from_dict() -> from_mapping() --- monkey/common/configuration/agent_configuration.py | 6 +++--- monkey/infection_monkey/master/control_channel.py | 2 +- .../tests/data_for_tests/monkey_configs/default_config.py | 2 +- .../in_memory_agent_configuration_repository.py | 2 +- .../common/configuration/test_agent_configuration.py | 8 ++++---- .../test_file_agent_configuration_repository.py | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/monkey/common/configuration/agent_configuration.py b/monkey/common/configuration/agent_configuration.py index e3d039ecc..72b26d2f0 100644 --- a/monkey/common/configuration/agent_configuration.py +++ b/monkey/common/configuration/agent_configuration.py @@ -1,7 +1,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import List +from typing import Any, List, Mapping from marshmallow import Schema, fields from marshmallow.exceptions import MarshmallowError @@ -45,9 +45,9 @@ class AgentConfiguration: raise InvalidConfigurationError(f"{INVALID_CONFIGURATION_ERROR_MESSAGE}: {err}") @staticmethod - def from_dict(config_dict: dict): + def from_mapping(config_mapping: Mapping[str, Any]) -> AgentConfiguration: try: - config_dict = AgentConfigurationSchema().load(config_dict) + config_dict = AgentConfigurationSchema().load(config_mapping) return AgentConfiguration(**config_dict) except MarshmallowError as err: raise InvalidConfigurationError(f"{INVALID_CONFIGURATION_ERROR_MESSAGE}: {err}") diff --git a/monkey/infection_monkey/master/control_channel.py b/monkey/infection_monkey/master/control_channel.py index 8567a301f..c83942f5d 100644 --- a/monkey/infection_monkey/master/control_channel.py +++ b/monkey/infection_monkey/master/control_channel.py @@ -58,7 +58,7 @@ class ControlChannel(IControlChannel): ) response.raise_for_status() - return AgentConfiguration.from_dict(json.loads(response.text)["config"]) + return AgentConfiguration.from_mapping(json.loads(response.text)["config"]) except ( json.JSONDecodeError, requests.exceptions.ConnectionError, diff --git a/monkey/tests/data_for_tests/monkey_configs/default_config.py b/monkey/tests/data_for_tests/monkey_configs/default_config.py index 6c0771af0..c0eca7c43 100644 --- a/monkey/tests/data_for_tests/monkey_configs/default_config.py +++ b/monkey/tests/data_for_tests/monkey_configs/default_config.py @@ -116,4 +116,4 @@ flat_config = { }, } -DEFAULT_CONFIG = AgentConfiguration.from_dict(flat_config) +DEFAULT_CONFIG = AgentConfiguration.from_mapping(flat_config) diff --git a/monkey/tests/monkey_island/in_memory_agent_configuration_repository.py b/monkey/tests/monkey_island/in_memory_agent_configuration_repository.py index b18465568..e9bcbae62 100644 --- a/monkey/tests/monkey_island/in_memory_agent_configuration_repository.py +++ b/monkey/tests/monkey_island/in_memory_agent_configuration_repository.py @@ -6,7 +6,7 @@ from monkey_island.cc.repository import IAgentConfigurationRepository class InMemoryAgentConfigurationRepository(IAgentConfigurationRepository): def __init__(self): - self._configuration = AgentConfiguration.from_dict(AGENT_CONFIGURATION) + self._configuration = AgentConfiguration.from_mapping(AGENT_CONFIGURATION) def get_configuration(self): return self._configuration diff --git a/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py b/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py index f8c8f4c38..4b264c8cb 100644 --- a/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py +++ b/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py @@ -161,7 +161,7 @@ def test_propagation_configuration(): def test_agent_configuration(): - config = AgentConfiguration.from_dict(AGENT_CONFIGURATION) + config = AgentConfiguration.from_mapping(AGENT_CONFIGURATION) config_json = AgentConfiguration.to_json(config) assert isinstance(config, AgentConfiguration) @@ -175,7 +175,7 @@ def test_agent_configuration(): def test_incorrect_type(): - valid_config = AgentConfiguration.from_dict(AGENT_CONFIGURATION) + valid_config = AgentConfiguration.from_mapping(AGENT_CONFIGURATION) with pytest.raises(InvalidConfigurationError): valid_config_dict = valid_config.__dict__ valid_config_dict["keep_tunnel_open_time"] = "not_a_float" @@ -192,7 +192,7 @@ def test_from_dict(): schema = AgentConfigurationSchema() dict_ = json.loads(DEFAULT_AGENT_CONFIGURATION_JSON) - config = AgentConfiguration.from_dict(dict_) + config = AgentConfiguration.from_mapping(dict_) assert schema.dump(config) == dict_ @@ -202,7 +202,7 @@ def test_from_dict__invalid_data(): dict_["payloads"] = "payloads" with pytest.raises(InvalidConfigurationError): - AgentConfiguration.from_dict(dict_) + AgentConfiguration.from_mapping(dict_) def test_from_json(): diff --git a/monkey/tests/unit_tests/monkey_island/cc/repository/test_file_agent_configuration_repository.py b/monkey/tests/unit_tests/monkey_island/cc/repository/test_file_agent_configuration_repository.py index 21dc4503b..fb7863dc3 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/repository/test_file_agent_configuration_repository.py +++ b/monkey/tests/unit_tests/monkey_island/cc/repository/test_file_agent_configuration_repository.py @@ -12,7 +12,7 @@ def repository(default_agent_configuration): def test_store_agent_config(repository): - agent_configuration = AgentConfiguration.from_dict(AGENT_CONFIGURATION) + agent_configuration = AgentConfiguration.from_mapping(AGENT_CONFIGURATION) repository.store_configuration(agent_configuration) retrieved_agent_configuration = repository.get_configuration() From 8cb045d63589a98b7b701f697726601c1c2d2be6 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 13:25:09 -0400 Subject: [PATCH 50/66] Common: Fix incorrect type hints on AgentConfiguration.from_json() --- monkey/common/configuration/agent_configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/common/configuration/agent_configuration.py b/monkey/common/configuration/agent_configuration.py index 72b26d2f0..b7dcb90c5 100644 --- a/monkey/common/configuration/agent_configuration.py +++ b/monkey/common/configuration/agent_configuration.py @@ -53,7 +53,7 @@ class AgentConfiguration: raise InvalidConfigurationError(f"{INVALID_CONFIGURATION_ERROR_MESSAGE}: {err}") @staticmethod - def from_json(config_json: dict): + def from_json(config_json: str) -> AgentConfiguration: try: config_dict = AgentConfigurationSchema().loads(config_json) return AgentConfiguration(**config_dict) From 568eb4ff3bd6de0663fed81a93f2d997caf6d3b4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 13:30:43 -0400 Subject: [PATCH 51/66] Common: Add docstrings to static methods in AgentConfiguration --- .../configuration/agent_configuration.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/monkey/common/configuration/agent_configuration.py b/monkey/common/configuration/agent_configuration.py index b7dcb90c5..097b86382 100644 --- a/monkey/common/configuration/agent_configuration.py +++ b/monkey/common/configuration/agent_configuration.py @@ -46,6 +46,15 @@ class AgentConfiguration: @staticmethod def from_mapping(config_mapping: Mapping[str, Any]) -> AgentConfiguration: + """ + Construct an AgentConfiguration from a Mapping + + :param config_mapping: A Mapping that represents an AgentConfiguration + :return: An AgentConfiguration + :raises: InvalidConfigurationError if the provided Mapping does not represent a valid + AgentConfiguration + """ + try: config_dict = AgentConfigurationSchema().load(config_mapping) return AgentConfiguration(**config_dict) @@ -54,6 +63,14 @@ class AgentConfiguration: @staticmethod def from_json(config_json: str) -> AgentConfiguration: + """ + Construct an AgentConfiguration from a JSON string + + :param config_json: A JSON string that represents an AgentConfiguration + :return: An AgentConfiguration + :raises: InvalidConfigurationError if the provided JSON does not represent a valid + AgentConfiguration + """ try: config_dict = AgentConfigurationSchema().loads(config_json) return AgentConfiguration(**config_dict) @@ -62,6 +79,12 @@ class AgentConfiguration: @staticmethod def to_json(config: AgentConfiguration) -> str: + """ + Serialize an AgentConfiguration to JSON + + :param config: An AgentConfiguration + :return: A JSON string representing the AgentConfiguration + """ return AgentConfigurationSchema().dumps(config) From 84fc78cbf84f115dcb1de9cd85eb465d4268f6ae Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 14:47:15 -0400 Subject: [PATCH 52/66] UT: Remove unused imports from conftest.py --- monkey/tests/unit_tests/conftest.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/tests/unit_tests/conftest.py b/monkey/tests/unit_tests/conftest.py index c54ccce23..3634e52b9 100644 --- a/monkey/tests/unit_tests/conftest.py +++ b/monkey/tests/unit_tests/conftest.py @@ -5,9 +5,6 @@ from typing import Callable, Dict import pytest from _pytest.monkeypatch import MonkeyPatch -from tests.data_for_tests.monkey_configs.default_config import DEFAULT_CONFIG - -from common.configuration import AgentConfiguration MONKEY_BASE_PATH = str(Path(__file__).parent.parent.parent) sys.path.insert(0, MONKEY_BASE_PATH) From b219ca09172ca6833ade893a574384085f587b01 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 14:47:55 -0400 Subject: [PATCH 53/66] UT: Fix line that was too long --- .../unit_tests/infection_monkey/master/test_propagator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 847d92bcd..2ebdcd84e 100644 --- a/monkey/tests/unit_tests/infection_monkey/master/test_propagator.py +++ b/monkey/tests/unit_tests/infection_monkey/master/test_propagator.py @@ -140,7 +140,9 @@ class StubExploiter: pass -def get_propagation_config(default_agent_configuration, scan_target_config: ScanTargetConfiguration): +def get_propagation_config( + default_agent_configuration, scan_target_config: ScanTargetConfiguration +): network_scan = NetworkScanConfiguration( default_agent_configuration.propagation.network_scan.tcp, default_agent_configuration.propagation.network_scan.icmp, From 33ec4f7ae963a43c8e977f9bf7729a09a17a24ab Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 15:16:43 -0400 Subject: [PATCH 54/66] Agent: Log configuration when it's received from the Island --- monkey/infection_monkey/master/control_channel.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/master/control_channel.py b/monkey/infection_monkey/master/control_channel.py index c83942f5d..d68f42bda 100644 --- a/monkey/infection_monkey/master/control_channel.py +++ b/monkey/infection_monkey/master/control_channel.py @@ -1,5 +1,6 @@ import json import logging +from pprint import pformat from typing import Mapping import requests @@ -58,7 +59,10 @@ class ControlChannel(IControlChannel): ) response.raise_for_status() - return AgentConfiguration.from_mapping(json.loads(response.text)["config"]) + config_dict = json.loads(response.text)["config"] + logger.debug(f"Received configuration:\n{pformat(json.loads(response.text))}") + + return AgentConfiguration.from_mapping(config_dict) except ( json.JSONDecodeError, requests.exceptions.ConnectionError, From dc9b91d43096a13c5ae11af8b7d9f6cf02dad02c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 15:32:06 -0400 Subject: [PATCH 55/66] Agent: Use Iterable instead of List in type hint --- monkey/infection_monkey/master/automated_master.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index e955311b5..c347e8627 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -1,7 +1,7 @@ import logging import threading import time -from typing import Any, Callable, Dict, List, Optional, Tuple +from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple from common.configuration import PluginConfiguration from common.utils import Timer @@ -212,7 +212,7 @@ class AutomatedMaster(IMaster): def _run_pbas( self, - plugins: List[PluginConfiguration], + plugins: Iterable[PluginConfiguration], callback: Callable[[Any], None], custom_pba_options: Dict, ): @@ -226,7 +226,10 @@ class AutomatedMaster(IMaster): ) def _run_plugins( - self, plugins: List[PluginConfiguration], plugin_type: str, callback: Callable[[Any], None] + self, + plugins: Iterable[PluginConfiguration], + plugin_type: str, + callback: Callable[[Any], None], ): logger.info(f"Running {plugin_type}s") logger.debug(f"Found {len(plugins)} {plugin_type}(s) to run") From 8886ebc8b8311408ba73eb8a7a9697399d170ddb Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 15:33:47 -0400 Subject: [PATCH 56/66] Agent: Remove unnecessary local variables --- monkey/infection_monkey/master/automated_master.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index c347e8627..f3624d03d 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -198,10 +198,7 @@ class AutomatedMaster(IMaster): logger.debug(f"No credentials were collected by {collector}") def _run_pba(self, pba: PluginConfiguration): - name = pba.name - options = pba.options - - for pba_data in self._puppet.run_pba(name, options): + for pba_data in self._puppet.run_pba(pba.name, pba.options): self._telemetry_messenger.send_telemetry(PostBreachTelem(pba_data)) def _run_payload(self, payload: Tuple[str, Dict]): From 4f7d8be6ba11bbe204c09ade56dd15b2a4e58dc4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 15:35:41 -0400 Subject: [PATCH 57/66] Agent: Use PluginConfiguration in _run_payload() --- monkey/infection_monkey/master/automated_master.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index f3624d03d..003c9873c 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -1,7 +1,7 @@ import logging import threading import time -from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple +from typing import Any, Callable, Dict, Iterable, List, Optional from common.configuration import PluginConfiguration from common.utils import Timer @@ -201,11 +201,8 @@ class AutomatedMaster(IMaster): for pba_data in self._puppet.run_pba(pba.name, pba.options): self._telemetry_messenger.send_telemetry(PostBreachTelem(pba_data)) - def _run_payload(self, payload: Tuple[str, Dict]): - name = payload.name - options = payload.options - - self._puppet.run_payload(name, options, self._stop) + def _run_payload(self, payload: PluginConfiguration): + self._puppet.run_payload(payload.name, payload.options, self._stop) def _run_pbas( self, From 503a0a833f8dd652587cceceea0d28201b9c7f3c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 19:19:51 -0400 Subject: [PATCH 58/66] Agent: Use Sequence instead of List for type hints --- monkey/infection_monkey/master/exploiter.py | 8 ++++---- monkey/infection_monkey/master/ip_scanner.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/monkey/infection_monkey/master/exploiter.py b/monkey/infection_monkey/master/exploiter.py index 60910226d..53665da38 100644 --- a/monkey/infection_monkey/master/exploiter.py +++ b/monkey/infection_monkey/master/exploiter.py @@ -5,7 +5,7 @@ from copy import deepcopy from itertools import chain from queue import Queue from threading import Event -from typing import Callable, Dict, List +from typing import Callable, Dict, Sequence from common import OperatingSystems from common.configuration.agent_sub_configurations import ( @@ -81,7 +81,7 @@ class Exploiter: @staticmethod def _process_exploiter_config( exploiter_config: ExploitationConfiguration, - ) -> List[ExploiterConfiguration]: + ) -> Sequence[ExploiterConfiguration]: # Run vulnerability exploiters before brute force exploiters to minimize the effect of # account lockout due to invalid credentials ordered_exploiters = chain(exploiter_config.vulnerability, exploiter_config.brute_force) @@ -98,7 +98,7 @@ class Exploiter: def _exploit_hosts_on_queue( self, - exploiters_to_run: List[ExploiterConfiguration], + exploiters_to_run: Sequence[ExploiterConfiguration], hosts_to_exploit: Queue, current_depth: int, results_callback: Callback, @@ -125,7 +125,7 @@ class Exploiter: def _run_all_exploiters( self, - exploiters_to_run: List[ExploiterConfiguration], + exploiters_to_run: Sequence[ExploiterConfiguration], victim_host: VictimHost, current_depth: int, results_callback: Callback, diff --git a/monkey/infection_monkey/master/ip_scanner.py b/monkey/infection_monkey/master/ip_scanner.py index 14391765f..071abe95a 100644 --- a/monkey/infection_monkey/master/ip_scanner.py +++ b/monkey/infection_monkey/master/ip_scanner.py @@ -3,7 +3,7 @@ import queue import threading from queue import Queue from threading import Event -from typing import Callable, Dict, List +from typing import Callable, Dict, Sequence from common.configuration.agent_sub_configurations import ( NetworkScanConfiguration, @@ -34,7 +34,7 @@ class IPScanner: def scan( self, - addresses_to_scan: List[NetworkAddress], + addresses_to_scan: Sequence[NetworkAddress], options: ScanTargetConfiguration, results_callback: Callback, stop: Event, @@ -99,7 +99,7 @@ class IPScanner: def _run_fingerprinters( self, ip: str, - fingerprinters: List[PluginConfiguration], + fingerprinters: Sequence[PluginConfiguration], ping_scan_data: PingScanData, port_scan_data: Dict[int, PortScanData], stop: Event, From fefd2daf2bb94fd48e2d9701433a4213eb5d3a9f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Fri, 24 Jun 2022 19:21:48 -0400 Subject: [PATCH 59/66] Agent: Use Mapping instead of Dict --- monkey/infection_monkey/master/automated_master.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index 003c9873c..9333554b7 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -1,7 +1,7 @@ import logging import threading import time -from typing import Any, Callable, Dict, Iterable, List, Optional +from typing import Any, Callable, Iterable, List, Mapping, Optional from common.configuration import PluginConfiguration from common.utils import Timer @@ -208,7 +208,7 @@ class AutomatedMaster(IMaster): self, plugins: Iterable[PluginConfiguration], callback: Callable[[Any], None], - custom_pba_options: Dict, + custom_pba_options: Mapping, ): self._run_plugins(plugins, "post-breach action", callback) From 4b7ab058c6b69e941c44bb4881f809cc57f577be Mon Sep 17 00:00:00 2001 From: vakarisz Date: Mon, 27 Jun 2022 09:36:56 +0300 Subject: [PATCH 60/66] Agent: Fix typehints in _run_pbas of automated_master.py Typehint was Mapping, when it was using and calling other methods with CustomPBAConfiguration --- monkey/infection_monkey/master/automated_master.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/master/automated_master.py b/monkey/infection_monkey/master/automated_master.py index 9333554b7..ad73b50f4 100644 --- a/monkey/infection_monkey/master/automated_master.py +++ b/monkey/infection_monkey/master/automated_master.py @@ -1,9 +1,9 @@ import logging import threading import time -from typing import Any, Callable, Iterable, List, Mapping, Optional +from typing import Any, Callable, Iterable, List, Optional -from common.configuration import PluginConfiguration +from common.configuration import CustomPBAConfiguration, PluginConfiguration from common.utils import Timer from infection_monkey.credential_store import ICredentialsStore from infection_monkey.i_control_channel import IControlChannel, IslandCommunicationError @@ -208,13 +208,13 @@ class AutomatedMaster(IMaster): self, plugins: Iterable[PluginConfiguration], callback: Callable[[Any], None], - custom_pba_options: Mapping, + custom_pba_options: CustomPBAConfiguration, ): self._run_plugins(plugins, "post-breach action", callback) if custom_pba_is_enabled(custom_pba_options): self._run_plugins( - [PluginConfiguration(name="CustomPBA", options=custom_pba_options)], + [PluginConfiguration(name="CustomPBA", options=custom_pba_options.__dict__)], "post-breach action", callback, ) From 7179f9128c16c535af252ba834aedf5312445b76 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Mon, 27 Jun 2022 10:01:15 +0300 Subject: [PATCH 61/66] Agent: Fix typehints in clear_command_history.py --- .../post_breach/actions/clear_command_history.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/clear_command_history.py b/monkey/infection_monkey/post_breach/actions/clear_command_history.py index 2641051cc..7a5a350f5 100644 --- a/monkey/infection_monkey/post_breach/actions/clear_command_history.py +++ b/monkey/infection_monkey/post_breach/actions/clear_command_history.py @@ -16,7 +16,7 @@ class ClearCommandHistory(PBA): super().__init__(telemetry_messenger, name=POST_BREACH_CLEAR_CMD_HISTORY) def run(self, options: Dict) -> Iterable[PostBreachData]: - results = [pba.run() for pba in self.clear_command_history_pba_list()] + results = [pba.run(options) for pba in self.clear_command_history_pba_list()] if results: # `self.command` is empty here self.pba_data.append(PostBreachData(self.name, self.command, results)) @@ -53,7 +53,7 @@ class ClearCommandHistory(PBA): linux_cmd=linux_cmds, ) - def run(self) -> Tuple[str, bool]: + def run(self, options: Dict) -> Tuple[str, bool]: if self.command: try: output = subprocess.check_output( # noqa: DUO116 From c080f030117280d9a55a0937bcdf617fe0b75a51 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Mon, 27 Jun 2022 10:02:45 +0300 Subject: [PATCH 62/66] Agent: Fix _filter_none_values to be a static method --- monkey/monkey_island/cc/services/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 1a8c7d006..6d14505c3 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -179,6 +179,7 @@ class ConfigService: should_encrypt=True, ) + @staticmethod def _filter_none_values(data): if isinstance(data, dict): return { From 232d6ba344b6e33225353386accdf05b10ca69c7 Mon Sep 17 00:00:00 2001 From: vakarisz Date: Mon, 27 Jun 2022 11:20:27 +0300 Subject: [PATCH 63/66] Agent: Fix string formatting in http_tools.py Move line 60 to f formatting from the old %s style --- monkey/infection_monkey/exploit/tools/http_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/tools/http_tools.py b/monkey/infection_monkey/exploit/tools/http_tools.py index 5b73251d9..b27f6cf6f 100644 --- a/monkey/infection_monkey/exploit/tools/http_tools.py +++ b/monkey/infection_monkey/exploit/tools/http_tools.py @@ -57,6 +57,6 @@ class HTTPTools(object): httpd.start() lock.acquire() return ( - "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(host.os["type"].value)), + f"http://{local_ip}:{local_port}/{urllib.parse.quote(host.os['type'].value)}", httpd, ) From bf1d360e50e9a9c9a3f0dbc3afa3a4bf4f402945 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 27 Jun 2022 06:55:39 -0400 Subject: [PATCH 64/66] UT: Remove disused DEFAULT_CONFIG --- .../monkey_configs/default_config.py | 119 ------------------ 1 file changed, 119 deletions(-) delete mode 100644 monkey/tests/data_for_tests/monkey_configs/default_config.py diff --git a/monkey/tests/data_for_tests/monkey_configs/default_config.py b/monkey/tests/data_for_tests/monkey_configs/default_config.py deleted file mode 100644 index c0eca7c43..000000000 --- a/monkey/tests/data_for_tests/monkey_configs/default_config.py +++ /dev/null @@ -1,119 +0,0 @@ -from common.configuration import AgentConfiguration - -flat_config = { - "keep_tunnel_open_time": 30, - "post_breach_actions": [ - {"name": "CommunicateAsBackdoorUser", "options": {}}, - {"name": "ModifyShellStartupFiles", "options": {}}, - {"name": "HiddenFiles", "options": {}}, - {"name": "TrapCommand", "options": {}}, - {"name": "ChangeSetuidSetgid", "options": {}}, - {"name": "ScheduleJobs", "options": {}}, - {"name": "Timestomping", "options": {}}, - {"name": "AccountDiscovery", "options": {}}, - {"name": "ProcessListCollection", "options": {}}, - ], - "credential_collectors": [ - {"name": "MimikatzCollector", "options": {}}, - {"name": "SSHCollector", "options": {}}, - ], - "payloads": [ - { - "name": "ransomware", - "options": { - "encryption": { - "enabled": True, - "directories": {"linux_target_dir": "", "windows_target_dir": ""}, - }, - "other_behaviors": {"readme": True}, - }, - } - ], - "custom_pbas": { - "linux_command": "", - "linux_filename": "", - "windows_command": "", - "windows_filename": "", - }, - "propagation": { - "network_scan": { - "tcp": { - "timeout": 3000, - "ports": [ - 22, - 80, - 135, - 443, - 445, - 2222, - 3306, - 3389, - 5985, - 5986, - 7001, - 8008, - 8080, - 8088, - 8983, - 9200, - 9600, - ], - }, - "icmp": {"timeout": 1000}, - "fingerprinters": [ - {"name": "elastic", "options": {}}, - { - "name": "http", - "options": {"http_ports": [80, 443, 7001, 8008, 8080, 8983, 9200, 9600]}, - }, - {"name": "mssql", "options": {}}, - {"name": "smb", "options": {}}, - {"name": "ssh", "options": {}}, - ], - "targets": { - "blocked_ips": [], - "inaccessible_subnets": [], - "local_network_scan": True, - "subnets": [], - }, - }, - "maximum_depth": 2, - "exploitation": { - "options": {"http_ports": [80, 443, 7001, 8008, 8080, 8983, 9200, 9600]}, - "brute_force": [ - { - "name": "MSSQLExploiter", - "options": {}, - }, - { - "name": "PowerShellExploiter", - "options": {}, - }, - { - "name": "SSHExploiter", - "options": {}, - }, - { - "name": "SmbExploiter", - "options": {"smb_download_timeout": 30}, - }, - { - "name": "WmiExploiter", - "options": {"smb_download_timeout": 30}, - }, - ], - "vulnerability": [ - { - "name": "HadoopExploiter", - "options": {}, - }, - { - "name": "Log4ShellExploiter", - "options": {}, - }, - ], - }, - }, -} - -DEFAULT_CONFIG = AgentConfiguration.from_mapping(flat_config) From 90259c1b7a8264bf107199240592d167793ad533 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 27 Jun 2022 07:07:53 -0400 Subject: [PATCH 65/66] UT: Remove dependency on DEFAULT_AGENT_CONFIGURATION_JSON --- .../configuration/test_agent_configuration.py | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py b/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py index 4b264c8cb..e06a4cf3e 100644 --- a/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py +++ b/monkey/tests/unit_tests/common/configuration/test_agent_configuration.py @@ -1,4 +1,5 @@ import json +from copy import deepcopy import pytest from tests.common.example_agent_configuration import ( @@ -26,11 +27,7 @@ from tests.common.example_agent_configuration import ( WINDOWS_FILENAME, ) -from common.configuration import ( - DEFAULT_AGENT_CONFIGURATION_JSON, - AgentConfiguration, - InvalidConfigurationError, -) +from common.configuration import AgentConfiguration, InvalidConfigurationError from common.configuration.agent_configuration import AgentConfigurationSchema from common.configuration.agent_sub_configuration_schemas import ( CustomPBAConfigurationSchema, @@ -182,15 +179,9 @@ def test_incorrect_type(): AgentConfiguration(**valid_config_dict) -def test_default_agent_configuration(): - config = AgentConfiguration.from_json(DEFAULT_AGENT_CONFIGURATION_JSON) - - assert isinstance(config, AgentConfiguration) - - def test_from_dict(): schema = AgentConfigurationSchema() - dict_ = json.loads(DEFAULT_AGENT_CONFIGURATION_JSON) + dict_ = deepcopy(AGENT_CONFIGURATION) config = AgentConfiguration.from_mapping(dict_) @@ -198,7 +189,7 @@ def test_from_dict(): def test_from_dict__invalid_data(): - dict_ = json.loads(DEFAULT_AGENT_CONFIGURATION_JSON) + dict_ = deepcopy(AGENT_CONFIGURATION) dict_["payloads"] = "payloads" with pytest.raises(InvalidConfigurationError): @@ -207,15 +198,16 @@ def test_from_dict__invalid_data(): def test_from_json(): schema = AgentConfigurationSchema() - dict_ = json.loads(DEFAULT_AGENT_CONFIGURATION_JSON) + dict_ = deepcopy(AGENT_CONFIGURATION) - config = AgentConfiguration.from_json(DEFAULT_AGENT_CONFIGURATION_JSON) + config = AgentConfiguration.from_json(json.dumps(dict_)) + assert isinstance(config, AgentConfiguration) assert schema.dump(config) == dict_ def test_from_json__invalid_data(): - invalid_dict = json.loads(DEFAULT_AGENT_CONFIGURATION_JSON) + invalid_dict = deepcopy(AGENT_CONFIGURATION) invalid_dict["payloads"] = "payloads" with pytest.raises(InvalidConfigurationError): @@ -223,8 +215,6 @@ def test_from_json__invalid_data(): def test_to_json(): - config = AgentConfiguration.from_json(DEFAULT_AGENT_CONFIGURATION_JSON) + config = deepcopy(AGENT_CONFIGURATION) - assert json.loads(AgentConfiguration.to_json(config)) == json.loads( - DEFAULT_AGENT_CONFIGURATION_JSON - ) + assert json.loads(AgentConfiguration.to_json(config)) == AGENT_CONFIGURATION From e6d3854f74cae6393018b805618e2533160ccbb4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 27 Jun 2022 08:22:41 -0400 Subject: [PATCH 66/66] Common: Remove DEFAULT_AGENT_CONFIGURATION_JSON It's easier to maintain object than a JSON string for the default configuration. --- monkey/common/configuration/__init__.py | 3 +- .../default_agent_configuration.py | 300 ++++++------------ .../monkey_island/cc/services/initialize.py | 4 +- monkey/tests/unit_tests/conftest.py | 4 +- 4 files changed, 109 insertions(+), 202 deletions(-) diff --git a/monkey/common/configuration/__init__.py b/monkey/common/configuration/__init__.py index 06ce30b50..c7fefc11b 100644 --- a/monkey/common/configuration/__init__.py +++ b/monkey/common/configuration/__init__.py @@ -12,6 +12,5 @@ from .agent_sub_configurations import ( PropagationConfiguration, ) from .default_agent_configuration import ( - DEFAULT_AGENT_CONFIGURATION_JSON, - build_default_agent_configuration, + DEFAULT_AGENT_CONFIGURATION, ) diff --git a/monkey/common/configuration/default_agent_configuration.py b/monkey/common/configuration/default_agent_configuration.py index f3295a4b6..4eb8496a2 100644 --- a/monkey/common/configuration/default_agent_configuration.py +++ b/monkey/common/configuration/default_agent_configuration.py @@ -1,207 +1,115 @@ from . import AgentConfiguration +from .agent_sub_configurations import ( + CustomPBAConfiguration, + ExploitationConfiguration, + ExploitationOptionsConfiguration, + ExploiterConfiguration, + ICMPScanConfiguration, + NetworkScanConfiguration, + PluginConfiguration, + PropagationConfiguration, + ScanTargetConfiguration, + TCPScanConfiguration, +) -DEFAULT_AGENT_CONFIGURATION_JSON = """{ - "keep_tunnel_open_time": 30, - "post_breach_actions": [ - { - "name": "CommunicateAsBackdoorUser", - "options": {} - }, - { - "name": "ModifyShellStartupFiles", - "options": {} - }, - { - "name": "HiddenFiles", - "options": {} - }, - { - "name": "TrapCommand", - "options": {} - }, - { - "name": "ChangeSetuidSetgid", - "options": {} - }, - { - "name": "ScheduleJobs", - "options": {} - }, - { - "name": "Timestomping", - "options": {} - }, - { - "name": "AccountDiscovery", - "options": {} - }, - { - "name": "ProcessListCollection", - "options": {} - } - ], - "credential_collectors": [ - { - "name": "MimikatzCollector", - "options": {} - }, - { - "name": "SSHCollector", - "options": {} - } - ], - "payloads": [ - { - "name": "ransomware", - "options": { - "encryption": { - "enabled": true, - "directories": { - "linux_target_dir": "", - "windows_target_dir": "" - } - }, - "other_behaviors": { - "readme": true - } - } - } - ], - "custom_pbas": { - "linux_command": "", - "linux_filename": "", - "windows_command": "", - "windows_filename": "" - }, - "propagation": { - "maximum_depth": 2, - "network_scan": { - "tcp": { - "timeout": 3000, - "ports": [ - 22, - 80, - 135, - 443, - 445, - 2222, - 3306, - 3389, - 5985, - 5986, - 7001, - 8008, - 8080, - 8088, - 8983, - 9200, - 9600 - ] - }, - "icmp": { - "timeout": 1000 - }, - "fingerprinters": [ - { - "name": "elastic", - "options": {} - }, - { - "name": "http", - "options": { - "http_ports": [ - 80, - 443, - 7001, - 8008, - 8080, - 8983, - 9200, - 9600 - ] - } - }, - { - "name": "mssql", - "options": {} - }, - { - "name": "smb", - "options": {} - }, - { - "name": "ssh", - "options": {} - } - ], - "targets": { - "blocked_ips": [], - "inaccessible_subnets": [], - "local_network_scan": true, - "subnets": [] - } - }, - "exploitation": { - "options": { - "http_ports": [ - 80, - 443, - 7001, - 8008, - 8080, - 8983, - 9200, - 9600 - ] - }, - "brute_force": [ - { - "name": "MSSQLExploiter", - "options": {} +PBAS = [ + "CommunicateAsBackdoorUser", + "ModifyShellStartupFiles", + "HiddenFiles", + "TrapCommand", + "ChangeSetuidSetgid", + "ScheduleJobs", + "Timestomping", + "AccountDiscovery", + "ProcessListCollection", +] - }, - { - "name": "PowerShellExploiter", - "options": {} +CREDENTIAL_COLLECTORS = ["MimikatzCollector", "SSHCollector"] - }, - { - "name": "SSHExploiter", - "options": {} +PBA_CONFIGURATION = [PluginConfiguration(pba, {}) for pba in PBAS] +CREDENTIAL_COLLECTOR_CONFIGURATION = [ + PluginConfiguration(collector, {}) for collector in CREDENTIAL_COLLECTORS +] - }, - { - "name": "SmbExploiter", - "options": { - "smb_download_timeout": 30 - } +RANSOMWARE_OPTIONS = { + "encryption": { + "enabled": True, + "directories": {"linux_target_dir": "", "windows_target_dir": ""}, + }, + "other_behaviors": {"readme": True}, +} - }, - { - "name": "WmiExploiter", - "options": { - "smb_download_timeout": 30 - } +PAYLOAD_CONFIGURATION = [PluginConfiguration("ransomware", RANSOMWARE_OPTIONS)] - } - ], - "vulnerability": [ - { - "name": "HadoopExploiter", - "options": {} +CUSTOM_PBA_CONFIGURATION = CustomPBAConfiguration( + linux_command="", linux_filename="", windows_command="", windows_filename="" +) - }, - { - "name": "Log4ShellExploiter", - "options": {} +TCP_PORTS = [ + 22, + 80, + 135, + 443, + 445, + 2222, + 3306, + 3389, + 5985, + 5986, + 7001, + 8008, + 8080, + 8088, + 8983, + 9200, + 9600, +] - } - ] - } - } - } -""" +TCP_SCAN_CONFIGURATION = TCPScanConfiguration(timeout=3.0, ports=TCP_PORTS) +ICMP_CONFIGURATION = ICMPScanConfiguration(timeout=1.0) +HTTP_PORTS = [80, 443, 7001, 8008, 8080, 8983, 9200, 9600] +FINGERPRINTERS = [ + PluginConfiguration("elastic", {}), + PluginConfiguration("http", {"http_ports": HTTP_PORTS}), + PluginConfiguration("mssql", {}), + PluginConfiguration("smb", {}), + PluginConfiguration("ssh", {}), +] +SCAN_TARGET_CONFIGURATION = ScanTargetConfiguration([], [], True, []) +NETWORK_SCAN_CONFIGURATION = NetworkScanConfiguration( + TCP_SCAN_CONFIGURATION, ICMP_CONFIGURATION, FINGERPRINTERS, SCAN_TARGET_CONFIGURATION +) -def build_default_agent_configuration() -> AgentConfiguration: - return AgentConfiguration.from_json(DEFAULT_AGENT_CONFIGURATION_JSON) +EXPLOITATION_OPTIONS_CONFIGURATION = ExploitationOptionsConfiguration(HTTP_PORTS) +BRUTE_FORCE_EXPLOITERS = [ + ExploiterConfiguration("MSSQLExploiter", {}), + ExploiterConfiguration("PowerShellExploiter", {}), + ExploiterConfiguration("SSHExploiter", {}), + ExploiterConfiguration("SmbExploiter", {"smb_download_timeout": 30}), + ExploiterConfiguration("WmiExploiter", {"smb_download_timeout": 30}), +] + +VULNERABILITY_EXPLOITERS = [ + ExploiterConfiguration("Log4ShellExploiter", {}), + ExploiterConfiguration("HadoopExploiter", {}), +] + +EXPLOITATION_CONFIGURATION = ExploitationConfiguration( + EXPLOITATION_OPTIONS_CONFIGURATION, BRUTE_FORCE_EXPLOITERS, VULNERABILITY_EXPLOITERS +) + +PROPAGATION_CONFIGURATION = PropagationConfiguration( + maximum_depth=2, + network_scan=NETWORK_SCAN_CONFIGURATION, + exploitation=EXPLOITATION_CONFIGURATION, +) + +DEFAULT_AGENT_CONFIGURATION = AgentConfiguration( + keep_tunnel_open_time=30, + custom_pbas=CUSTOM_PBA_CONFIGURATION, + post_breach_actions=PBA_CONFIGURATION, + credential_collectors=CREDENTIAL_COLLECTOR_CONFIGURATION, + payloads=PAYLOAD_CONFIGURATION, + propagation=PROPAGATION_CONFIGURATION, +) diff --git a/monkey/monkey_island/cc/services/initialize.py b/monkey/monkey_island/cc/services/initialize.py index 52343bbf5..922c3654b 100644 --- a/monkey/monkey_island/cc/services/initialize.py +++ b/monkey/monkey_island/cc/services/initialize.py @@ -3,7 +3,7 @@ from pathlib import Path from common import DIContainer from common.aws import AWSInstance -from common.configuration import AgentConfiguration, build_default_agent_configuration +from common.configuration import DEFAULT_AGENT_CONFIGURATION, AgentConfiguration from common.utils.file_utils import get_binary_io_sha256_hash from monkey_island.cc.repository import ( AgentBinaryRepository, @@ -32,7 +32,7 @@ def initialize_services(data_dir: Path) -> DIContainer: container.register_convention(Path, "data_dir", data_dir) container.register_convention( - AgentConfiguration, "default_agent_configuration", build_default_agent_configuration() + AgentConfiguration, "default_agent_configuration", DEFAULT_AGENT_CONFIGURATION ) container.register_instance(AWSInstance, AWSInstance()) diff --git a/monkey/tests/unit_tests/conftest.py b/monkey/tests/unit_tests/conftest.py index 3634e52b9..51528ba00 100644 --- a/monkey/tests/unit_tests/conftest.py +++ b/monkey/tests/unit_tests/conftest.py @@ -9,7 +9,7 @@ from _pytest.monkeypatch import MonkeyPatch MONKEY_BASE_PATH = str(Path(__file__).parent.parent.parent) sys.path.insert(0, MONKEY_BASE_PATH) -from common.configuration import AgentConfiguration, build_default_agent_configuration # noqa: E402 +from common.configuration import DEFAULT_AGENT_CONFIGURATION, AgentConfiguration # noqa: E402 @pytest.fixture(scope="session") @@ -60,4 +60,4 @@ def load_monkey_config(data_for_tests_dir) -> Callable[[str], Dict]: @pytest.fixture def default_agent_configuration() -> AgentConfiguration: - return build_default_agent_configuration() + return DEFAULT_AGENT_CONFIGURATION