Merge branch '2000-configuration-resource' into 1960-configuration-object

PR #2038
This commit is contained in:
Mike Salvatore 2022-06-23 11:45:58 -04:00
commit d079d74b2c
16 changed files with 163 additions and 109 deletions

View File

@ -1,7 +1,4 @@
from marshmallow import Schema, fields, post_load from marshmallow import Schema, fields, post_load
from marshmallow_enum import EnumField
from common import OperatingSystems
from .agent_sub_configurations import ( from .agent_sub_configurations import (
CustomPBAConfiguration, CustomPBAConfiguration,
@ -87,7 +84,6 @@ class ExploitationOptionsConfigurationSchema(Schema):
class ExploiterConfigurationSchema(Schema): class ExploiterConfigurationSchema(Schema):
name = fields.Str() name = fields.Str()
options = fields.Mapping() options = fields.Mapping()
supported_os = fields.List(EnumField(OperatingSystems))
@post_load @post_load
def _make_exploiter_configuration(self, data, **kwargs): def _make_exploiter_configuration(self, data, **kwargs):

View File

@ -1,8 +1,6 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, List from typing import Dict, List
from common import OperatingSystems
@dataclass(frozen=True) @dataclass(frozen=True)
class CustomPBAConfiguration: class CustomPBAConfiguration:
@ -54,7 +52,6 @@ class ExploitationOptionsConfiguration:
class ExploiterConfiguration: class ExploiterConfiguration:
name: str name: str
options: Dict options: Dict
supported_os: List[OperatingSystems]
@dataclass(frozen=True) @dataclass(frozen=True)

View File

@ -157,60 +157,44 @@ DEFAULT_AGENT_CONFIGURATION_JSON = """{
"brute_force": [ "brute_force": [
{ {
"name": "MSSQLExploiter", "name": "MSSQLExploiter",
"options": {}, "options": {}
"supported_os": [
"WINDOWS"
]
}, },
{ {
"name": "PowerShellExploiter", "name": "PowerShellExploiter",
"options": {}, "options": {}
"supported_os": [
"WINDOWS"
]
}, },
{ {
"name": "SSHExploiter", "name": "SSHExploiter",
"options": {}, "options": {}
"supported_os": [
"LINUX"
]
}, },
{ {
"name": "SmbExploiter", "name": "SmbExploiter",
"options": { "options": {
"smb_download_timeout": 30 "smb_download_timeout": 30
}, }
"supported_os": [
"WINDOWS"
]
}, },
{ {
"name": "WmiExploiter", "name": "WmiExploiter",
"options": { "options": {
"smb_download_timeout": 30 "smb_download_timeout": 30
}, }
"supported_os": [
"WINDOWS"
]
} }
], ],
"vulnerability": [ "vulnerability": [
{ {
"name": "HadoopExploiter", "name": "HadoopExploiter",
"options": {}, "options": {}
"supported_os": [
"LINUX",
"WINDOWS"
]
}, },
{ {
"name": "Log4ShellExploiter", "name": "Log4ShellExploiter",
"options": {}, "options": {}
"supported_os": [
"LINUX",
"WINDOWS"
]
} }
] ]
} }

View File

@ -7,6 +7,7 @@ from queue import Queue
from threading import Event from threading import Event
from typing import Callable, Dict, List, Mapping from typing import Callable, Dict, List, Mapping
from common import OperatingSystems
from infection_monkey.custom_types import PropagationCredentials from infection_monkey.custom_types import PropagationCredentials
from infection_monkey.i_puppet import ExploiterResultData, IPuppet from infection_monkey.i_puppet import ExploiterResultData, IPuppet
from infection_monkey.model import VictimHost from infection_monkey.model import VictimHost
@ -20,6 +21,18 @@ ExploiterName = str
Callback = Callable[[ExploiterName, VictimHost, ExploiterResultData], None] Callback = Callable[[ExploiterName, VictimHost, ExploiterResultData], None]
SUPPORTED_OS = {
"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],
}
class Exploiter: class Exploiter:
def __init__( def __init__(
self, self,
@ -118,7 +131,8 @@ class Exploiter:
victim_os = victim_host.os.get("type") victim_os = victim_host.os.get("type")
# We want to try all exploiters if the victim's OS is unknown # We want to try all exploiters if the victim's OS is unknown
if victim_os is not None and victim_os not in exploiter["supported_os"]: print(victim_os)
if victim_os is not None and victim_os not in SUPPORTED_OS[exploiter_name]:
logger.debug( logger.debug(
f"Skipping {exploiter_name} because it does not support " f"Skipping {exploiter_name} because it does not support "
f"the victim's OS ({victim_os})" f"the victim's OS ({victim_os})"

View File

@ -12,6 +12,7 @@ from common import DIContainer
from monkey_island.cc.database import database, mongo from monkey_island.cc.database import database, mongo
from monkey_island.cc.resources import AgentBinaries, RemoteRun from monkey_island.cc.resources import AgentBinaries, RemoteRun
from monkey_island.cc.resources.AbstractResource import AbstractResource from monkey_island.cc.resources.AbstractResource import AbstractResource
from monkey_island.cc.resources.agent_configuration import AgentConfiguration
from monkey_island.cc.resources.agent_controls import StopAgentCheck, StopAllAgents from monkey_island.cc.resources.agent_controls import StopAgentCheck, StopAllAgents
from monkey_island.cc.resources.attack.attack_report import AttackReport from monkey_island.cc.resources.attack.attack_report import AttackReport
from monkey_island.cc.resources.auth.auth import Authenticate, init_jwt from monkey_island.cc.resources.auth.auth import Authenticate, init_jwt
@ -155,6 +156,7 @@ def init_api_resources(api: FlaskDIWrapper):
api.add_resource(IslandConfiguration) api.add_resource(IslandConfiguration)
api.add_resource(ConfigurationExport) api.add_resource(ConfigurationExport)
api.add_resource(ConfigurationImport) api.add_resource(ConfigurationImport)
api.add_resource(AgentConfiguration)
api.add_resource(AgentBinaries) api.add_resource(AgentBinaries)
api.add_resource(NetMap) api.add_resource(NetMap)
api.add_resource(Edge) api.add_resource(Edge)

View File

@ -0,0 +1,36 @@
import json
import marshmallow
from flask import make_response, request
from common.configuration.agent_configuration import AgentConfigurationSchema
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
class AgentConfiguration(AbstractResource):
urls = ["/api/agent-configuration"]
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)
return make_response(configuration_json, 200)
@jwt_required
def post(self):
try:
configuration_object = self._schema.loads(request.data)
self._agent_configuration_repository.store_configuration(configuration_object)
return make_response({}, 200)
except (marshmallow.exceptions.ValidationError, json.JSONDecodeError) as err:
return make_response(
{"message": f"Invalid configuration supplied: {err}"},
400,
)

View File

@ -3,12 +3,10 @@ import copy
import functools import functools
import logging import logging
import re import re
from itertools import chain
from typing import Any, Dict, List from typing import Any, Dict, List
from jsonschema import Draft4Validator, validators from jsonschema import Draft4Validator, validators
from common import OperatingSystems
from common.config_value_paths import ( from common.config_value_paths import (
LM_HASH_LIST_PATH, LM_HASH_LIST_PATH,
NTLM_HASH_LIST_PATH, NTLM_HASH_LIST_PATH,
@ -580,7 +578,7 @@ class ConfigService:
formatted_exploiters_config = ConfigService._add_smb_download_timeout_to_exploiters( formatted_exploiters_config = ConfigService._add_smb_download_timeout_to_exploiters(
formatted_exploiters_config formatted_exploiters_config
) )
return ConfigService._add_supported_os_to_exploiters(formatted_exploiters_config) return formatted_exploiters_config
@staticmethod @staticmethod
def _add_smb_download_timeout_to_exploiters( def _add_smb_download_timeout_to_exploiters(
@ -593,23 +591,3 @@ class ConfigService:
exploiter["options"]["smb_download_timeout"] = SMB_DOWNLOAD_TIMEOUT exploiter["options"]["smb_download_timeout"] = SMB_DOWNLOAD_TIMEOUT
return new_config return new_config
@staticmethod
def _add_supported_os_to_exploiters(
formatted_config: Dict,
) -> Dict[str, List[Dict[str, Any]]]:
supported_os = {
"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"]):
exploiter["supported_os"] = supported_os.get(exploiter["name"], [])
return new_config

View File

@ -39,18 +39,16 @@ NETWORK_SCAN_CONFIGURATION = {
} }
BRUTE_FORCE = [ BRUTE_FORCE = [
{"name": "ex1", "options": {}, "supported_os": ["LINUX"]}, {"name": "ex1", "options": {}},
{ {
"name": "ex2", "name": "ex2",
"options": {"smb_download_timeout": 10}, "options": {"smb_download_timeout": 10},
"supported_os": ["LINUX", "WINDOWS"],
}, },
] ]
VULNERABILITY = [ VULNERABILITY = [
{ {
"name": "ex3", "name": "ex3",
"options": {"smb_download_timeout": 10}, "options": {"smb_download_timeout": 10},
"supported_os": ["WINDOWS"],
}, },
] ]
EXPLOITATION_CONFIGURATION = { EXPLOITATION_CONFIGURATION = {

View File

@ -47,16 +47,16 @@
"exploiters": { "exploiters": {
"options": {}, "options": {},
"brute_force": [ "brute_force": [
{"name": "MSSQLExploiter", "supported_os": ["windows"], "options": {}}, {"name": "MSSQLExploiter", "options": {}},
{"name": "PowerShellExploiter", "supported_os": ["windows"], "options": {}}, {"name": "PowerShellExploiter", "options": {}},
{"name": "SmbExploiter", "supported_os": ["windows"], "options": {}}, {"name": "SmbExploiter", "options": {}},
{"name": "SSHExploiter", "supported_os": ["linux"], "options": {}}, {"name": "SSHExploiter", "options": {}},
{"name": "WmiExploiter", "supported_os": ["windows"], "options": {}} {"name": "WmiExploiter", "options": {}}
], ],
"vulnerability": [ "vulnerability": [
{"name": "HadoopExploiter", "supported_os": ["linux", "windows"], "options": {}}, {"name": "HadoopExploiter", "options": {}},
{"name": "ShellShockExploiter", "supported_os": ["linux"], "options": {}}, {"name": "ShellShockExploiter", "options": {}},
{"name": "ZerologonExploiter", "supported_os": ["windows"], "options": {}} {"name": "ZerologonExploiter", "options": {}}
] ]
} }
}, },

View File

@ -1,3 +1,4 @@
from .single_file_repository import SingleFileRepository from .single_file_repository import SingleFileRepository
from .mock_file_repository import MockFileRepository, FILE_CONTENTS, FILE_NAME from .mock_file_repository import MockFileRepository, FILE_CONTENTS, FILE_NAME
from .open_error_file_repository import OpenErrorFileRepository from .open_error_file_repository import OpenErrorFileRepository
from .in_memory_agent_configuration_repository import InMemoryAgentConfigurationRepository

View File

@ -0,0 +1,15 @@
from tests.common.example_agent_configuration import AGENT_CONFIGURATION
from common.configuration.agent_configuration import AgentConfigurationSchema
from monkey_island.cc.repository import IAgentConfigurationRepository
class InMemoryAgentConfigurationRepository(IAgentConfigurationRepository):
def __init__(self):
self._configuration = AgentConfigurationSchema().load(AGENT_CONFIGURATION)
def get_configuration(self):
return self._configuration
def store_configuration(self, agent_configuration):
self._configuration = agent_configuration

View File

@ -23,7 +23,6 @@ from tests.common.example_agent_configuration import (
WINDOWS_FILENAME, WINDOWS_FILENAME,
) )
from common import OperatingSystems
from common.configuration import ( from common.configuration import (
DEFAULT_AGENT_CONFIGURATION_JSON, DEFAULT_AGENT_CONFIGURATION_JSON,
AgentConfiguration, AgentConfiguration,
@ -126,16 +125,12 @@ def test_exploitation_options_configuration_schema():
def test_exploiter_configuration_schema(): def test_exploiter_configuration_schema():
name = "bond" name = "bond"
options = {"gun": "Walther PPK", "car": "Aston Martin DB5"} options = {"gun": "Walther PPK", "car": "Aston Martin DB5"}
supported_os = [OperatingSystems.LINUX, OperatingSystems.WINDOWS]
schema = ExploiterConfigurationSchema() schema = ExploiterConfigurationSchema()
config = schema.load( config = schema.load({"name": name, "options": options})
{"name": name, "options": options, "supported_os": [os_.name for os_ in supported_os]}
)
assert config.name == name assert config.name == name
assert config.options == options assert config.options == options
assert config.supported_os == supported_os
def test_exploitation_configuration(): def test_exploitation_configuration():

View File

@ -2,6 +2,7 @@ from unittest.mock import Mock
import pytest import pytest
from common import OperatingSystems
from infection_monkey.exploit.tools.helpers import ( from infection_monkey.exploit.tools.helpers import (
AGENT_BINARY_PATH_LINUX, AGENT_BINARY_PATH_LINUX,
AGENT_BINARY_PATH_WIN64, AGENT_BINARY_PATH_WIN64,
@ -13,22 +14,28 @@ from infection_monkey.exploit.tools.helpers import (
def _get_host(os): def _get_host(os):
host = Mock() host = Mock()
host.os = {"type": os} host.os = {"type": os}
host.is_windows = lambda: os == OperatingSystems.WINDOWS
return host return host
@pytest.mark.parametrize( @pytest.mark.parametrize(
"os, path", [("linux", AGENT_BINARY_PATH_LINUX), ("windows", AGENT_BINARY_PATH_WIN64)] "os, path",
[
(OperatingSystems.LINUX, AGENT_BINARY_PATH_LINUX),
(OperatingSystems.WINDOWS, AGENT_BINARY_PATH_WIN64),
],
) )
def test_get_agent_dst_path(os, path): def test_get_agent_dst_path(os, path):
host = _get_host(os) host = _get_host(os)
rand_path = get_agent_dst_path(host) rand_path = get_agent_dst_path(host)
print(f"{os}: {rand_path}")
# Assert that filename got longer by RAND_SUFFIX_LEN and one dash # Assert that filename got longer by RAND_SUFFIX_LEN and one dash
assert len(str(rand_path)) == (len(str(path)) + RAND_SUFFIX_LEN + 1) assert len(str(rand_path)) == (len(str(path)) + RAND_SUFFIX_LEN + 1)
def test_get_agent_dst_path_randomness(): def test_get_agent_dst_path_randomness():
host = _get_host("windows") host = _get_host(OperatingSystems.WINDOWS)
path1 = get_agent_dst_path(host) path1 = get_agent_dst_path(host)
path2 = get_agent_dst_path(host) path2 = get_agent_dst_path(host)
@ -37,7 +44,7 @@ def test_get_agent_dst_path_randomness():
def test_get_agent_dst_path_str_place(): def test_get_agent_dst_path_str_place():
host = _get_host("windows") host = _get_host(OperatingSystems.WINDOWS)
rand_path = get_agent_dst_path(host) rand_path = get_agent_dst_path(host)

View File

@ -39,24 +39,12 @@ def exploiter_config():
return { return {
"options": {"dropper_path_linux": "/tmp/monkey"}, "options": {"dropper_path_linux": "/tmp/monkey"},
"brute_force": [ "brute_force": [
{ {"name": "MSSQLExploiter", "options": {"timeout": 10}},
"name": "HadoopExploiter", {"name": "SSHExploiter", "options": {}},
"supported_os": [OperatingSystems.WINDOWS], {"name": "WmiExploiter", "options": {"timeout": 10}},
"options": {"timeout": 10},
},
{"name": "SSHExploiter", "supported_os": [OperatingSystems.LINUX], "options": {}},
{
"name": "WmiExploiter",
"supported_os": [OperatingSystems.WINDOWS],
"options": {"timeout": 10},
},
], ],
"vulnerability": [ "vulnerability": [
{ {"name": "ZerologonExploiter", "options": {}},
"name": "ZerologonExploiter",
"supported_os": [OperatingSystems.WINDOWS],
"options": {},
},
], ],
} }
@ -118,11 +106,11 @@ def test_exploiter(callback, hosts, hosts_to_exploit, run_exploiters):
host_exploit_combos = get_host_exploit_combos_from_call_args_list(callback.call_args_list) host_exploit_combos = get_host_exploit_combos_from_call_args_list(callback.call_args_list)
assert ("ZerologonExploiter", hosts[0]) in host_exploit_combos assert ("ZerologonExploiter", hosts[0]) in host_exploit_combos
assert ("HadoopExploiter", hosts[0]) in host_exploit_combos assert ("MSSQLExploiter", hosts[0]) in host_exploit_combos
assert ("SSHExploiter", hosts[0]) in host_exploit_combos assert ("SSHExploiter", hosts[0]) in host_exploit_combos
assert ("WmiExploiter", hosts[0]) in host_exploit_combos assert ("WmiExploiter", hosts[0]) in host_exploit_combos
assert ("ZerologonExploiter", hosts[1]) in host_exploit_combos assert ("ZerologonExploiter", hosts[1]) in host_exploit_combos
assert ("HadoopExploiter", hosts[1]) in host_exploit_combos assert ("MSSQLExploiter", hosts[1]) in host_exploit_combos
assert ("WmiExploiter", hosts[1]) in host_exploit_combos assert ("WmiExploiter", hosts[1]) in host_exploit_combos
assert ("SSHExploiter", hosts[1]) in host_exploit_combos assert ("SSHExploiter", hosts[1]) in host_exploit_combos
@ -209,6 +197,6 @@ def test_all_exploiters_run_on_unknown_host(callback, hosts, hosts_to_exploit, r
host_exploit_combos = get_host_exploit_combos_from_call_args_list(callback.call_args_list) host_exploit_combos = get_host_exploit_combos_from_call_args_list(callback.call_args_list)
assert ("ZerologonExploiter", hosts[0]) in host_exploit_combos assert ("ZerologonExploiter", hosts[0]) in host_exploit_combos
assert ("HadoopExploiter", hosts[0]) in host_exploit_combos assert ("MSSQLExploiter", hosts[0]) in host_exploit_combos
assert ("SSHExploiter", host) in host_exploit_combos assert ("SSHExploiter", host) in host_exploit_combos
assert ("WmiExploiter", hosts[0]) in host_exploit_combos assert ("WmiExploiter", hosts[0]) in host_exploit_combos

View File

@ -0,0 +1,50 @@
import json
import pytest
from tests.common import StubDIContainer
from tests.common.example_agent_configuration import AGENT_CONFIGURATION
from tests.monkey_island import InMemoryAgentConfigurationRepository
from tests.unit_tests.monkey_island.conftest import get_url_for_resource
from monkey_island.cc.repository import IAgentConfigurationRepository
from monkey_island.cc.resources.agent_configuration import AgentConfiguration
@pytest.fixture
def flask_client(build_flask_client):
container = StubDIContainer()
container.register(IAgentConfigurationRepository, InMemoryAgentConfigurationRepository)
with build_flask_client(container) as flask_client:
yield flask_client
def test_agent_configuration_endpoint(flask_client):
agent_configuration_url = get_url_for_resource(AgentConfiguration)
flask_client.post(
agent_configuration_url, data=json.dumps(AGENT_CONFIGURATION), follow_redirects=True
)
resp = flask_client.get(agent_configuration_url)
assert resp.status_code == 200
assert json.loads(resp.data) == AGENT_CONFIGURATION
def test_agent_configuration_invalid_config(flask_client):
agent_configuration_url = get_url_for_resource(AgentConfiguration)
resp = flask_client.post(
agent_configuration_url, data=json.dumps({"invalid_config": "invalid_stuff"})
)
assert resp.status_code == 400
def test_agent_configuration_invalid_json(flask_client):
agent_configuration_url = get_url_for_resource(AgentConfiguration)
resp = flask_client.post(agent_configuration_url, data="InvalidJson!")
assert resp.status_code == 400

View File

@ -1,6 +1,5 @@
import pytest import pytest
from common import OperatingSystems
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
@ -172,38 +171,32 @@ def test_format_config_for_agent__exploiters():
"http_ports": [80, 443, 7001, 8008, 8080, 9200], "http_ports": [80, 443, 7001, 8008, 8080, 9200],
}, },
"brute_force": [ "brute_force": [
{"name": "MSSQLExploiter", "supported_os": [OperatingSystems.WINDOWS], "options": {}}, {"name": "MSSQLExploiter", "options": {}},
{ {
"name": "PowerShellExploiter", "name": "PowerShellExploiter",
"supported_os": [OperatingSystems.WINDOWS],
"options": {}, "options": {},
}, },
{"name": "SSHExploiter", "supported_os": [OperatingSystems.LINUX], "options": {}}, {"name": "SSHExploiter", "options": {}},
{ {
"name": "SmbExploiter", "name": "SmbExploiter",
"supported_os": [OperatingSystems.WINDOWS],
"options": {"smb_download_timeout": 30}, "options": {"smb_download_timeout": 30},
}, },
{ {
"name": "WmiExploiter", "name": "WmiExploiter",
"supported_os": [OperatingSystems.WINDOWS],
"options": {"smb_download_timeout": 30}, "options": {"smb_download_timeout": 30},
}, },
], ],
"vulnerability": [ "vulnerability": [
{ {
"name": "HadoopExploiter", "name": "HadoopExploiter",
"supported_os": [OperatingSystems.LINUX, OperatingSystems.WINDOWS],
"options": {}, "options": {},
}, },
{ {
"name": "Log4ShellExploiter", "name": "Log4ShellExploiter",
"supported_os": [OperatingSystems.LINUX, OperatingSystems.WINDOWS],
"options": {}, "options": {},
}, },
{ {
"name": "ZerologonExploiter", "name": "ZerologonExploiter",
"supported_os": [OperatingSystems.WINDOWS],
"options": {}, "options": {},
}, },
], ],