Merge pull request #2103 from guardicore/2092-modify-blackbox-tests

Modify BB tests to use new configuration objects
This commit is contained in:
Mike Salvatore 2022-07-25 07:04:10 -04:00 committed by GitHub
commit 4d3fb03da2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 124 additions and 1208 deletions

View File

@ -1,2 +1 @@
logs/
/blackbox/tests/performance/telemetry_sample

View File

@ -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.

View File

@ -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

View File

@ -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])}")

View File

@ -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:

View File

@ -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,
}

View File

@ -1,8 +0,0 @@
from abc import ABC, abstractmethod
class ConfigTemplate(ABC):
@property
@abstractmethod
def config_values(self) -> dict:
pass

View File

@ -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",
],
}
)

View File

@ -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"],
}
)

View File

@ -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",
],
}
)

View File

@ -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],
}
)

View File

@ -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",
],
}
)

View File

@ -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",
],
}
)

View File

@ -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],
}
)

View File

@ -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"
):

View File

@ -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", ".")

View File

@ -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

View File

@ -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

View File

@ -1,8 +0,0 @@
from enum import Enum
class SupportedRequestMethod(Enum):
GET = "GET"
POST = "POST"
PATCH = "PATCH"
DELETE = "DELETE"

View File

@ -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

View File

@ -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"
)

View File

@ -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

View File

@ -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("")

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()]

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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"]))

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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(

View File

@ -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"}

View File

@ -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)

View File

@ -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():
"""

View File

@ -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():

View File

@ -13,7 +13,12 @@ export function reformatConfig(config, reverse = false) {
}
formattedConfig['keep_tunnel_open_time'] = formattedConfig['advanced']['keep_tunnel_open_time'];
} else {
formattedConfig['payloads'] = formattedConfig['payloads'][0]['options'];
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'];
}

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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