Merge pull request #2032 from guardicore/1960-flat-config-changes

Change config flattening logic to return new schema
This commit is contained in:
VakarisZ 2022-06-20 15:32:47 +03:00 committed by GitHub
commit b959763318
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 132 additions and 84 deletions

View File

@ -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"]):

View File

@ -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)

View File

@ -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,

View File

@ -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

View File

@ -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)