forked from p15670423/monkey
Merge branch '2000-configuration-resource' into 1960-configuration-object
PR #2038
This commit is contained in:
commit
d079d74b2c
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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": {}
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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})"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
)
|
|
@ -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
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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": {}}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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():
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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": {},
|
||||
},
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue