forked from p15670423/monkey
Merge pull request #2103 from guardicore/2092-modify-blackbox-tests
Modify BB tests to use new configuration objects
This commit is contained in:
commit
4d3fb03da2
|
@ -1,2 +1 @@
|
|||
logs/
|
||||
/blackbox/tests/performance/telemetry_sample
|
||||
|
|
|
@ -16,8 +16,6 @@ Either run pytest from `/monkey` directory or set `PYTHONPATH` environment varia
|
|||
Blackbox tests have following parameters:
|
||||
- `--island=IP` Sets island's IP
|
||||
- `--no-gcp` (Optional) Use for no interaction with the cloud (local test).
|
||||
- `--quick-performance-tests` (Optional) If enabled performance tests won't reset island and won't send telemetries,
|
||||
instead will just test performance of endpoints in already present island state.
|
||||
|
||||
Example run command:
|
||||
|
||||
|
@ -26,26 +24,3 @@ Example run command:
|
|||
#### Running in PyCharm
|
||||
Configure a PyTest configuration with the additional arguments `-s --island=35.207.152.72:5000`, and to run from
|
||||
directory `monkey\envs\monkey_zoo\blackbox`.
|
||||
|
||||
### Running telemetry performance test
|
||||
|
||||
**Before running performance test make sure browser is not sending requests to island!**
|
||||
|
||||
To run telemetry performance test follow these steps:
|
||||
0. Set no password protection on the island.
|
||||
Make sure the island parameter is an IP address(not localhost) as the name resolution will increase the time for requests.
|
||||
1. Gather monkey telemetries.
|
||||
1. Enable "Export monkey telemetries" in Configuration -> Internal -> Tests if you don't have
|
||||
exported telemetries already.
|
||||
2. Run monkey and wait until infection is done.
|
||||
3. All telemetries are gathered in `monkey/telem_sample`. If not, restart the island process.
|
||||
2. Run telemetry performance test.
|
||||
1. Move directory `monkey/telem_sample` to `envs/monkey_zoo/blackbox/tests/performance/telemetry_sample`
|
||||
2. (Optional) Use `envs/monkey_zoo/blackbox/tests/performance/telem_sample_parsing/sample_multiplier/sample_multiplier.py` to multiply
|
||||
telemetries gathered.
|
||||
1. Run `sample_multiplier.py` script with working directory set to `monkey\envs\monkey_zoo\blackbox`
|
||||
2. Pass integer to indicate the multiplier. For example running `telem_parser.py 4` will replicate
|
||||
telemetries 4 times.
|
||||
3. If you're using pycharm check "Emulate terminal in output console" on debug/run configuration.
|
||||
3. Add a `--run-performance-tests` flag to blackbox scripts to run performance tests as part of BlackBox tests.
|
||||
You can run a single test separately by adding `-k 'test_telem_performance'` option.
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
from typing import Iterable
|
||||
|
||||
from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer
|
||||
from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog
|
||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||
|
||||
|
||||
class CommunicationAnalyzer(Analyzer):
|
||||
def __init__(self, island_client, machine_ips):
|
||||
def __init__(self, island_client: MonkeyIslandClient, machine_ips: Iterable[str]):
|
||||
self.island_client = island_client
|
||||
self.machine_ips = machine_ips
|
||||
self.log = AnalyzerLog(self.__class__.__name__)
|
||||
|
@ -19,6 +22,6 @@ class CommunicationAnalyzer(Analyzer):
|
|||
self.log.add_entry("Monkey from {} communicated back".format(machine_ip))
|
||||
return all_monkeys_communicated
|
||||
|
||||
def did_monkey_communicate_back(self, machine_ip):
|
||||
def did_monkey_communicate_back(self, machine_ip: str):
|
||||
query = {"ip_addresses": {"$elemMatch": {"$eq": machine_ip}}}
|
||||
return len(self.island_client.find_monkeys_in_db(query)) > 0
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
import logging
|
||||
from datetime import timedelta
|
||||
from typing import Dict
|
||||
|
||||
from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer
|
||||
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PerformanceAnalyzer(Analyzer):
|
||||
def __init__(
|
||||
self, performance_test_config: PerformanceTestConfig, endpoint_timings: Dict[str, timedelta]
|
||||
):
|
||||
self.performance_test_config = performance_test_config
|
||||
self.endpoint_timings = endpoint_timings
|
||||
|
||||
def analyze_test_results(self):
|
||||
# Calculate total time and check each endpoint
|
||||
single_page_time_less_then_max = True
|
||||
total_time = timedelta()
|
||||
for endpoint, elapsed in self.endpoint_timings.items():
|
||||
total_time += elapsed
|
||||
if elapsed > self.performance_test_config.max_allowed_single_page_time:
|
||||
single_page_time_less_then_max = False
|
||||
|
||||
total_time_less_then_max = total_time < self.performance_test_config.max_allowed_total_time
|
||||
|
||||
PerformanceAnalyzer.log_slowest_endpoints(self.endpoint_timings)
|
||||
LOGGER.info(f"Total time is {str(total_time)}")
|
||||
|
||||
performance_is_good_enough = total_time_less_then_max and single_page_time_less_then_max
|
||||
|
||||
if self.performance_test_config.break_on_timeout and not performance_is_good_enough:
|
||||
LOGGER.warning(
|
||||
"Calling breakpoint - pausing to enable investigation of island. "
|
||||
"Type 'c' to continue once you're done "
|
||||
"investigating. Type 'p timings' and 'p total_time' to see performance information."
|
||||
)
|
||||
breakpoint()
|
||||
|
||||
return performance_is_good_enough
|
||||
|
||||
@staticmethod
|
||||
def log_slowest_endpoints(endpoint_timings, max_endpoints_to_display=100):
|
||||
slow_endpoint_list = list(endpoint_timings.items())
|
||||
slow_endpoint_list.sort(key=lambda x: x[1], reverse=True)
|
||||
slow_endpoint_list = slow_endpoint_list[:max_endpoints_to_display]
|
||||
for endpoint in slow_endpoint_list:
|
||||
LOGGER.info(f"{endpoint[0]} took {str(endpoint[1])}")
|
|
@ -1,9 +1,6 @@
|
|||
from pprint import pformat
|
||||
from typing import List
|
||||
|
||||
import dpath.util
|
||||
|
||||
from common.config_value_paths import LM_HASH_LIST_PATH, NTLM_HASH_LIST_PATH, USER_LIST_PATH
|
||||
from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer
|
||||
from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog
|
||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||
|
@ -36,9 +33,11 @@ class ZerologonAnalyzer(Analyzer):
|
|||
@staticmethod
|
||||
def _get_relevant_credentials(config: dict):
|
||||
credentials_on_island = []
|
||||
credentials_on_island.extend(dpath.util.get(config["configuration"], USER_LIST_PATH))
|
||||
credentials_on_island.extend(dpath.util.get(config["configuration"], NTLM_HASH_LIST_PATH))
|
||||
credentials_on_island.extend(dpath.util.get(config["configuration"], LM_HASH_LIST_PATH))
|
||||
# TODO: Pull configured credentials and put usernames, nt and lm hashes into
|
||||
# credentials_island
|
||||
# credentials_on_island.extend(dpath.util.get(config["configuration"], USER_LIST_PATH))
|
||||
# credentials_on_island.extend(dpath.util.get(config["configuration"], NTLM_HASH_LIST_PATH))
|
||||
# credentials_on_island.extend(dpath.util.get(config["configuration"], LM_HASH_LIST_PATH))
|
||||
return credentials_on_island
|
||||
|
||||
def _is_all_credentials_in_list(self, all_creds: List[str]) -> bool:
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate
|
||||
|
||||
|
||||
# Disables a lot of config values not required for a specific feature test
|
||||
class BaseTemplate(ConfigTemplate):
|
||||
|
||||
config_values = {
|
||||
"basic.exploiters.exploiter_classes": [],
|
||||
"basic_network.scope.local_network_scan": False,
|
||||
"basic_network.scope.depth": 1,
|
||||
"internal.classes.finger_classes": ["HTTPFinger"],
|
||||
"internal.monkey.system_info.system_info_collector_classes": [],
|
||||
"monkey.post_breach.post_breach_actions": [],
|
||||
"internal.general.keep_tunnel_open_time": 0,
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class ConfigTemplate(ABC):
|
||||
@property
|
||||
@abstractmethod
|
||||
def config_values(self) -> dict:
|
||||
pass
|
|
@ -1,42 +0,0 @@
|
|||
from copy import copy
|
||||
|
||||
from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate
|
||||
from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate
|
||||
|
||||
|
||||
class Depth1A(ConfigTemplate):
|
||||
config_values = copy(BaseTemplate.config_values)
|
||||
# Tests:
|
||||
# Hadoop (10.2.2.2, 10.2.2.3)
|
||||
# Log4shell (10.2.3.55, 10.2.3.56, 10.2.3.49, 10.2.3.50, 10.2.3.51, 10.2.3.52)
|
||||
# MSSQL (10.2.2.16)
|
||||
# SMB mimikatz password stealing and brute force (10.2.2.14 and 10.2.2.15)
|
||||
config_values.update(
|
||||
{
|
||||
"basic.exploiters.exploiter_classes": [
|
||||
"HadoopExploiter",
|
||||
"Log4ShellExploiter",
|
||||
"MSSQLExploiter",
|
||||
"SmbExploiter",
|
||||
"SSHExploiter",
|
||||
],
|
||||
"basic_network.scope.subnet_scan_list": [
|
||||
"10.2.2.2",
|
||||
"10.2.2.3",
|
||||
"10.2.3.55",
|
||||
"10.2.3.56",
|
||||
"10.2.3.49",
|
||||
"10.2.3.50",
|
||||
"10.2.3.51",
|
||||
"10.2.3.52",
|
||||
"10.2.2.16",
|
||||
"10.2.2.14",
|
||||
"10.2.2.15",
|
||||
],
|
||||
"basic.credentials.exploit_password_list": ["Ivrrw5zEzs", "Xk8VDTsC"],
|
||||
"basic.credentials.exploit_user_list": ["m0nk3y"],
|
||||
"monkey.system_info.system_info_collector_classes": [
|
||||
"MimikatzCollector",
|
||||
],
|
||||
}
|
||||
)
|
|
@ -1,23 +0,0 @@
|
|||
from copy import copy
|
||||
|
||||
from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate
|
||||
from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate
|
||||
|
||||
|
||||
class Depth2A(ConfigTemplate):
|
||||
config_values = copy(BaseTemplate.config_values)
|
||||
# SSH password and key brute-force, key stealing (10.2.2.11, 10.2.2.12)
|
||||
config_values.update(
|
||||
{
|
||||
"basic.exploiters.exploiter_classes": [
|
||||
"SSHExploiter",
|
||||
],
|
||||
"basic_network.scope.subnet_scan_list": [
|
||||
"10.2.2.11",
|
||||
"10.2.2.12",
|
||||
],
|
||||
"basic_network.scope.depth": 2,
|
||||
"basic.credentials.exploit_password_list": ["^NgDvY59~8"],
|
||||
"basic.credentials.exploit_user_list": ["m0nk3y"],
|
||||
}
|
||||
)
|
|
@ -1,48 +0,0 @@
|
|||
from copy import copy
|
||||
|
||||
from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate
|
||||
from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate
|
||||
|
||||
|
||||
class Depth3A(ConfigTemplate):
|
||||
config_values = copy(BaseTemplate.config_values)
|
||||
|
||||
# Tests:
|
||||
# Powershell (10.2.3.45, 10.2.3.46, 10.2.3.47, 10.2.3.48)
|
||||
# Tunneling (SSH brute force) (10.2.2.9, 10.2.1.10, 10.2.0.12, 10.2.0.11)
|
||||
# WMI pass the hash (10.2.2.15)
|
||||
config_values.update(
|
||||
{
|
||||
"basic.exploiters.exploiter_classes": [
|
||||
"PowerShellExploiter",
|
||||
"SSHExploiter",
|
||||
"WmiExploiter",
|
||||
],
|
||||
"basic_network.scope.subnet_scan_list": [
|
||||
"10.2.2.9",
|
||||
"10.2.3.45",
|
||||
"10.2.3.46",
|
||||
"10.2.3.47",
|
||||
"10.2.3.48",
|
||||
"10.2.1.10",
|
||||
"10.2.0.12",
|
||||
"10.2.0.11",
|
||||
"10.2.2.15",
|
||||
],
|
||||
"basic.credentials.exploit_password_list": [
|
||||
"Passw0rd!",
|
||||
"3Q=(Ge(+&w]*",
|
||||
"`))jU7L(w}",
|
||||
"t67TC5ZDmz",
|
||||
],
|
||||
"basic_network.scope.depth": 3,
|
||||
"internal.general.keep_tunnel_open_time": 20,
|
||||
"basic.credentials.exploit_user_list": ["m0nk3y", "m0nk3y-user"],
|
||||
"internal.network.tcp_scanner.HTTP_PORTS": [],
|
||||
"internal.exploits.exploit_ntlm_hash_list": [
|
||||
"d0f0132b308a0c4e5d1029cc06f48692",
|
||||
"5da0889ea2081aa79f6852294cba4a5e",
|
||||
"50c9987a6bf1ac59398df9f911122c9b",
|
||||
],
|
||||
}
|
||||
)
|
|
@ -1,21 +0,0 @@
|
|||
from copy import copy
|
||||
|
||||
from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate
|
||||
from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate
|
||||
|
||||
|
||||
class PowerShellCredentialsReuse(ConfigTemplate):
|
||||
config_values = copy(BaseTemplate.config_values)
|
||||
|
||||
config_values.update(
|
||||
{
|
||||
"basic.exploiters.exploiter_classes": ["PowerShellExploiter"],
|
||||
"basic_network.scope.subnet_scan_list": [
|
||||
"10.2.3.46",
|
||||
],
|
||||
"basic_network.scope.depth": 2,
|
||||
"internal.classes.finger_classes": [],
|
||||
"internal.network.tcp_scanner.HTTP_PORTS": [],
|
||||
"internal.network.tcp_scanner.tcp_target_ports": [5985, 5986],
|
||||
}
|
||||
)
|
|
@ -1,24 +0,0 @@
|
|||
from copy import copy
|
||||
|
||||
from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate
|
||||
from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate
|
||||
|
||||
|
||||
class SmbPth(ConfigTemplate):
|
||||
config_values = copy(BaseTemplate.config_values)
|
||||
|
||||
config_values.update(
|
||||
{
|
||||
"basic.exploiters.exploiter_classes": ["SmbExploiter"],
|
||||
"basic_network.scope.subnet_scan_list": ["10.2.2.15"],
|
||||
"basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"],
|
||||
"basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"],
|
||||
"internal.classes.finger_classes": ["SMBFinger", "HTTPFinger"],
|
||||
"internal.network.tcp_scanner.HTTP_PORTS": [],
|
||||
"internal.network.tcp_scanner.tcp_target_ports": [445],
|
||||
"internal.classes.exploits.exploit_ntlm_hash_list": [
|
||||
"5da0889ea2081aa79f6852294cba4a5e",
|
||||
"50c9987a6bf1ac59398df9f911122c9b",
|
||||
],
|
||||
}
|
||||
)
|
|
@ -1,22 +0,0 @@
|
|||
from copy import copy
|
||||
|
||||
from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate
|
||||
from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate
|
||||
|
||||
|
||||
class WmiMimikatz(ConfigTemplate):
|
||||
config_values = copy(BaseTemplate.config_values)
|
||||
|
||||
config_values.update(
|
||||
{
|
||||
"basic.exploiters.exploiter_classes": ["WmiExploiter"],
|
||||
"basic_network.scope.subnet_scan_list": ["10.2.2.14", "10.2.2.15"],
|
||||
"basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"],
|
||||
"basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"],
|
||||
"internal.network.tcp_scanner.HTTP_PORTS": [],
|
||||
"internal.network.tcp_scanner.tcp_target_ports": [135],
|
||||
"monkey.system_info.system_info_collector_classes": [
|
||||
"MimikatzCollector",
|
||||
],
|
||||
}
|
||||
)
|
|
@ -1,20 +0,0 @@
|
|||
from copy import copy
|
||||
|
||||
from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate
|
||||
from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate
|
||||
|
||||
|
||||
class Zerologon(ConfigTemplate):
|
||||
|
||||
config_values = copy(BaseTemplate.config_values)
|
||||
|
||||
config_values.update(
|
||||
{
|
||||
"basic.exploiters.exploiter_classes": ["ZerologonExploiter", "SmbExploiter"],
|
||||
"basic_network.scope.subnet_scan_list": ["10.2.2.25"],
|
||||
# Empty list to make sure ZeroLogon adds "Administrator" username
|
||||
"basic.credentials.exploit_user_list": [],
|
||||
"internal.network.tcp_scanner.HTTP_PORTS": [],
|
||||
"internal.network.tcp_scanner.tcp_target_ports": [135, 445],
|
||||
}
|
||||
)
|
|
@ -14,19 +14,6 @@ def pytest_addoption(parser):
|
|||
default=False,
|
||||
help="Use for no interaction with the cloud.",
|
||||
)
|
||||
parser.addoption(
|
||||
"--quick-performance-tests",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="If enabled performance tests won't reset island and won't send telemetries, "
|
||||
"instead will just test performance of already present island state.",
|
||||
)
|
||||
parser.addoption(
|
||||
"--run-performance-tests",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="If enabled performance tests will be run.",
|
||||
)
|
||||
parser.addoption(
|
||||
"--skip-powershell-reuse",
|
||||
action="store_true",
|
||||
|
@ -45,19 +32,7 @@ def no_gcp(request):
|
|||
return request.config.getoption("--no-gcp")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def quick_performance_tests(request):
|
||||
return request.config.getoption("--quick-performance-tests")
|
||||
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
if "run_performance_tests" in item.keywords and not item.config.getoption(
|
||||
"--run-performance-tests"
|
||||
):
|
||||
pytest.skip(
|
||||
"Skipping performance test because " "--run-performance-tests flag isn't specified."
|
||||
)
|
||||
|
||||
if "skip_powershell_reuse" in item.keywords and item.config.getoption(
|
||||
"--skip-powershell-reuse"
|
||||
):
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
import json
|
||||
|
||||
import dpath.util
|
||||
from typing_extensions import Type
|
||||
|
||||
from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate
|
||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||
|
||||
|
||||
class IslandConfigParser:
|
||||
@staticmethod
|
||||
def get_raw_config(
|
||||
config_template: Type[ConfigTemplate], island_client: MonkeyIslandClient
|
||||
) -> str:
|
||||
response = island_client.get_config()
|
||||
config = IslandConfigParser.apply_template_to_config(
|
||||
config_template, response["configuration"]
|
||||
)
|
||||
return json.dumps(config)
|
||||
|
||||
@staticmethod
|
||||
def apply_template_to_config(config_template: Type[ConfigTemplate], config: dict) -> dict:
|
||||
for path, value in config_template.config_values.items():
|
||||
dpath.util.set(config, path, value, ".")
|
||||
return config
|
||||
|
||||
@staticmethod
|
||||
def get_ips_of_targets(raw_config):
|
||||
return dpath.util.get(json.loads(raw_config), "basic_network.scope.subnet_scan_list", ".")
|
|
@ -5,7 +5,10 @@ from typing import Union
|
|||
|
||||
from bson import json_util
|
||||
|
||||
from common.configuration import AgentConfiguration
|
||||
from common.credentials import Credentials
|
||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_requests import MonkeyIslandRequests
|
||||
from envs.monkey_zoo.blackbox.test_configurations.test_configuration import TestConfiguration
|
||||
|
||||
SLEEP_BETWEEN_REQUESTS_SECONDS = 0.5
|
||||
MONKEY_TEST_ENDPOINT = "api/test/monkey"
|
||||
|
@ -27,15 +30,26 @@ class MonkeyIslandClient(object):
|
|||
return self.requests.get("api")
|
||||
|
||||
def get_config(self):
|
||||
return json.loads(self.requests.get("api/configuration/island").content)
|
||||
return json.loads(self.requests.get("api/agent-configuration").content)
|
||||
|
||||
@avoid_race_condition
|
||||
def import_config(self, config_contents):
|
||||
_ = self.requests.post("api/configuration/island", data=config_contents)
|
||||
def import_config(self, test_configuration: TestConfiguration):
|
||||
self.requests.post_json(
|
||||
"api/agent-configuration",
|
||||
json=AgentConfiguration.to_mapping(test_configuration.agent_configuration),
|
||||
)
|
||||
serialized_propagation_credentials = [
|
||||
Credentials.to_mapping(credentials)
|
||||
for credentials in test_configuration.propagation_credentials
|
||||
]
|
||||
self.requests.post_json(
|
||||
"/api/propagation-credentials/configured-credentials",
|
||||
json=serialized_propagation_credentials,
|
||||
)
|
||||
|
||||
@avoid_race_condition
|
||||
def run_monkey_local(self):
|
||||
response = self.requests.post_json("api/local-monkey", data={"action": "run"})
|
||||
response = self.requests.post_json("api/local-monkey", json={"action": "run"})
|
||||
if MonkeyIslandClient.monkey_ran_successfully(response):
|
||||
LOGGER.info("Running the monkey.")
|
||||
else:
|
||||
|
@ -49,7 +63,7 @@ class MonkeyIslandClient(object):
|
|||
@avoid_race_condition
|
||||
def kill_all_monkeys(self):
|
||||
response = self.requests.post_json(
|
||||
"api/monkey-control/stop-all-agents", data={"kill_time": time.time()}
|
||||
"api/monkey-control/stop-all-agents", json={"kill_time": time.time()}
|
||||
)
|
||||
if response.ok:
|
||||
LOGGER.info("Killing all monkeys after the test.")
|
||||
|
@ -67,10 +81,6 @@ class MonkeyIslandClient(object):
|
|||
LOGGER.error("Failed to reset the environment.")
|
||||
assert False
|
||||
|
||||
@avoid_race_condition
|
||||
def set_scenario(self, scenario):
|
||||
self.requests.post_json("api/island-mode", {"mode": scenario})
|
||||
|
||||
def find_monkeys_in_db(self, query):
|
||||
if query is None:
|
||||
raise TypeError
|
||||
|
@ -110,13 +120,3 @@ class MonkeyIslandClient(object):
|
|||
def is_all_monkeys_dead(self):
|
||||
query = {"dead": False}
|
||||
return len(self.find_monkeys_in_db(query)) == 0
|
||||
|
||||
def clear_caches(self):
|
||||
"""
|
||||
Tries to clear caches.
|
||||
:raises: If error (by error code), raises the error
|
||||
:return: The response
|
||||
"""
|
||||
response = self.requests.get("api/test/clear-caches")
|
||||
response.raise_for_status()
|
||||
return response
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import functools
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from typing import Dict
|
||||
|
||||
import requests
|
||||
|
||||
from envs.monkey_zoo.blackbox.island_client.supported_request_method import SupportedRequestMethod
|
||||
|
||||
ISLAND_USERNAME = "test"
|
||||
ISLAND_PASSWORD = "test"
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
@ -25,28 +22,6 @@ class MonkeyIslandRequests(object):
|
|||
def __init__(self, server_address):
|
||||
self.addr = "https://{IP}/".format(IP=server_address)
|
||||
self.token = self.try_get_jwt_from_server()
|
||||
self.supported_request_methods = {
|
||||
SupportedRequestMethod.GET: self.get,
|
||||
SupportedRequestMethod.POST: self.post,
|
||||
SupportedRequestMethod.PATCH: self.patch,
|
||||
SupportedRequestMethod.DELETE: self.delete,
|
||||
}
|
||||
|
||||
def get_request_time(self, url, method: SupportedRequestMethod, data=None):
|
||||
response = self.send_request_by_method(url, method, data)
|
||||
if response.ok:
|
||||
LOGGER.debug(f"Got ok for {url} content peek:\n{response.content[:120].strip()}")
|
||||
return response.elapsed
|
||||
else:
|
||||
LOGGER.error(f"Trying to get {url} but got unexpected {str(response)}")
|
||||
# instead of raising for status, mark failed responses as maxtime
|
||||
return timedelta.max
|
||||
|
||||
def send_request_by_method(self, url, method=SupportedRequestMethod.GET, data=None):
|
||||
if data:
|
||||
return self.supported_request_methods[method](url, data)
|
||||
else:
|
||||
return self.supported_request_methods[method](url)
|
||||
|
||||
def try_get_jwt_from_server(self):
|
||||
try:
|
||||
|
@ -108,9 +83,9 @@ class MonkeyIslandRequests(object):
|
|||
)
|
||||
|
||||
@_Decorators.refresh_jwt_token
|
||||
def post_json(self, url, data: Dict):
|
||||
def post_json(self, url, json: Dict):
|
||||
return requests.post( # noqa: DUO123
|
||||
self.addr + url, json=data, headers=self.get_jwt_header(), verify=False
|
||||
self.addr + url, json=json, headers=self.get_jwt_header(), verify=False
|
||||
)
|
||||
|
||||
@_Decorators.refresh_jwt_token
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
class SupportedRequestMethod(Enum):
|
||||
GET = "GET"
|
||||
POST = "POST"
|
||||
PATCH = "PATCH"
|
||||
DELETE = "DELETE"
|
|
@ -0,0 +1,7 @@
|
|||
from typing import Iterable
|
||||
|
||||
from envs.monkey_zoo.blackbox.test_configurations.test_configuration import TestConfiguration
|
||||
|
||||
|
||||
def get_target_ips(test_configuration: TestConfiguration) -> Iterable[str]:
|
||||
return test_configuration.agent_configuration.propagation.network_scan.targets.subnets
|
|
@ -3,31 +3,29 @@ import os
|
|||
from time import sleep
|
||||
|
||||
import pytest
|
||||
from typing_extensions import Type
|
||||
|
||||
from envs.monkey_zoo.blackbox.analyzers.communication_analyzer import CommunicationAnalyzer
|
||||
from envs.monkey_zoo.blackbox.analyzers.zerologon_analyzer import ZerologonAnalyzer
|
||||
from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate
|
||||
from envs.monkey_zoo.blackbox.config_templates.depth_1_a import Depth1A
|
||||
from envs.monkey_zoo.blackbox.config_templates.depth_2_a import Depth2A
|
||||
from envs.monkey_zoo.blackbox.config_templates.depth_3_a import Depth3A
|
||||
from envs.monkey_zoo.blackbox.config_templates.powershell_credentials_reuse import (
|
||||
PowerShellCredentialsReuse,
|
||||
)
|
||||
from envs.monkey_zoo.blackbox.config_templates.smb_pth import SmbPth
|
||||
from envs.monkey_zoo.blackbox.config_templates.wmi_mimikatz import WmiMimikatz
|
||||
from envs.monkey_zoo.blackbox.config_templates.zerologon import Zerologon
|
||||
from envs.monkey_zoo.blackbox.gcp_test_machine_list import GCP_TEST_MACHINE_LIST
|
||||
from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandConfigParser
|
||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||
from envs.monkey_zoo.blackbox.island_client.test_configuration_parser import get_target_ips
|
||||
from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler
|
||||
from envs.monkey_zoo.blackbox.test_configurations import (
|
||||
depth_1_a_test_configuration,
|
||||
depth_2_a_test_configuration,
|
||||
depth_3_a_test_configuration,
|
||||
powershell_credentials_reuse_test_configuration,
|
||||
smb_pth_test_configuration,
|
||||
wmi_mimikatz_test_configuration,
|
||||
zerologon_test_configuration,
|
||||
)
|
||||
from envs.monkey_zoo.blackbox.test_configurations.test_configuration import TestConfiguration
|
||||
from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest
|
||||
from envs.monkey_zoo.blackbox.utils.gcp_machine_handlers import (
|
||||
initialize_gcp_client,
|
||||
start_machines,
|
||||
stop_machines,
|
||||
)
|
||||
from monkey_island.cc.services.mode.mode_enum import IslandModeEnum
|
||||
|
||||
DEFAULT_TIMEOUT_SECONDS = 2 * 60 + 30
|
||||
MACHINE_BOOTUP_WAIT_SECONDS = 30
|
||||
|
@ -64,7 +62,7 @@ def wait_machine_bootup():
|
|||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def island_client(island, quick_performance_tests):
|
||||
def island_client(island):
|
||||
client_established = False
|
||||
try:
|
||||
island_client_object = MonkeyIslandClient(island)
|
||||
|
@ -74,9 +72,6 @@ def island_client(island, quick_performance_tests):
|
|||
finally:
|
||||
if not client_established:
|
||||
pytest.exit("BB tests couldn't establish communication to the island.")
|
||||
if not quick_performance_tests:
|
||||
island_client_object.reset_env()
|
||||
island_client_object.set_scenario(IslandModeEnum.ADVANCED.value)
|
||||
yield island_client_object
|
||||
|
||||
|
||||
|
@ -86,13 +81,13 @@ class TestMonkeyBlackbox:
|
|||
@staticmethod
|
||||
def run_exploitation_test(
|
||||
island_client: MonkeyIslandClient,
|
||||
config_template: Type[ConfigTemplate],
|
||||
test_configuration: TestConfiguration,
|
||||
test_name: str,
|
||||
timeout_in_seconds=DEFAULT_TIMEOUT_SECONDS,
|
||||
):
|
||||
raw_config = IslandConfigParser.get_raw_config(config_template, island_client)
|
||||
analyzer = CommunicationAnalyzer(
|
||||
island_client, IslandConfigParser.get_ips_of_targets(raw_config)
|
||||
island_client,
|
||||
get_target_ips(test_configuration),
|
||||
)
|
||||
log_handler = TestLogsHandler(
|
||||
test_name, island_client, TestMonkeyBlackbox.get_log_dir_path()
|
||||
|
@ -100,7 +95,7 @@ class TestMonkeyBlackbox:
|
|||
ExploitationTest(
|
||||
name=test_name,
|
||||
island_client=island_client,
|
||||
raw_config=raw_config,
|
||||
test_configuration=test_configuration,
|
||||
analyzers=[analyzer],
|
||||
timeout=timeout_in_seconds,
|
||||
log_handler=log_handler,
|
||||
|
@ -113,20 +108,26 @@ class TestMonkeyBlackbox:
|
|||
# If test_depth_1_a() is run first, some test will fail because machines are not yet fully
|
||||
# booted. Running test_depth_2_a() first gives slow VMs extra time to boot.
|
||||
def test_depth_2_a(self, island_client):
|
||||
TestMonkeyBlackbox.run_exploitation_test(island_client, Depth2A, "Depth2A test suite")
|
||||
TestMonkeyBlackbox.run_exploitation_test(
|
||||
island_client, depth_2_a_test_configuration, "Depth2A test suite"
|
||||
)
|
||||
|
||||
def test_depth_1_a(self, island_client):
|
||||
TestMonkeyBlackbox.run_exploitation_test(island_client, Depth1A, "Depth1A test suite")
|
||||
TestMonkeyBlackbox.run_exploitation_test(
|
||||
island_client, depth_1_a_test_configuration, "Depth1A test suite"
|
||||
)
|
||||
|
||||
def test_depth_3_a(self, island_client):
|
||||
TestMonkeyBlackbox.run_exploitation_test(island_client, Depth3A, "Depth3A test suite")
|
||||
TestMonkeyBlackbox.run_exploitation_test(
|
||||
island_client, depth_3_a_test_configuration, "Depth3A test suite"
|
||||
)
|
||||
|
||||
# Not grouped because can only be ran on windows
|
||||
@pytest.mark.skip_powershell_reuse
|
||||
def test_powershell_exploiter_credentials_reuse(self, island_client):
|
||||
TestMonkeyBlackbox.run_exploitation_test(
|
||||
island_client,
|
||||
PowerShellCredentialsReuse,
|
||||
powershell_credentials_reuse_test_configuration,
|
||||
"PowerShell_Remoting_exploiter_credentials_reuse",
|
||||
)
|
||||
|
||||
|
@ -138,10 +139,10 @@ class TestMonkeyBlackbox:
|
|||
"aad3b435b51404eeaad3b435b51404ee",
|
||||
"2864b62ea4496934a5d6e86f50b834a5",
|
||||
]
|
||||
raw_config = IslandConfigParser.get_raw_config(Zerologon, island_client)
|
||||
zero_logon_analyzer = ZerologonAnalyzer(island_client, expected_creds)
|
||||
communication_analyzer = CommunicationAnalyzer(
|
||||
island_client, IslandConfigParser.get_ips_of_targets(raw_config)
|
||||
island_client,
|
||||
get_target_ips(zerologon_test_configuration),
|
||||
)
|
||||
log_handler = TestLogsHandler(
|
||||
test_name, island_client, TestMonkeyBlackbox.get_log_dir_path()
|
||||
|
@ -149,7 +150,7 @@ class TestMonkeyBlackbox:
|
|||
ExploitationTest(
|
||||
name=test_name,
|
||||
island_client=island_client,
|
||||
raw_config=raw_config,
|
||||
test_configuration=zerologon_test_configuration,
|
||||
analyzers=[zero_logon_analyzer, communication_analyzer],
|
||||
timeout=DEFAULT_TIMEOUT_SECONDS + 30,
|
||||
log_handler=log_handler,
|
||||
|
@ -159,9 +160,11 @@ class TestMonkeyBlackbox:
|
|||
# Consider grouping when more depth 1 exploiters collide with group depth_1_a
|
||||
def test_wmi_and_mimikatz_exploiters(self, island_client):
|
||||
TestMonkeyBlackbox.run_exploitation_test(
|
||||
island_client, WmiMimikatz, "WMI_exploiter,_mimikatz"
|
||||
island_client, wmi_mimikatz_test_configuration, "WMI_exploiter,_mimikatz"
|
||||
)
|
||||
|
||||
# Not grouped because it's depth 1 but conflicts with SMB exploiter in group depth_1_a
|
||||
def test_smb_pth(self, island_client):
|
||||
TestMonkeyBlackbox.run_exploitation_test(island_client, SmbPth, "SMB_PTH")
|
||||
TestMonkeyBlackbox.run_exploitation_test(
|
||||
island_client, smb_pth_test_configuration, "SMB_PTH"
|
||||
)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from .test_configuration import TestConfiguration
|
||||
from .zerologon import zerologon_test_configuration
|
||||
from .depth_1_a import depth_1_a_test_configuration
|
||||
from .depth_2_a import depth_2_a_test_configuration
|
||||
from .depth_3_a import depth_3_a_test_configuration
|
||||
from .powershell_credentials_reuse import powershell_credentials_reuse_test_configuration
|
||||
from .smb_pth import smb_pth_test_configuration
|
||||
from .wmi_mimikatz import wmi_mimikatz_test_configuration
|
||||
from .zerologon import zerologon_test_configuration
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import logging
|
||||
from time import sleep
|
||||
|
||||
from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandConfigParser
|
||||
from envs.monkey_zoo.blackbox.island_client.test_configuration_parser import get_target_ips
|
||||
from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest
|
||||
from envs.monkey_zoo.blackbox.utils.test_timer import TestTimer
|
||||
|
||||
|
@ -13,16 +13,16 @@ LOGGER = logging.getLogger(__name__)
|
|||
|
||||
|
||||
class ExploitationTest(BasicTest):
|
||||
def __init__(self, name, island_client, raw_config, analyzers, timeout, log_handler):
|
||||
def __init__(self, name, island_client, test_configuration, analyzers, timeout, log_handler):
|
||||
self.name = name
|
||||
self.island_client = island_client
|
||||
self.raw_config = raw_config
|
||||
self.test_configuration = test_configuration
|
||||
self.analyzers = analyzers
|
||||
self.timeout = timeout
|
||||
self.log_handler = log_handler
|
||||
|
||||
def run(self):
|
||||
self.island_client.import_config(self.raw_config)
|
||||
self.island_client.import_config(self.test_configuration)
|
||||
self.print_test_starting_info()
|
||||
try:
|
||||
self.island_client.run_monkey_local()
|
||||
|
@ -36,7 +36,7 @@ class ExploitationTest(BasicTest):
|
|||
|
||||
def print_test_starting_info(self):
|
||||
LOGGER.info("Started {} test".format(self.name))
|
||||
machine_list = ", ".join(IslandConfigParser.get_ips_of_targets(self.raw_config))
|
||||
machine_list = ", ".join(get_target_ips(self.test_configuration))
|
||||
LOGGER.info(f"Machines participating in test: {machine_list}")
|
||||
print("")
|
||||
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
import logging
|
||||
|
||||
from envs.monkey_zoo.blackbox.analyzers.performance_analyzer import PerformanceAnalyzer
|
||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||
from envs.monkey_zoo.blackbox.island_client.supported_request_method import SupportedRequestMethod
|
||||
from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest
|
||||
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EndpointPerformanceTest(BasicTest):
|
||||
def __init__(self, name, test_config: PerformanceTestConfig, island_client: MonkeyIslandClient):
|
||||
self.name = name
|
||||
self.test_config = test_config
|
||||
self.island_client = island_client
|
||||
|
||||
def run(self) -> bool:
|
||||
# Collect timings for all pages
|
||||
endpoint_timings = {}
|
||||
for endpoint in self.test_config.endpoints_to_test:
|
||||
self.island_client.clear_caches()
|
||||
endpoint_timings[endpoint] = self.island_client.requests.get_request_time(
|
||||
endpoint, SupportedRequestMethod.GET
|
||||
)
|
||||
analyzer = PerformanceAnalyzer(self.test_config, endpoint_timings)
|
||||
|
||||
return analyzer.analyze_test_results()
|
|
@ -1,40 +0,0 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest
|
||||
from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest
|
||||
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||
from envs.monkey_zoo.blackbox.tests.performance.performance_test_workflow import (
|
||||
PerformanceTestWorkflow,
|
||||
)
|
||||
|
||||
MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2)
|
||||
MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5)
|
||||
|
||||
MAP_RESOURCES = [
|
||||
"api/netmap",
|
||||
]
|
||||
|
||||
|
||||
class MapGenerationTest(PerformanceTest):
|
||||
|
||||
TEST_NAME = "Map generation performance test"
|
||||
|
||||
def __init__(
|
||||
self, island_client, raw_config, analyzers, timeout, log_handler, break_on_timeout
|
||||
):
|
||||
self.island_client = island_client
|
||||
exploitation_test = ExploitationTest(
|
||||
MapGenerationTest.TEST_NAME, island_client, raw_config, analyzers, timeout, log_handler
|
||||
)
|
||||
performance_config = PerformanceTestConfig(
|
||||
max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
|
||||
max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
|
||||
endpoints_to_test=MAP_RESOURCES,
|
||||
break_on_timeout=break_on_timeout,
|
||||
)
|
||||
self.performance_test_workflow = PerformanceTestWorkflow(
|
||||
MapGenerationTest.TEST_NAME, exploitation_test, performance_config
|
||||
)
|
||||
|
||||
def run(self):
|
||||
self.performance_test_workflow.run()
|
|
@ -1,37 +0,0 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest
|
||||
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||
from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test_workflow import (
|
||||
TelemetryPerformanceTestWorkflow,
|
||||
)
|
||||
|
||||
MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2)
|
||||
MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5)
|
||||
|
||||
MAP_RESOURCES = [
|
||||
"api/netmap",
|
||||
]
|
||||
|
||||
|
||||
class MapGenerationFromTelemetryTest(PerformanceTest):
|
||||
|
||||
TEST_NAME = "Map generation from fake telemetries test"
|
||||
|
||||
def __init__(self, island_client, quick_performance_test: bool, break_on_timeout=False):
|
||||
self.island_client = island_client
|
||||
performance_config = PerformanceTestConfig(
|
||||
max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
|
||||
max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
|
||||
endpoints_to_test=MAP_RESOURCES,
|
||||
break_on_timeout=break_on_timeout,
|
||||
)
|
||||
self.performance_test_workflow = TelemetryPerformanceTestWorkflow(
|
||||
MapGenerationFromTelemetryTest.TEST_NAME,
|
||||
self.island_client,
|
||||
performance_config,
|
||||
quick_performance_test,
|
||||
)
|
||||
|
||||
def run(self):
|
||||
self.performance_test_workflow.run()
|
|
@ -1,16 +0,0 @@
|
|||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest
|
||||
|
||||
|
||||
class PerformanceTest(BasicTest, metaclass=ABCMeta):
|
||||
@abstractmethod
|
||||
def __init__(
|
||||
self, island_client, raw_config, analyzers, timeout, log_handler, break_on_timeout
|
||||
):
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def TEST_NAME(self):
|
||||
pass
|
|
@ -1,16 +0,0 @@
|
|||
from datetime import timedelta
|
||||
from typing import List
|
||||
|
||||
|
||||
class PerformanceTestConfig:
|
||||
def __init__(
|
||||
self,
|
||||
max_allowed_single_page_time: timedelta,
|
||||
max_allowed_total_time: timedelta,
|
||||
endpoints_to_test: List[str] = None,
|
||||
break_on_timeout=False,
|
||||
):
|
||||
self.max_allowed_single_page_time = max_allowed_single_page_time
|
||||
self.max_allowed_total_time = max_allowed_total_time
|
||||
self.endpoints_to_test = endpoints_to_test
|
||||
self.break_on_timeout = break_on_timeout
|
|
@ -1,40 +0,0 @@
|
|||
from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest
|
||||
from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest
|
||||
from envs.monkey_zoo.blackbox.tests.performance.endpoint_performance_test import (
|
||||
EndpointPerformanceTest,
|
||||
)
|
||||
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||
|
||||
|
||||
class PerformanceTestWorkflow(BasicTest):
|
||||
def __init__(
|
||||
self, name, exploitation_test: ExploitationTest, performance_config: PerformanceTestConfig
|
||||
):
|
||||
self.name = name
|
||||
self.exploitation_test = exploitation_test
|
||||
self.island_client = exploitation_test.island_client
|
||||
self.raw_config = exploitation_test.raw_config
|
||||
self.performance_config = performance_config
|
||||
|
||||
def run(self):
|
||||
self.island_client.import_config(self.raw_config)
|
||||
self.exploitation_test.print_test_starting_info()
|
||||
try:
|
||||
self.island_client.run_monkey_local()
|
||||
self.exploitation_test.test_until_timeout()
|
||||
finally:
|
||||
self.island_client.kill_all_monkeys()
|
||||
self.exploitation_test.wait_until_monkeys_die()
|
||||
self.exploitation_test.wait_for_monkey_process_to_finish()
|
||||
if not self.island_client.is_all_monkeys_dead():
|
||||
raise RuntimeError("Can't test report times since not all Monkeys have died.")
|
||||
performance_test = EndpointPerformanceTest(
|
||||
self.name, self.performance_config, self.island_client
|
||||
)
|
||||
try:
|
||||
if not self.island_client.is_all_monkeys_dead():
|
||||
raise RuntimeError("Can't test report times since not all Monkeys have died.")
|
||||
assert performance_test.run()
|
||||
finally:
|
||||
self.exploitation_test.parse_logs()
|
||||
self.island_client.reset_env()
|
|
@ -1,48 +0,0 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest
|
||||
from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest
|
||||
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||
from envs.monkey_zoo.blackbox.tests.performance.performance_test_workflow import (
|
||||
PerformanceTestWorkflow,
|
||||
)
|
||||
|
||||
MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2)
|
||||
MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5)
|
||||
|
||||
REPORT_RESOURCES = [
|
||||
"api/report/security",
|
||||
"api/attack/report",
|
||||
"api/report/zero_trust/findings",
|
||||
"api/report/zero_trust/principles",
|
||||
"api/report/zero_trust/pillars",
|
||||
]
|
||||
|
||||
|
||||
class ReportGenerationTest(PerformanceTest):
|
||||
TEST_NAME = "Report generation performance test"
|
||||
|
||||
def __init__(
|
||||
self, island_client, raw_config, analyzers, timeout, log_handler, break_on_timeout
|
||||
):
|
||||
self.island_client = island_client
|
||||
exploitation_test = ExploitationTest(
|
||||
ReportGenerationTest.TEST_NAME,
|
||||
island_client,
|
||||
raw_config,
|
||||
analyzers,
|
||||
timeout,
|
||||
log_handler,
|
||||
)
|
||||
performance_config = PerformanceTestConfig(
|
||||
max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
|
||||
max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
|
||||
endpoints_to_test=REPORT_RESOURCES,
|
||||
break_on_timeout=break_on_timeout,
|
||||
)
|
||||
self.performance_test_workflow = PerformanceTestWorkflow(
|
||||
ReportGenerationTest.TEST_NAME, exploitation_test, performance_config
|
||||
)
|
||||
|
||||
def run(self):
|
||||
self.performance_test_workflow.run()
|
|
@ -1,41 +0,0 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from envs.monkey_zoo.blackbox.tests.performance.performance_test import PerformanceTest
|
||||
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||
from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test_workflow import (
|
||||
TelemetryPerformanceTestWorkflow,
|
||||
)
|
||||
|
||||
MAX_ALLOWED_SINGLE_PAGE_TIME = timedelta(seconds=2)
|
||||
MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=5)
|
||||
|
||||
REPORT_RESOURCES = [
|
||||
"api/report/security",
|
||||
"api/attack/report",
|
||||
"api/report/zero_trust/findings",
|
||||
"api/report/zero_trust/principles",
|
||||
"api/report/zero_trust/pillars",
|
||||
]
|
||||
|
||||
|
||||
class ReportGenerationFromTelemetryTest(PerformanceTest):
|
||||
|
||||
TEST_NAME = "Map generation from fake telemetries test"
|
||||
|
||||
def __init__(self, island_client, quick_performance_test, break_on_timeout=False):
|
||||
self.island_client = island_client
|
||||
performance_config = PerformanceTestConfig(
|
||||
max_allowed_single_page_time=MAX_ALLOWED_SINGLE_PAGE_TIME,
|
||||
max_allowed_total_time=MAX_ALLOWED_TOTAL_TIME,
|
||||
endpoints_to_test=REPORT_RESOURCES,
|
||||
break_on_timeout=break_on_timeout,
|
||||
)
|
||||
self.performance_test_workflow = TelemetryPerformanceTestWorkflow(
|
||||
ReportGenerationFromTelemetryTest.TEST_NAME,
|
||||
self.island_client,
|
||||
performance_config,
|
||||
quick_performance_test,
|
||||
)
|
||||
|
||||
def run(self):
|
||||
self.performance_test_workflow.run()
|
|
@ -1,51 +0,0 @@
|
|||
import json
|
||||
import logging
|
||||
from os import listdir, path
|
||||
from typing import Dict, List
|
||||
|
||||
from tqdm import tqdm
|
||||
|
||||
TELEM_DIR_PATH = "../envs/monkey_zoo/blackbox/tests/performance/telemetry_sample"
|
||||
MAX_SAME_TYPE_TELEM_FILES = 10000
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SampleFileParser:
|
||||
@staticmethod
|
||||
def save_teletries_to_files(telems: List[Dict]):
|
||||
for telem in tqdm(telems, desc="Telemetries saved to files", position=3):
|
||||
SampleFileParser.save_telemetry_to_file(telem)
|
||||
|
||||
@staticmethod
|
||||
def save_telemetry_to_file(telem: Dict):
|
||||
telem_filename = telem["name"] + telem["method"]
|
||||
for i in range(MAX_SAME_TYPE_TELEM_FILES):
|
||||
if not path.exists(path.join(TELEM_DIR_PATH, (str(i) + telem_filename))):
|
||||
telem_filename = str(i) + telem_filename
|
||||
break
|
||||
with open(path.join(TELEM_DIR_PATH, telem_filename), "w") as file:
|
||||
file.write(json.dumps(telem))
|
||||
|
||||
@staticmethod
|
||||
def read_telem_files() -> List[str]:
|
||||
telems = []
|
||||
try:
|
||||
file_paths = [
|
||||
path.join(TELEM_DIR_PATH, f)
|
||||
for f in listdir(TELEM_DIR_PATH)
|
||||
if path.isfile(path.join(TELEM_DIR_PATH, f))
|
||||
]
|
||||
except FileNotFoundError:
|
||||
raise FileNotFoundError(
|
||||
"Telemetries to send not found. "
|
||||
"Refer to readme to figure out how to generate telemetries and where to put them."
|
||||
)
|
||||
for file_path in file_paths:
|
||||
with open(file_path, "r") as telem_file:
|
||||
telem_string = "".join(telem_file.readlines()).replace("\n", "")
|
||||
telems.append(telem_string)
|
||||
return telems
|
||||
|
||||
@staticmethod
|
||||
def get_all_telemetries() -> List[Dict]:
|
||||
return [json.loads(t) for t in SampleFileParser.read_telem_files()]
|
|
@ -1,25 +0,0 @@
|
|||
from typing import List
|
||||
|
||||
|
||||
class FakeIpGenerator:
|
||||
def __init__(self):
|
||||
self.fake_ip_parts = [1, 1, 1, 1]
|
||||
|
||||
def generate_fake_ips_for_real_ips(self, real_ips: List[str]) -> List[str]:
|
||||
fake_ips = []
|
||||
for i in range(len(real_ips)):
|
||||
fake_ips.append(".".join(str(part) for part in self.fake_ip_parts))
|
||||
self.increment_ip()
|
||||
return fake_ips
|
||||
|
||||
def increment_ip(self):
|
||||
self.fake_ip_parts[3] += 1
|
||||
self.try_fix_ip_range()
|
||||
|
||||
def try_fix_ip_range(self):
|
||||
for i in range(len(self.fake_ip_parts)):
|
||||
if self.fake_ip_parts[i] > 256:
|
||||
if i - 1 < 0:
|
||||
raise Exception("Fake IP's out of range.")
|
||||
self.fake_ip_parts[i - 1] += 1
|
||||
self.fake_ip_parts[i] = 1
|
|
@ -1,19 +0,0 @@
|
|||
import random
|
||||
|
||||
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( # noqa: E501
|
||||
FakeIpGenerator,
|
||||
)
|
||||
|
||||
|
||||
class FakeMonkey:
|
||||
def __init__(self, ips, guid, fake_ip_generator: FakeIpGenerator, on_island=False):
|
||||
self.original_ips = ips
|
||||
self.original_guid = guid
|
||||
self.fake_ip_generator = fake_ip_generator
|
||||
self.on_island = on_island
|
||||
self.fake_guid = str(random.randint(1000000000000, 9999999999999)) # noqa: DUO102
|
||||
self.fake_ips = fake_ip_generator.generate_fake_ips_for_real_ips(ips)
|
||||
|
||||
def change_fake_data(self):
|
||||
self.fake_ips = self.fake_ip_generator.generate_fake_ips_for_real_ips(self.original_ips)
|
||||
self.fake_guid = str(random.randint(1000000000000, 9999999999999)) # noqa: DUO102
|
|
@ -1,107 +0,0 @@
|
|||
import copy
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from typing import Dict, List
|
||||
|
||||
from tqdm import tqdm
|
||||
|
||||
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import (
|
||||
SampleFileParser,
|
||||
)
|
||||
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( # noqa: E501
|
||||
FakeIpGenerator,
|
||||
)
|
||||
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_monkey import ( # noqa: E501
|
||||
FakeMonkey,
|
||||
)
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SampleMultiplier:
|
||||
def __init__(self, multiplier: int):
|
||||
self.multiplier = multiplier
|
||||
self.fake_ip_generator = FakeIpGenerator()
|
||||
|
||||
def multiply_telems(self):
|
||||
telems = SampleFileParser.get_all_telemetries()
|
||||
telem_contents = [json.loads(telem["content"]) for telem in telems]
|
||||
monkeys = self.get_monkeys_from_telems(telem_contents)
|
||||
for i in tqdm(range(self.multiplier), desc="Batch of fabricated telemetries", position=1):
|
||||
for monkey in monkeys:
|
||||
monkey.change_fake_data()
|
||||
fake_telem_batch = copy.deepcopy(telems)
|
||||
SampleMultiplier.fabricate_monkeys_in_telems(fake_telem_batch, monkeys)
|
||||
SampleMultiplier.offset_telem_times(iteration=i, telems=fake_telem_batch)
|
||||
SampleFileParser.save_teletries_to_files(fake_telem_batch)
|
||||
LOGGER.info("")
|
||||
|
||||
@staticmethod
|
||||
def fabricate_monkeys_in_telems(telems: List[Dict], monkeys: List[FakeMonkey]):
|
||||
for telem in tqdm(telems, desc="Telemetries fabricated", position=2):
|
||||
for monkey in monkeys:
|
||||
if monkey.on_island:
|
||||
continue
|
||||
if (
|
||||
monkey.original_guid in telem["content"]
|
||||
or monkey.original_guid in telem["endpoint"]
|
||||
) and not monkey.on_island:
|
||||
telem["content"] = telem["content"].replace(
|
||||
monkey.original_guid, monkey.fake_guid
|
||||
)
|
||||
telem["endpoint"] = telem["endpoint"].replace(
|
||||
monkey.original_guid, monkey.fake_guid
|
||||
)
|
||||
for i in range(len(monkey.original_ips)):
|
||||
telem["content"] = telem["content"].replace(
|
||||
monkey.original_ips[i], monkey.fake_ips[i]
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def offset_telem_times(iteration: int, telems: List[Dict]):
|
||||
for telem in telems:
|
||||
telem["time"]["$date"] += iteration * 1000
|
||||
|
||||
def get_monkeys_from_telems(self, telems: List[Dict]):
|
||||
island_ips = SampleMultiplier.get_island_ips_from_telems(telems)
|
||||
monkeys = []
|
||||
for telem in [
|
||||
telem
|
||||
for telem in telems
|
||||
if "telem_category" in telem and telem["telem_category"] == "system_info"
|
||||
]:
|
||||
if "network_info" not in telem["data"]:
|
||||
continue
|
||||
guid = telem["monkey_guid"]
|
||||
monkey_present = [monkey for monkey in monkeys if monkey.original_guid == guid]
|
||||
if not monkey_present:
|
||||
ips = [net_info["addr"] for net_info in telem["data"]["network_info"]["networks"]]
|
||||
if set(island_ips).intersection(ips):
|
||||
on_island = True
|
||||
else:
|
||||
on_island = False
|
||||
|
||||
monkeys.append(
|
||||
FakeMonkey(
|
||||
ips=ips,
|
||||
guid=guid,
|
||||
fake_ip_generator=self.fake_ip_generator,
|
||||
on_island=on_island,
|
||||
)
|
||||
)
|
||||
return monkeys
|
||||
|
||||
@staticmethod
|
||||
def get_island_ips_from_telems(telems: List[Dict]) -> List[str]:
|
||||
island_ips = []
|
||||
for telem in telems:
|
||||
if "config" in telem:
|
||||
island_ips = telem["config"]["command_servers"]
|
||||
for i in range(len(island_ips)):
|
||||
island_ips[i] = island_ips[i].replace(":5000", "")
|
||||
return island_ips
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
SampleMultiplier(multiplier=int(sys.argv[1])).multiply_telems()
|
|
@ -1,21 +0,0 @@
|
|||
from unittest import TestCase
|
||||
|
||||
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_multiplier.fake_ip_generator import ( # noqa: E501
|
||||
FakeIpGenerator,
|
||||
)
|
||||
|
||||
|
||||
class TestFakeIpGenerator(TestCase):
|
||||
def test_fake_ip_generation(self):
|
||||
fake_ip_gen = FakeIpGenerator()
|
||||
self.assertListEqual([1, 1, 1, 1], fake_ip_gen.fake_ip_parts)
|
||||
for i in range(256):
|
||||
fake_ip_gen.generate_fake_ips_for_real_ips(["1.1.1.1"])
|
||||
self.assertListEqual(["1.1.2.1"], fake_ip_gen.generate_fake_ips_for_real_ips(["1.1.1.1"]))
|
||||
fake_ip_gen.fake_ip_parts = [256, 256, 255, 256]
|
||||
self.assertListEqual(
|
||||
["256.256.255.256", "256.256.256.1"],
|
||||
fake_ip_gen.generate_fake_ips_for_real_ips(["1.1.1.1", "1.1.1.2"]),
|
||||
)
|
||||
fake_ip_gen.fake_ip_parts = [256, 256, 256, 256]
|
||||
self.assertRaises(Exception, fake_ip_gen.generate_fake_ips_for_real_ips(["1.1.1.1"]))
|
|
@ -1,66 +0,0 @@
|
|||
import json
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
from envs.monkey_zoo.blackbox.analyzers.performance_analyzer import PerformanceAnalyzer
|
||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||
from envs.monkey_zoo.blackbox.island_client.supported_request_method import SupportedRequestMethod
|
||||
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||
from envs.monkey_zoo.blackbox.tests.performance.telem_sample_parsing.sample_file_parser import (
|
||||
SampleFileParser,
|
||||
)
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
MAX_ALLOWED_SINGLE_TELEM_PARSE_TIME = timedelta(seconds=2)
|
||||
MAX_ALLOWED_TOTAL_TIME = timedelta(seconds=60)
|
||||
|
||||
|
||||
class TelemetryPerformanceTest:
|
||||
def __init__(self, island_client: MonkeyIslandClient, quick_performance_test: bool):
|
||||
self.island_client = island_client
|
||||
self.quick_performance_test = quick_performance_test
|
||||
|
||||
def test_telemetry_performance(self):
|
||||
LOGGER.info("Starting telemetry performance test.")
|
||||
try:
|
||||
all_telemetries = SampleFileParser.get_all_telemetries()
|
||||
except FileNotFoundError:
|
||||
raise FileNotFoundError(
|
||||
"Telemetries to send not found. "
|
||||
"Refer to readme to figure out how to generate telemetries and where to put them."
|
||||
)
|
||||
LOGGER.info("Telemetries imported successfully.")
|
||||
all_telemetries.sort(key=lambda telem: telem["time"]["$date"])
|
||||
telemetry_parse_times = {}
|
||||
for i in range(len(all_telemetries)):
|
||||
telemetry_endpoint = TelemetryPerformanceTest.get_verbose_telemetry_endpoint(
|
||||
all_telemetries[i]
|
||||
)
|
||||
telemetry_parse_times[telemetry_endpoint] = self.get_telemetry_time(all_telemetries[i])
|
||||
LOGGER.info(f"Telemetry Nr.{i} sent out of {len(all_telemetries)} total.")
|
||||
test_config = PerformanceTestConfig(
|
||||
MAX_ALLOWED_SINGLE_TELEM_PARSE_TIME, MAX_ALLOWED_TOTAL_TIME
|
||||
)
|
||||
PerformanceAnalyzer(test_config, telemetry_parse_times).analyze_test_results()
|
||||
if not self.quick_performance_test:
|
||||
self.island_client.reset_env()
|
||||
|
||||
def get_telemetry_time(self, telemetry):
|
||||
content = telemetry["content"]
|
||||
url = telemetry["endpoint"]
|
||||
method = SupportedRequestMethod.__getattr__(telemetry["method"])
|
||||
|
||||
return self.island_client.requests.get_request_time(url=url, method=method, data=content)
|
||||
|
||||
@staticmethod
|
||||
def get_verbose_telemetry_endpoint(telemetry):
|
||||
telem_category = ""
|
||||
if "telem_category" in telemetry["content"]:
|
||||
telem_category = (
|
||||
"_"
|
||||
+ json.loads(telemetry["content"])["telem_category"]
|
||||
+ "_"
|
||||
+ telemetry["_id"]["$oid"]
|
||||
)
|
||||
return telemetry["endpoint"] + telem_category
|
|
@ -1,34 +0,0 @@
|
|||
from envs.monkey_zoo.blackbox.tests.basic_test import BasicTest
|
||||
from envs.monkey_zoo.blackbox.tests.performance.endpoint_performance_test import (
|
||||
EndpointPerformanceTest,
|
||||
)
|
||||
from envs.monkey_zoo.blackbox.tests.performance.performance_test_config import PerformanceTestConfig
|
||||
from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import (
|
||||
TelemetryPerformanceTest,
|
||||
)
|
||||
|
||||
|
||||
class TelemetryPerformanceTestWorkflow(BasicTest):
|
||||
def __init__(
|
||||
self, name, island_client, performance_config: PerformanceTestConfig, quick_performance_test
|
||||
):
|
||||
self.name = name
|
||||
self.island_client = island_client
|
||||
self.performance_config = performance_config
|
||||
self.quick_performance_test = quick_performance_test
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
if not self.quick_performance_test:
|
||||
telem_sending_test = TelemetryPerformanceTest(
|
||||
island_client=self.island_client,
|
||||
quick_performance_test=self.quick_performance_test,
|
||||
)
|
||||
telem_sending_test.test_telemetry_performance()
|
||||
performance_test = EndpointPerformanceTest(
|
||||
self.name, self.performance_config, self.island_client
|
||||
)
|
||||
assert performance_test.run()
|
||||
finally:
|
||||
if not self.quick_performance_test:
|
||||
self.island_client.reset_env()
|
|
@ -1,59 +0,0 @@
|
|||
import argparse
|
||||
import pathlib
|
||||
from typing import Type
|
||||
|
||||
from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate
|
||||
from envs.monkey_zoo.blackbox.config_templates.depth_1_a import Depth1A
|
||||
from envs.monkey_zoo.blackbox.config_templates.depth_2_a import Depth2A
|
||||
from envs.monkey_zoo.blackbox.config_templates.depth_3_a import Depth3A
|
||||
from envs.monkey_zoo.blackbox.config_templates.powershell_credentials_reuse import (
|
||||
PowerShellCredentialsReuse,
|
||||
)
|
||||
from envs.monkey_zoo.blackbox.config_templates.smb_pth import SmbPth
|
||||
from envs.monkey_zoo.blackbox.config_templates.wmi_mimikatz import WmiMimikatz
|
||||
from envs.monkey_zoo.blackbox.config_templates.zerologon import Zerologon
|
||||
from envs.monkey_zoo.blackbox.island_client.island_config_parser import IslandConfigParser
|
||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||
|
||||
DST_DIR_NAME = "generated_configs"
|
||||
DST_DIR_PATH = pathlib.Path(pathlib.Path(__file__).parent.absolute(), DST_DIR_NAME)
|
||||
|
||||
parser = argparse.ArgumentParser(description="Generate config files.")
|
||||
parser.add_argument(
|
||||
"island_ip",
|
||||
metavar="IP:PORT",
|
||||
help="Island endpoint. Example: 123.123.123.123:5000",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
island_client = MonkeyIslandClient(args.island_ip)
|
||||
|
||||
CONFIG_TEMPLATES = [
|
||||
Depth1A,
|
||||
Depth2A,
|
||||
Depth3A,
|
||||
Zerologon,
|
||||
SmbPth,
|
||||
WmiMimikatz,
|
||||
PowerShellCredentialsReuse,
|
||||
]
|
||||
|
||||
|
||||
def generate_templates():
|
||||
for template in CONFIG_TEMPLATES:
|
||||
save_template_as_config(template)
|
||||
|
||||
|
||||
def save_template_as_config(template: Type[ConfigTemplate]):
|
||||
file_path = pathlib.Path(DST_DIR_PATH, f"{template.__name__}.conf")
|
||||
file_contents = IslandConfigParser.get_raw_config(template, island_client)
|
||||
save_to_file(file_path, file_contents)
|
||||
|
||||
|
||||
def save_to_file(file_path, contents):
|
||||
with open(file_path, "w") as file:
|
||||
file.write(contents)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
generate_templates()
|
|
@ -79,13 +79,23 @@ class AgentConfiguration:
|
|||
except MarshmallowError as err:
|
||||
raise InvalidConfigurationError(str(err))
|
||||
|
||||
@staticmethod
|
||||
def to_mapping(config: AgentConfiguration) -> Mapping[str, Any]:
|
||||
"""
|
||||
Serialize an AgentConfiguration to a Mapping
|
||||
|
||||
:param config: An AgentConfiguration
|
||||
:return: A Mapping that represents the AgentConfiguration
|
||||
"""
|
||||
return AgentConfigurationSchema().dump(config)
|
||||
|
||||
@staticmethod
|
||||
def to_json(config: AgentConfiguration) -> str:
|
||||
"""
|
||||
Serialize an AgentConfiguration to JSON
|
||||
|
||||
:param config: An AgentConfiguration
|
||||
:return: A JSON string representing the AgentConfiguration
|
||||
:return: A JSON string that represents the AgentConfiguration
|
||||
"""
|
||||
return AgentConfigurationSchema().dumps(config)
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ from monkey_island.cc.resources.agent_controls import StopAgentCheck, StopAllAge
|
|||
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.registration import Registration
|
||||
from monkey_island.cc.resources.blackbox.clear_caches import ClearCaches
|
||||
from monkey_island.cc.resources.blackbox.log_blackbox_endpoint import LogBlackboxEndpoint
|
||||
from monkey_island.cc.resources.blackbox.monkey_blackbox_endpoint import MonkeyBlackboxEndpoint
|
||||
from monkey_island.cc.resources.blackbox.telemetry_blackbox_endpoint import (
|
||||
|
@ -195,7 +194,6 @@ def init_restful_endpoints(api: FlaskDIWrapper):
|
|||
# Note: Preferably, the API will provide a rich feature set and allow access to all of the
|
||||
# necessary data. This would make these endpoints obsolete.
|
||||
api.add_resource(MonkeyBlackboxEndpoint)
|
||||
api.add_resource(ClearCaches)
|
||||
api.add_resource(LogBlackboxEndpoint)
|
||||
api.add_resource(TelemetryBlackboxEndpoint)
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ class AgentConfiguration(AbstractResource):
|
|||
try:
|
||||
configuration_object = AgentConfigurationObject.from_mapping(request.json)
|
||||
self._agent_configuration_repository.store_configuration(configuration_object)
|
||||
# API Spec: Should return 204 (NO CONTENT)
|
||||
return make_response({}, 200)
|
||||
except (InvalidConfigurationError, json.JSONDecodeError) as err:
|
||||
return make_response(
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
import logging
|
||||
|
||||
import flask_restful
|
||||
|
||||
from monkey_island.cc.resources.AbstractResource import AbstractResource
|
||||
from monkey_island.cc.resources.request_authentication import jwt_required
|
||||
from monkey_island.cc.services.attack.attack_report import AttackReportService
|
||||
from monkey_island.cc.services.reporting.report import ReportService
|
||||
|
||||
NOT_ALL_REPORTS_DELETED = "Not all reports have been cleared from the DB!"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ClearCaches(AbstractResource):
|
||||
# API Spec: Action, not a resource; RPC-style endpoint?
|
||||
urls = ["/api/test/clear-caches"]
|
||||
"""
|
||||
Used for timing tests - we want to get actual execution time of functions in BlackBox without
|
||||
caching -
|
||||
so we use this to clear the caches.
|
||||
:note: DO NOT CALL THIS IN PRODUCTION CODE as this will slow down the user experience.
|
||||
"""
|
||||
|
||||
@jwt_required
|
||||
def get(self, **kw):
|
||||
try:
|
||||
logger.warning("Trying to clear caches! Make sure this is not production")
|
||||
ReportService.delete_saved_report_if_exists()
|
||||
AttackReportService.delete_saved_report_if_exists()
|
||||
# TODO: Monkey.clear_caches(), clear LRU caches of function in the Monkey object
|
||||
except RuntimeError as e:
|
||||
logger.exception(e)
|
||||
flask_restful.abort(500, error_info=str(e))
|
||||
|
||||
if ReportService.is_report_generated() or AttackReportService.is_report_generated():
|
||||
logger.error(NOT_ALL_REPORTS_DELETED)
|
||||
flask_restful.abort(500, error_info=NOT_ALL_REPORTS_DELETED)
|
||||
|
||||
return {"success": "true"}
|
|
@ -29,7 +29,7 @@ class PropagationCredentials(AbstractResource):
|
|||
return propagation_credentials, HTTPStatus.OK
|
||||
|
||||
def post(self, collection=None):
|
||||
credentials = [Credentials.from_json(c) for c in request.json]
|
||||
credentials = [Credentials.from_mapping(c) for c in request.json]
|
||||
|
||||
if collection == _configured_collection:
|
||||
self._credentials_repository.save_configured_credentials(credentials)
|
||||
|
|
|
@ -137,14 +137,6 @@ class AttackReportService:
|
|||
generated_report = mongo.db.attack_report.find_one({})
|
||||
return generated_report is not None
|
||||
|
||||
@staticmethod
|
||||
def delete_saved_report_if_exists():
|
||||
delete_result = mongo.db.attack_report.delete_many({})
|
||||
if mongo.db.attack_report.count_documents({}) != 0:
|
||||
raise RuntimeError(
|
||||
"Attack Report cache not cleared. DeleteResult: " + delete_result.raw_result
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_techniques_for_report():
|
||||
"""
|
||||
|
|
|
@ -499,19 +499,6 @@ class ReportService:
|
|||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def delete_saved_report_if_exists():
|
||||
"""
|
||||
This function clears the saved report from the DB.
|
||||
|
||||
:raises RuntimeError if deletion failed
|
||||
"""
|
||||
delete_result = mongo.db.report.delete_many({})
|
||||
if mongo.db.report.count_documents({}) != 0:
|
||||
raise RuntimeError(
|
||||
"Report cache not cleared. DeleteResult: " + delete_result.raw_result
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_report():
|
||||
if not ReportService.is_latest_report_exists():
|
||||
|
|
|
@ -13,7 +13,12 @@ export function reformatConfig(config, reverse = false) {
|
|||
}
|
||||
formattedConfig['keep_tunnel_open_time'] = formattedConfig['advanced']['keep_tunnel_open_time'];
|
||||
} else {
|
||||
if(formattedConfig['payloads'].length !== 0)
|
||||
{
|
||||
formattedConfig['payloads'] = formattedConfig['payloads'][0]['options'];
|
||||
} else {
|
||||
formattedConfig['payloads'] = {'encryption': {}, 'other_behaviors': {}}
|
||||
}
|
||||
formattedConfig['advanced'] = {};
|
||||
formattedConfig['advanced']['keep_tunnel_open_time'] = formattedConfig['keep_tunnel_open_time'];
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
from tests.common.example_agent_configuration import AGENT_CONFIGURATION
|
||||
|
||||
from common.configuration.agent_configuration import AgentConfiguration
|
||||
from common.configuration import DEFAULT_AGENT_CONFIGURATION
|
||||
from monkey_island.cc.repository import IAgentConfigurationRepository
|
||||
|
||||
|
||||
class InMemoryAgentConfigurationRepository(IAgentConfigurationRepository):
|
||||
def __init__(self):
|
||||
self._default_configuration = AgentConfiguration.from_mapping(AGENT_CONFIGURATION)
|
||||
self._default_configuration = DEFAULT_AGENT_CONFIGURATION
|
||||
self._configuration = self._default_configuration
|
||||
|
||||
def get_configuration(self):
|
||||
|
|
|
@ -28,7 +28,6 @@ from tests.common.example_agent_configuration import (
|
|||
)
|
||||
|
||||
from common.configuration import AgentConfiguration, InvalidConfigurationError
|
||||
from common.configuration.agent_configuration import AgentConfigurationSchema
|
||||
from common.configuration.agent_sub_configuration_schemas import (
|
||||
CustomPBAConfigurationSchema,
|
||||
ExploitationConfigurationSchema,
|
||||
|
@ -178,16 +177,13 @@ def test_incorrect_type():
|
|||
AgentConfiguration(**valid_config_dict)
|
||||
|
||||
|
||||
def test_from_dict():
|
||||
schema = AgentConfigurationSchema()
|
||||
dict_ = deepcopy(AGENT_CONFIGURATION)
|
||||
def test_to_from_mapping():
|
||||
config = AgentConfiguration.from_mapping(AGENT_CONFIGURATION)
|
||||
|
||||
config = AgentConfiguration.from_mapping(dict_)
|
||||
|
||||
assert schema.dump(config) == dict_
|
||||
assert AgentConfiguration.to_mapping(config) == AGENT_CONFIGURATION
|
||||
|
||||
|
||||
def test_from_dict__invalid_data():
|
||||
def test_from_mapping__invalid_data():
|
||||
dict_ = deepcopy(AGENT_CONFIGURATION)
|
||||
dict_["payloads"] = "payloads"
|
||||
|
||||
|
@ -195,14 +191,11 @@ def test_from_dict__invalid_data():
|
|||
AgentConfiguration.from_mapping(dict_)
|
||||
|
||||
|
||||
def test_from_json():
|
||||
schema = AgentConfigurationSchema()
|
||||
dict_ = deepcopy(AGENT_CONFIGURATION)
|
||||
def test_to_from_json():
|
||||
original_config = AgentConfiguration.from_mapping(AGENT_CONFIGURATION)
|
||||
config_json = AgentConfiguration.to_json(original_config)
|
||||
|
||||
config = AgentConfiguration.from_json(json.dumps(dict_))
|
||||
|
||||
assert isinstance(config, AgentConfiguration)
|
||||
assert schema.dump(config) == dict_
|
||||
assert AgentConfiguration.from_json(config_json) == original_config
|
||||
|
||||
|
||||
def test_from_json__invalid_data():
|
||||
|
@ -211,9 +204,3 @@ def test_from_json__invalid_data():
|
|||
|
||||
with pytest.raises(InvalidConfigurationError):
|
||||
AgentConfiguration.from_json(json.dumps(invalid_dict))
|
||||
|
||||
|
||||
def test_to_json():
|
||||
config = deepcopy(AGENT_CONFIGURATION)
|
||||
|
||||
assert json.loads(AgentConfiguration.to_json(config)) == AGENT_CONFIGURATION
|
||||
|
|
|
@ -6,8 +6,13 @@ 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 common.configuration import AgentConfiguration
|
||||
from monkey_island.cc.repository import IAgentConfigurationRepository
|
||||
from monkey_island.cc.resources.agent_configuration import AgentConfiguration
|
||||
from monkey_island.cc.resources.agent_configuration import (
|
||||
AgentConfiguration as AgentConfigurationResource,
|
||||
)
|
||||
|
||||
AGENT_CONFIGURATION_URL = get_url_for_resource(AgentConfigurationResource)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -21,30 +26,25 @@ def flask_client(build_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.post(
|
||||
AGENT_CONFIGURATION_URL,
|
||||
json=AgentConfiguration.to_mapping(AGENT_CONFIGURATION),
|
||||
follow_redirects=True,
|
||||
)
|
||||
resp = flask_client.get(agent_configuration_url)
|
||||
assert resp.status_code == 200
|
||||
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"})
|
||||
)
|
||||
resp = flask_client.post(AGENT_CONFIGURATION_URL, json={"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!")
|
||||
resp = flask_client.post(AGENT_CONFIGURATION_URL, data="InvalidJson!")
|
||||
|
||||
assert resp.status_code == 400
|
||||
|
|
|
@ -89,8 +89,8 @@ def test_propagation_credentials_endpoint__post_stolen(flask_client, credentials
|
|||
resp = flask_client.post(
|
||||
url,
|
||||
json=[
|
||||
Credentials.to_json(LM_HASH_CREDENTIALS),
|
||||
Credentials.to_json(NT_HASH_CREDENTIALS),
|
||||
Credentials.to_mapping(LM_HASH_CREDENTIALS),
|
||||
Credentials.to_mapping(NT_HASH_CREDENTIALS),
|
||||
],
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.NO_CONTENT
|
||||
|
@ -134,8 +134,8 @@ def test_propagation_credentials_endpoint__post_not_found(flask_client):
|
|||
resp = flask_client.post(
|
||||
NON_EXISTENT_COLLECTION_URL,
|
||||
json=[
|
||||
Credentials.to_json(LM_HASH_CREDENTIALS),
|
||||
Credentials.to_json(NT_HASH_CREDENTIALS),
|
||||
Credentials.to_mapping(LM_HASH_CREDENTIALS),
|
||||
Credentials.to_mapping(NT_HASH_CREDENTIALS),
|
||||
],
|
||||
)
|
||||
assert resp.status_code == HTTPStatus.NOT_FOUND
|
||||
|
|
Loading…
Reference in New Issue