Merge pull request #2042 from guardicore/2001-use-new-configuration-endpoint

2001 use new configuration endpoint
This commit is contained in:
Mike Salvatore 2022-06-27 11:00:47 -04:00 committed by GitHub
commit 596bacfa36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 4 additions and 450 deletions

View File

@ -52,17 +52,16 @@ class ControlChannel(IControlChannel):
def get_config(self) -> AgentConfiguration: def get_config(self) -> AgentConfiguration:
try: try:
response = requests.get( # noqa: DUO123 response = requests.get( # noqa: DUO123
f"https://{self._control_channel_server}/api/agent", f"https://{self._control_channel_server}/api/agent-configuration",
verify=False, verify=False,
proxies=self._proxies, proxies=self._proxies,
timeout=SHORT_REQUEST_TIMEOUT, timeout=SHORT_REQUEST_TIMEOUT,
) )
response.raise_for_status() response.raise_for_status()
config_dict = json.loads(response.text)["config"]
logger.debug(f"Received configuration:\n{pformat(json.loads(response.text))}") logger.debug(f"Received configuration:\n{pformat(json.loads(response.text))}")
return AgentConfiguration.from_mapping(config_dict) return AgentConfiguration.from_json(response.text)
except ( except (
json.JSONDecodeError, json.JSONDecodeError,
requests.exceptions.ConnectionError, requests.exceptions.ConnectionError,

View File

@ -15,7 +15,7 @@ class AgentConfiguration(AbstractResource):
def __init__(self, agent_configuration_repository: IAgentConfigurationRepository): def __init__(self, agent_configuration_repository: IAgentConfigurationRepository):
self._agent_configuration_repository = agent_configuration_repository self._agent_configuration_repository = agent_configuration_repository
@jwt_required # Used by the agent. Can't secure
def get(self): def get(self):
configuration = self._agent_configuration_repository.get_configuration() configuration = self._agent_configuration_repository.get_configuration()
configuration_json = AgentConfigurationObject.to_json(configuration) configuration_json = AgentConfigurationObject.to_json(configuration)
@ -23,7 +23,6 @@ class AgentConfiguration(AbstractResource):
@jwt_required @jwt_required
def post(self): def post(self):
try: try:
configuration_object = AgentConfigurationObject.from_json(request.data) configuration_object = AgentConfigurationObject.from_json(request.data)
self._agent_configuration_repository.store_configuration(configuration_object) self._agent_configuration_repository.store_configuration(configuration_object)

View File

@ -8,7 +8,6 @@ from monkey_island.cc.models.monkey_ttl import create_monkey_ttl_document
from monkey_island.cc.resources.AbstractResource import AbstractResource from monkey_island.cc.resources.AbstractResource import AbstractResource
from monkey_island.cc.resources.utils.semaphores import agent_killing_mutex from monkey_island.cc.resources.utils.semaphores import agent_killing_mutex
from monkey_island.cc.server_utils.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS from monkey_island.cc.server_utils.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS
from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.services.edge.edge import EdgeService from monkey_island.cc.services.edge.edge import EdgeService
from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.node import NodeService
@ -22,10 +21,6 @@ class Monkey(AbstractResource):
"/api/agent/<string:guid>", "/api/agent/<string:guid>",
] ]
# Used by monkey. can't secure.
def get(self):
return {"config": ConfigService.format_flat_config_for_agent()}
# Used by monkey. can't secure. # Used by monkey. can't secure.
# Called on monkey wakeup to initialize local configuration # Called on monkey wakeup to initialize local configuration
def post(self, **kw): def post(self, **kw):

View File

@ -2,8 +2,7 @@ import collections
import copy import copy
import functools import functools
import logging import logging
import re from typing import Dict, List
from typing import Any, Dict, List
from jsonschema import Draft4Validator, validators from jsonschema import Draft4Validator, validators
@ -44,8 +43,6 @@ SENSITIVE_SSH_KEY_FIELDS = [
SensitiveField(path="public_key", field_encryptor=StringEncryptor), SensitiveField(path="public_key", field_encryptor=StringEncryptor),
] ]
SMB_DOWNLOAD_TIMEOUT = 30
class ConfigService: class ConfigService:
default_config = None default_config = None
@ -349,246 +346,3 @@ class ConfigService:
"exploit_ntlm_hash_list": config.get("exploit_ntlm_hash_list", []), "exploit_ntlm_hash_list": config.get("exploit_ntlm_hash_list", []),
"exploit_ssh_keys": config.get("exploit_ssh_keys", []), "exploit_ssh_keys": config.get("exploit_ssh_keys", []),
} }
@staticmethod
def format_flat_config_for_agent():
config = ConfigService.get_flat_config()
ConfigService._remove_credentials_from_flat_config(config)
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"]
return config
@staticmethod
def _remove_credentials_from_flat_config(config: Dict):
fields_to_remove = {
"exploit_lm_hash_list",
"exploit_ntlm_hash_list",
"exploit_password_list",
"exploit_ssh_keys",
"exploit_user_list",
}
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", []).append(
{"name": "ransomware", "options": config["ransomware"]}
)
config.pop("ransomware", None)
@staticmethod
def _format_pbas_from_flat_config(config: Dict):
flat_linux_command_field = "custom_PBA_linux_cmd"
flat_linux_filename_field = "PBA_linux_filename"
flat_windows_command_field = "custom_PBA_windows_cmd"
flat_windows_filename_field = "PBA_windows_filename"
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, ""),
"linux_filename": config.get(flat_linux_filename_field, ""),
"windows_command": config.get(flat_windows_command_field, ""),
"windows_filename": config.get(flat_windows_filename_field, ""),
}
config["post_breach_actions"] = formatted_pbas_config
config.pop(flat_linux_command_field, None)
config.pop(flat_linux_filename_field, None)
config.pop(flat_windows_command_field, None)
config.pop(flat_windows_filename_field, None)
@staticmethod
def _format_propagation_from_flat_config(config: Dict):
formatted_propagation_config = {"network_scan": {}, "maximum_depth": {}, "exploitation": {}}
formatted_propagation_config[
"network_scan"
] = ConfigService._format_network_scan_from_flat_config(config)
formatted_propagation_config[
"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": [], "targets": {}}
formatted_network_scan_config["tcp"] = ConfigService._format_tcp_scan_from_flat_config(
config
)
formatted_network_scan_config["icmp"] = ConfigService._format_icmp_scan_from_flat_config(
config
)
formatted_network_scan_config[
"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
def _format_tcp_scan_from_flat_config(config: Dict) -> Dict[str, Any]:
flat_http_ports_field = "HTTP_PORTS"
flat_tcp_timeout_field = "tcp_scan_timeout"
flat_tcp_ports_field = "tcp_target_ports"
formatted_tcp_scan_config = {}
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]
)
formatted_tcp_scan_config["ports"] = ports
# Do not remove HTTP_PORTS field. Other components besides scanning need it.
config.pop(flat_tcp_timeout_field, None)
config.pop(flat_tcp_ports_field, None)
return formatted_tcp_scan_config
@staticmethod
def _union_tcp_and_http_ports(tcp_ports: List[int], http_ports: List[int]) -> List[int]:
combined_ports = list(set(tcp_ports) | set(http_ports))
return sorted(combined_ports)
@staticmethod
def _format_icmp_scan_from_flat_config(config: Dict) -> Dict[str, Any]:
flat_ping_timeout_field = "ping_scan_timeout"
formatted_icmp_scan_config = {}
formatted_icmp_scan_config["timeout"] = config[flat_ping_timeout_field] / 1000
config.pop(flat_ping_timeout_field, None)
return formatted_icmp_scan_config
@staticmethod
def _format_fingerprinters_from_flat_config(config: Dict) -> List[Dict[str, Any]]:
flat_fingerprinter_classes_field = "finger_classes"
flat_http_ports_field = "HTTP_PORTS"
formatted_fingerprinters = [
{"name": f, "options": {}} for f in sorted(config[flat_fingerprinter_classes_field])
]
for fp in formatted_fingerprinters:
if fp["name"] == "HTTPFinger":
fp["options"] = {"http_ports": sorted(config[flat_http_ports_field])}
fp["name"] = ConfigService._translate_fingerprinter_name(fp["name"])
config.pop(flat_fingerprinter_classes_field)
return formatted_fingerprinters
@staticmethod
def _translate_fingerprinter_name(name: str) -> str:
# This translates names like "HTTPFinger" to "http". "HTTPFinger" is an old classname on the
# agent-side and is therefore unnecessarily couples the island to the fingerprinter's
# implementation within the agent. For the time being, fingerprinters will have names like
# "http", "ssh", "elastic", etc. This will be revisited when fingerprinters become plugins.
return re.sub(r"Finger", "", name).lower()
@staticmethod
def _format_targets_from_flat_config(config: Dict) -> Dict[str, Any]:
flat_blocked_ips_field = "blocked_ips"
flat_inaccessible_subnets_field = "inaccessible_subnets"
flat_local_network_scan_field = "local_network_scan"
flat_subnet_scan_list_field = "subnet_scan_list"
formatted_scan_targets_config = {}
formatted_scan_targets_config[flat_blocked_ips_field] = config[flat_blocked_ips_field]
formatted_scan_targets_config[flat_inaccessible_subnets_field] = config[
flat_inaccessible_subnets_field
]
formatted_scan_targets_config[flat_local_network_scan_field] = config[
flat_local_network_scan_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)
config.pop(flat_local_network_scan_field, None)
config.pop(flat_subnet_scan_list_field, None)
return formatted_scan_targets_config
@staticmethod
def _format_exploiters_from_flat_config(config: Dict) -> Dict[str, List[Dict[str, Any]]]:
flat_config_exploiter_classes_field = "exploiter_classes"
brute_force_category = "brute_force"
vulnerability_category = "vulnerability"
brute_force_exploiters = {
"MSSQLExploiter",
"PowerShellExploiter",
"SSHExploiter",
"SmbExploiter",
"WmiExploiter",
}
exploit_options = {}
exploit_options["http_ports"] = sorted(config["HTTP_PORTS"])
formatted_exploiters_config = {
"options": exploit_options,
"brute_force": [],
"vulnerability": [],
}
for exploiter in sorted(config[flat_config_exploiter_classes_field]):
category = (
brute_force_category
if exploiter in brute_force_exploiters
else vulnerability_category
)
formatted_exploiters_config[category].append({"name": exploiter, "options": {}})
config.pop(flat_config_exploiter_classes_field, None)
formatted_exploiters_config = ConfigService._add_smb_download_timeout_to_exploiters(
formatted_exploiters_config
)
return formatted_exploiters_config
@staticmethod
def _add_smb_download_timeout_to_exploiters(
formatted_config: Dict,
) -> Dict[str, List[Dict[str, Any]]]:
new_config = copy.deepcopy(formatted_config)
uses_smb_timeout = {"SmbExploiter", "WmiExploiter"}
for exploiter in filter(lambda e: e["name"] in uses_smb_timeout, new_config["brute_force"]):
exploiter["options"]["smb_download_timeout"] = SMB_DOWNLOAD_TIMEOUT
return new_config

View File

@ -1,80 +1,9 @@
import pytest
from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.config import ConfigService
# If tests fail because config path is changed, sync with # If tests fail because config path is changed, sync with
# monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js # monkey/monkey_island/cc/ui/src/components/pages/RunMonkeyPage/RunOptions.js
@pytest.fixture(autouse=True)
def mock_flat_config(monkeypatch, flat_monkey_config):
monkeypatch.setattr(
"monkey_island.cc.services.config.ConfigService.get_flat_config", lambda: flat_monkey_config
)
def test_format_config_for_agent__credentials_removed():
flat_monkey_config = ConfigService.format_flat_config_for_agent()
assert "exploit_lm_hash_list" not in flat_monkey_config
assert "exploit_ntlm_hash_list" not in flat_monkey_config
assert "exploit_password_list" not in flat_monkey_config
assert "exploit_ssh_keys" not in flat_monkey_config
assert "exploit_user_list" not in flat_monkey_config
def test_format_config_for_agent__ransomware_payload():
expected_ransomware_options = {
"encryption": {
"enabled": True,
"directories": {
"linux_target_dir": "/tmp/ransomware-target",
"windows_target_dir": "C:\\windows\\temp\\ransomware-target",
},
},
"other_behaviors": {"readme": True},
}
flat_monkey_config = ConfigService.format_flat_config_for_agent()
assert "payloads" in flat_monkey_config
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 = [
{"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
assert flat_monkey_config["post_breach_actions"] == expected_pbas_config
assert "custom_PBA_linux_cmd" not in flat_monkey_config
assert "PBA_linux_filename" not in flat_monkey_config
assert "custom_PBA_windows_cmd" not in flat_monkey_config
assert "PBA_windows_filename" not in flat_monkey_config
def test_format_config_for_custom_pbas():
custom_config = {
"linux_command": "bash test.sh",
"windows_command": "powershell test.ps1",
"linux_filename": "test.sh",
"windows_filename": "test.ps1",
}
flat_monkey_config = ConfigService.format_flat_config_for_agent()
assert flat_monkey_config["custom_pbas"] == custom_config
def test_get_config_propagation_credentials_from_flat_config(flat_monkey_config): def test_get_config_propagation_credentials_from_flat_config(flat_monkey_config):
expected_creds = { expected_creds = {
"exploit_lm_hash_list": ["lm_hash_1", "lm_hash_2"], "exploit_lm_hash_list": ["lm_hash_1", "lm_hash_2"],
@ -86,125 +15,3 @@ def test_get_config_propagation_credentials_from_flat_config(flat_monkey_config)
creds = ConfigService.get_config_propagation_credentials_from_flat_config(flat_monkey_config) creds = ConfigService.get_config_propagation_credentials_from_flat_config(flat_monkey_config)
assert creds == expected_creds assert creds == expected_creds
def test_format_config_for_agent__propagation():
flat_monkey_config = ConfigService.format_flat_config_for_agent()
assert "propagation" in flat_monkey_config
assert "network_scan" in flat_monkey_config["propagation"]
assert "exploitation" in flat_monkey_config["propagation"]
def test_format_config_for_agent__network_scan():
expected_network_scan_config = {
"tcp": {
"timeout": 3.0,
"ports": [
22,
80,
135,
443,
445,
2222,
3306,
3389,
7001,
8008,
8080,
8088,
9200,
],
},
"icmp": {
"timeout": 1.0,
},
"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": {}},
{
"name": "http",
"options": {"http_ports": [80, 443, 7001, 8008, 8080, 9200]},
},
{"name": "mssql", "options": {}},
{"name": "smb", "options": {}},
{"name": "ssh", "options": {}},
],
}
flat_monkey_config = ConfigService.format_flat_config_for_agent()
assert "propagation" in flat_monkey_config
assert "network_scan" in flat_monkey_config["propagation"]
assert flat_monkey_config["propagation"]["network_scan"] == expected_network_scan_config
assert "tcp_scan_timeout" not in flat_monkey_config
assert "tcp_target_ports" not in flat_monkey_config
assert "ping_scan_timeout" not in flat_monkey_config
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", "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": {},
},
{
"name": "ZerologonExploiter",
"options": {},
},
],
}
flat_monkey_config = ConfigService.format_flat_config_for_agent()
assert "propagation" in flat_monkey_config
assert "exploitation" in flat_monkey_config["propagation"]
assert flat_monkey_config["propagation"]["exploitation"] == expected_exploiters_config
assert "exploiter_classes" not in flat_monkey_config