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_enum import EnumField
from common import OperatingSystems
from .agent_sub_configurations import (
CustomPBAConfiguration,
@ -87,7 +84,6 @@ class ExploitationOptionsConfigurationSchema(Schema):
class ExploiterConfigurationSchema(Schema):
name = fields.Str()
options = fields.Mapping()
supported_os = fields.List(EnumField(OperatingSystems))
@post_load
def _make_exploiter_configuration(self, data, **kwargs):

View File

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

View File

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

View File

@ -7,6 +7,7 @@ from queue import Queue
from threading import Event
from typing import Callable, Dict, List, Mapping
from common import OperatingSystems
from infection_monkey.custom_types import PropagationCredentials
from infection_monkey.i_puppet import ExploiterResultData, IPuppet
from infection_monkey.model import VictimHost
@ -20,6 +21,18 @@ ExploiterName = str
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:
def __init__(
self,
@ -118,7 +131,8 @@ class Exploiter:
victim_os = victim_host.os.get("type")
# 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(
f"Skipping {exploiter_name} because it does not support "
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.resources import AgentBinaries, RemoteRun
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.attack.attack_report import AttackReport
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(ConfigurationExport)
api.add_resource(ConfigurationImport)
api.add_resource(AgentConfiguration)
api.add_resource(AgentBinaries)
api.add_resource(NetMap)
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 logging
import re
from itertools import chain
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,
@ -580,7 +578,7 @@ class ConfigService:
formatted_exploiters_config = ConfigService._add_smb_download_timeout_to_exploiters(
formatted_exploiters_config
)
return ConfigService._add_supported_os_to_exploiters(formatted_exploiters_config)
return formatted_exploiters_config
@staticmethod
def _add_smb_download_timeout_to_exploiters(
@ -593,23 +591,3 @@ class ConfigService:
exploiter["options"]["smb_download_timeout"] = SMB_DOWNLOAD_TIMEOUT
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 = [
{"name": "ex1", "options": {}, "supported_os": ["LINUX"]},
{"name": "ex1", "options": {}},
{
"name": "ex2",
"options": {"smb_download_timeout": 10},
"supported_os": ["LINUX", "WINDOWS"],
},
]
VULNERABILITY = [
{
"name": "ex3",
"options": {"smb_download_timeout": 10},
"supported_os": ["WINDOWS"],
},
]
EXPLOITATION_CONFIGURATION = {

View File

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

View File

@ -1,3 +1,4 @@
from .single_file_repository import SingleFileRepository
from .mock_file_repository import MockFileRepository, FILE_CONTENTS, FILE_NAME
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,
)
from common import OperatingSystems
from common.configuration import (
DEFAULT_AGENT_CONFIGURATION_JSON,
AgentConfiguration,
@ -126,16 +125,12 @@ def test_exploitation_options_configuration_schema():
def test_exploiter_configuration_schema():
name = "bond"
options = {"gun": "Walther PPK", "car": "Aston Martin DB5"}
supported_os = [OperatingSystems.LINUX, OperatingSystems.WINDOWS]
schema = ExploiterConfigurationSchema()
config = schema.load(
{"name": name, "options": options, "supported_os": [os_.name for os_ in supported_os]}
)
config = schema.load({"name": name, "options": options})
assert config.name == name
assert config.options == options
assert config.supported_os == supported_os
def test_exploitation_configuration():

View File

@ -2,6 +2,7 @@ from unittest.mock import Mock
import pytest
from common import OperatingSystems
from infection_monkey.exploit.tools.helpers import (
AGENT_BINARY_PATH_LINUX,
AGENT_BINARY_PATH_WIN64,
@ -13,22 +14,28 @@ from infection_monkey.exploit.tools.helpers import (
def _get_host(os):
host = Mock()
host.os = {"type": os}
host.is_windows = lambda: os == OperatingSystems.WINDOWS
return host
@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):
host = _get_host(os)
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 len(str(rand_path)) == (len(str(path)) + RAND_SUFFIX_LEN + 1)
def test_get_agent_dst_path_randomness():
host = _get_host("windows")
host = _get_host(OperatingSystems.WINDOWS)
path1 = 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():
host = _get_host("windows")
host = _get_host(OperatingSystems.WINDOWS)
rand_path = get_agent_dst_path(host)

View File

@ -39,24 +39,12 @@ def exploiter_config():
return {
"options": {"dropper_path_linux": "/tmp/monkey"},
"brute_force": [
{
"name": "HadoopExploiter",
"supported_os": [OperatingSystems.WINDOWS],
"options": {"timeout": 10},
},
{"name": "SSHExploiter", "supported_os": [OperatingSystems.LINUX], "options": {}},
{
"name": "WmiExploiter",
"supported_os": [OperatingSystems.WINDOWS],
"options": {"timeout": 10},
},
{"name": "MSSQLExploiter", "options": {"timeout": 10}},
{"name": "SSHExploiter", "options": {}},
{"name": "WmiExploiter", "options": {"timeout": 10}},
],
"vulnerability": [
{
"name": "ZerologonExploiter",
"supported_os": [OperatingSystems.WINDOWS],
"options": {},
},
{"name": "ZerologonExploiter", "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)
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 ("WmiExploiter", hosts[0]) 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 ("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)
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 ("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
from common import OperatingSystems
from monkey_island.cc.services.config import ConfigService
# 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],
},
"brute_force": [
{"name": "MSSQLExploiter", "supported_os": [OperatingSystems.WINDOWS], "options": {}},
{"name": "MSSQLExploiter", "options": {}},
{
"name": "PowerShellExploiter",
"supported_os": [OperatingSystems.WINDOWS],
"options": {},
},
{"name": "SSHExploiter", "supported_os": [OperatingSystems.LINUX], "options": {}},
{"name": "SSHExploiter", "options": {}},
{
"name": "SmbExploiter",
"supported_os": [OperatingSystems.WINDOWS],
"options": {"smb_download_timeout": 30},
},
{
"name": "WmiExploiter",
"supported_os": [OperatingSystems.WINDOWS],
"options": {"smb_download_timeout": 30},
},
],
"vulnerability": [
{
"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": {},
},
],