diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 82346258f..86541c33d 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -8,6 +8,7 @@ from typing import Any, Dict, List from jsonschema import Draft4Validator, validators +from common import OperatingSystems from common.config_value_paths import ( LM_HASH_LIST_PATH, NTLM_HASH_LIST_PATH, @@ -357,6 +358,7 @@ class ConfigService: ConfigService._format_payloads_from_flat_config(config) ConfigService._format_pbas_from_flat_config(config) ConfigService._format_propagation_from_flat_config(config) + ConfigService._format_credential_collectors(config) # Ok, I'll admit this is just sort of jammed in here. But this code is going away very soon. del config["HTTP_PORTS"] @@ -376,9 +378,18 @@ class ConfigService: for field in fields_to_remove: config.pop(field, None) + @staticmethod + def _format_credential_collectors(config: Dict): + collectors = [ + {"name": collector, "options": {}} for collector in config["credential_collectors"] + ] + config["credential_collectors"] = collectors + @staticmethod def _format_payloads_from_flat_config(config: Dict): - config.setdefault("payloads", {})["ransomware"] = config["ransomware"] + config.setdefault("payloads", []).append( + {"name": "ransomware", "options": config["ransomware"]} + ) config.pop("ransomware", None) @staticmethod @@ -388,9 +399,9 @@ class ConfigService: flat_windows_command_field = "custom_PBA_windows_cmd" flat_windows_filename_field = "PBA_windows_filename" - formatted_pbas_config = {} - for pba in config.get("post_breach_actions", []): - formatted_pbas_config[pba] = {} + formatted_pbas_config = [ + {"name": pba, "options": {}} for pba in config.get("post_breach_actions", []) + ] config["custom_pbas"] = { "linux_command": config.get(flat_linux_command_field, ""), @@ -408,24 +419,24 @@ class ConfigService: @staticmethod def _format_propagation_from_flat_config(config: Dict): - formatted_propagation_config = {"network_scan": {}, "targets": {}} + formatted_propagation_config = {"network_scan": {}, "maximum_depth": {}, "exploitation": {}} formatted_propagation_config[ "network_scan" ] = ConfigService._format_network_scan_from_flat_config(config) - formatted_propagation_config["targets"] = ConfigService._format_targets_from_flat_config( - config - ) formatted_propagation_config[ - "exploiters" + "exploitation" ] = ConfigService._format_exploiters_from_flat_config(config) + formatted_propagation_config["maximum_depth"] = config["depth"] + del config["depth"] + config["propagation"] = formatted_propagation_config @staticmethod def _format_network_scan_from_flat_config(config: Dict) -> Dict[str, Any]: - formatted_network_scan_config = {"tcp": {}, "icmp": {}, "fingerprinters": []} + formatted_network_scan_config = {"tcp": {}, "icmp": {}, "fingerprinters": [], "targets": {}} formatted_network_scan_config["tcp"] = ConfigService._format_tcp_scan_from_flat_config( config @@ -437,6 +448,10 @@ class ConfigService: "fingerprinters" ] = ConfigService._format_fingerprinters_from_flat_config(config) + formatted_network_scan_config["targets"] = ConfigService._format_targets_from_flat_config( + config + ) + return formatted_network_scan_config @staticmethod @@ -447,7 +462,7 @@ class ConfigService: formatted_tcp_scan_config = {} - formatted_tcp_scan_config["timeout_ms"] = config[flat_tcp_timeout_field] + formatted_tcp_scan_config["timeout"] = config[flat_tcp_timeout_field] ports = ConfigService._union_tcp_and_http_ports( config[flat_tcp_ports_field], config[flat_http_ports_field] @@ -471,7 +486,7 @@ class ConfigService: flat_ping_timeout_field = "ping_scan_timeout" formatted_icmp_scan_config = {} - formatted_icmp_scan_config["timeout_ms"] = config[flat_ping_timeout_field] + formatted_icmp_scan_config["timeout"] = config[flat_ping_timeout_field] config.pop(flat_ping_timeout_field, None) @@ -519,9 +534,7 @@ class ConfigService: formatted_scan_targets_config[flat_local_network_scan_field] = config[ flat_local_network_scan_field ] - formatted_scan_targets_config[flat_subnet_scan_list_field] = config[ - flat_subnet_scan_list_field - ] + formatted_scan_targets_config["subnets"] = config[flat_subnet_scan_list_field] config.pop(flat_blocked_ips_field, None) config.pop(flat_inaccessible_subnets_field, None) @@ -586,14 +599,14 @@ class ConfigService: formatted_config: Dict, ) -> Dict[str, List[Dict[str, Any]]]: supported_os = { - "HadoopExploiter": ["linux", "windows"], - "Log4ShellExploiter": ["linux", "windows"], - "MSSQLExploiter": ["windows"], - "PowerShellExploiter": ["windows"], - "SSHExploiter": ["linux"], - "SmbExploiter": ["windows"], - "WmiExploiter": ["windows"], - "ZerologonExploiter": ["windows"], + "HadoopExploiter": [OperatingSystems.LINUX, OperatingSystems.WINDOWS], + "Log4ShellExploiter": [OperatingSystems.LINUX, OperatingSystems.WINDOWS], + "MSSQLExploiter": [OperatingSystems.WINDOWS], + "PowerShellExploiter": [OperatingSystems.WINDOWS], + "SSHExploiter": [OperatingSystems.LINUX], + "SmbExploiter": [OperatingSystems.WINDOWS], + "WmiExploiter": [OperatingSystems.WINDOWS], + "ZerologonExploiter": [OperatingSystems.WINDOWS], } new_config = copy.deepcopy(formatted_config) for exploiter in chain(new_config["brute_force"], new_config["vulnerability"]): diff --git a/monkey/monkey_island/cc/services/representations.py b/monkey/monkey_island/cc/services/representations.py index 0193fae0d..8a1e849a6 100644 --- a/monkey/monkey_island/cc/services/representations.py +++ b/monkey/monkey_island/cc/services/representations.py @@ -1,4 +1,5 @@ from datetime import datetime +from enum import Enum import bson from bson.json_util import dumps @@ -11,19 +12,27 @@ def normalize_obj(obj): del obj["_id"] for key, value in list(obj.items()): - if isinstance(value, bson.objectid.ObjectId): - obj[key] = str(value) - if isinstance(value, datetime): - obj[key] = str(value) - if isinstance(value, dict): - obj[key] = normalize_obj(value) if isinstance(value, list): for i in range(0, len(value)): - if isinstance(value[i], dict): - value[i] = normalize_obj(value[i]) + obj[key][i] = _normalize_value(value[i]) + else: + obj[key] = _normalize_value(value) return obj +def _normalize_value(value): + if type(value) == dict: + return normalize_obj(value) + if isinstance(value, bson.objectid.ObjectId): + return str(value) + if isinstance(value, datetime): + return str(value) + if issubclass(type(value), Enum): + return value.name + else: + return value + + def output_json(obj, code, headers=None): obj = normalize_obj(obj) resp = make_response(dumps(obj), code) diff --git a/monkey/tests/data_for_tests/monkey_configs/flat_config.json b/monkey/tests/data_for_tests/monkey_configs/flat_config.json index 33bf50da1..42568404a 100644 --- a/monkey/tests/data_for_tests/monkey_configs/flat_config.json +++ b/monkey/tests/data_for_tests/monkey_configs/flat_config.json @@ -9,7 +9,6 @@ ], "PBA_linux_filename": "test.sh", "PBA_windows_filename": "test.ps1", - "alive": true, "blocked_ips": ["192.168.1.1", "192.168.1.100"], "custom_PBA_linux_cmd": "bash test.sh", "custom_PBA_windows_cmd": "powershell test.ps1", @@ -27,6 +26,7 @@ "private_key": "my_private_key" } ], + "credential_collectors": ["MimikatzCollector", "SSHCollector"], "exploit_user_list": [ "Administrator", "root", @@ -53,7 +53,6 @@ "inaccessible_subnets": ["10.0.0.0/24", "10.0.10.0/24"], "keep_tunnel_open_time": 60, "local_network_scan": true, - "max_depth": null, "ping_scan_timeout": 1000, "post_breach_actions": [ "CommunicateAsBackdoorUser", @@ -75,9 +74,6 @@ } }, "subnet_scan_list": ["192.168.1.50", "192.168.56.0/24", "10.0.33.0/30"], - "system_info_collector_classes": [ - "MimikatzCollector" - ], "tcp_scan_timeout": 3000, "tcp_target_ports": [ 22, 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 9e01b8365..df866e388 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 @@ -1,5 +1,6 @@ import pytest +from common import OperatingSystems from monkey_island.cc.services.config import ConfigService # If tests fail because config path is changed, sync with @@ -25,34 +26,33 @@ def test_format_config_for_agent__credentials_removed(): def test_format_config_for_agent__ransomware_payload(): expected_ransomware_options = { - "ransomware": { - "encryption": { - "enabled": True, - "directories": { - "linux_target_dir": "/tmp/ransomware-target", - "windows_target_dir": "C:\\windows\\temp\\ransomware-target", - }, + "encryption": { + "enabled": True, + "directories": { + "linux_target_dir": "/tmp/ransomware-target", + "windows_target_dir": "C:\\windows\\temp\\ransomware-target", }, - "other_behaviors": {"readme": True}, - } + }, + "other_behaviors": {"readme": True}, } flat_monkey_config = ConfigService.format_flat_config_for_agent() assert "payloads" in flat_monkey_config - assert flat_monkey_config["payloads"] == expected_ransomware_options + assert flat_monkey_config["payloads"][0]["name"] == "ransomware" + assert flat_monkey_config["payloads"][0]["options"] == expected_ransomware_options assert "ransomware" not in flat_monkey_config def test_format_config_for_agent__pbas(): - expected_pbas_config = { - "CommunicateAsBackdoorUser": {}, - "ModifyShellStartupFiles": {}, - "ScheduleJobs": {}, - "Timestomping": {}, - "AccountDiscovery": {}, - } + expected_pbas_config = [ + {"name": "CommunicateAsBackdoorUser", "options": {}}, + {"name": "ModifyShellStartupFiles", "options": {}}, + {"name": "ScheduleJobs", "options": {}}, + {"name": "Timestomping", "options": {}}, + {"name": "AccountDiscovery", "options": {}}, + ] flat_monkey_config = ConfigService.format_flat_config_for_agent() assert "post_breach_actions" in flat_monkey_config @@ -93,32 +93,14 @@ def test_format_config_for_agent__propagation(): flat_monkey_config = ConfigService.format_flat_config_for_agent() assert "propagation" in flat_monkey_config - assert "targets" in flat_monkey_config["propagation"] assert "network_scan" in flat_monkey_config["propagation"] - assert "exploiters" in flat_monkey_config["propagation"] - - -def test_format_config_for_agent__propagation_targets(): - expected_targets = { - "blocked_ips": ["192.168.1.1", "192.168.1.100"], - "inaccessible_subnets": ["10.0.0.0/24", "10.0.10.0/24"], - "local_network_scan": True, - "subnet_scan_list": ["192.168.1.50", "192.168.56.0/24", "10.0.33.0/30"], - } - - flat_monkey_config = ConfigService.format_flat_config_for_agent() - - assert flat_monkey_config["propagation"]["targets"] == expected_targets - assert "blocked_ips" not in flat_monkey_config - assert "inaccessible_subnets" not in flat_monkey_config - assert "local_network_scan" not in flat_monkey_config - assert "subnet_scan_list" not in flat_monkey_config + assert "exploitation" in flat_monkey_config["propagation"] def test_format_config_for_agent__network_scan(): expected_network_scan_config = { "tcp": { - "timeout_ms": 3000, + "timeout": 3000, "ports": [ 22, 80, @@ -136,7 +118,13 @@ def test_format_config_for_agent__network_scan(): ], }, "icmp": { - "timeout_ms": 1000, + "timeout": 1000, + }, + "targets": { + "blocked_ips": ["192.168.1.1", "192.168.1.100"], + "inaccessible_subnets": ["10.0.0.0/24", "10.0.10.0/24"], + "local_network_scan": True, + "subnets": ["192.168.1.50", "192.168.56.0/24", "10.0.33.0/30"], }, "fingerprinters": [ {"name": "elastic", "options": {}}, @@ -161,36 +149,69 @@ def test_format_config_for_agent__network_scan(): assert "finger_classes" not in flat_monkey_config +def test_format_config_for_agent__propagation_network_scan_targets(): + expected_targets = { + "blocked_ips": ["192.168.1.1", "192.168.1.100"], + "inaccessible_subnets": ["10.0.0.0/24", "10.0.10.0/24"], + "local_network_scan": True, + "subnets": ["192.168.1.50", "192.168.56.0/24", "10.0.33.0/30"], + } + + flat_monkey_config = ConfigService.format_flat_config_for_agent() + + assert flat_monkey_config["propagation"]["network_scan"]["targets"] == expected_targets + assert "blocked_ips" not in flat_monkey_config + assert "inaccessible_subnets" not in flat_monkey_config + assert "local_network_scan" not in flat_monkey_config + assert "subnet_scan_list" not in flat_monkey_config + + def test_format_config_for_agent__exploiters(): expected_exploiters_config = { "options": { "http_ports": [80, 443, 7001, 8008, 8080, 9200], }, "brute_force": [ - {"name": "MSSQLExploiter", "supported_os": ["windows"], "options": {}}, - {"name": "PowerShellExploiter", "supported_os": ["windows"], "options": {}}, - {"name": "SSHExploiter", "supported_os": ["linux"], "options": {}}, + {"name": "MSSQLExploiter", "supported_os": [OperatingSystems.WINDOWS], "options": {}}, + { + "name": "PowerShellExploiter", + "supported_os": [OperatingSystems.WINDOWS], + "options": {}, + }, + {"name": "SSHExploiter", "supported_os": [OperatingSystems.LINUX], "options": {}}, { "name": "SmbExploiter", - "supported_os": ["windows"], + "supported_os": [OperatingSystems.WINDOWS], "options": {"smb_download_timeout": 30}, }, { "name": "WmiExploiter", - "supported_os": ["windows"], + "supported_os": [OperatingSystems.WINDOWS], "options": {"smb_download_timeout": 30}, }, ], "vulnerability": [ - {"name": "HadoopExploiter", "supported_os": ["linux", "windows"], "options": {}}, - {"name": "Log4ShellExploiter", "supported_os": ["linux", "windows"], "options": {}}, - {"name": "ZerologonExploiter", "supported_os": ["windows"], "options": {}}, + { + "name": "HadoopExploiter", + "supported_os": [OperatingSystems.LINUX, OperatingSystems.WINDOWS], + "options": {}, + }, + { + "name": "Log4ShellExploiter", + "supported_os": [OperatingSystems.LINUX, OperatingSystems.WINDOWS], + "options": {}, + }, + { + "name": "ZerologonExploiter", + "supported_os": [OperatingSystems.WINDOWS], + "options": {}, + }, ], } flat_monkey_config = ConfigService.format_flat_config_for_agent() assert "propagation" in flat_monkey_config - assert "exploiters" in flat_monkey_config["propagation"] + assert "exploitation" in flat_monkey_config["propagation"] - assert flat_monkey_config["propagation"]["exploiters"] == expected_exploiters_config + assert flat_monkey_config["propagation"]["exploitation"] == expected_exploiters_config assert "exploiter_classes" not in flat_monkey_config diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/test_representations.py b/monkey/tests/unit_tests/monkey_island/cc/services/test_representations.py index c088c3dce..e40e4470f 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/test_representations.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/test_representations.py @@ -1,4 +1,5 @@ from datetime import datetime +from enum import Enum from unittest import TestCase import bson @@ -44,3 +45,11 @@ class TestRepresentations(TestCase): } ), ) + + def test_normalize__enum(self): + class BogusEnum(Enum): + bogus_val = "Bogus" + + my_obj = {"something": "something", "my_enum": BogusEnum.bogus_val} + + assert {"something": "something", "my_enum": "bogus_val"} == normalize_obj(my_obj)