Merge pull request #1618 from guardicore/1532-remove-mitre-attack-config

1532 remove mitre attack config
This commit is contained in:
ilija-lazoroski 2021-11-22 12:06:45 +01:00 committed by GitHub
commit 6cc983dc21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 57 additions and 659 deletions

View File

@ -11,7 +11,7 @@
"content": [
{
"type": "text",
"text": "In order to make sure that the new `ScheduleJobs` PBA is shown in the configuration on the Monkey Island, you need to add its details to the configuration file(s). <br><br>\n\nSince this particular PBA is related to the MITRE techniques [T1168](https://attack.mitre.org/techniques/T1168) and [T1053](https://attack.mitre.org/techniques/T1053), make sure to link the PBA with these techniques in the configuration as well. <br><br>\n\nEach part of the configuration has an important role \n- *enum* — contains the relevant PBA's class name(s)\n- *title* — holds the name of the PBA which is displayed in the configuration on the Monkey Island\n- *info* — consists of an elaboration on the PBA's working which is displayed in the configuration on the Monkey Island\n- *attack_techniques* — has the IDs of the MITRE techniques associated with the PBA\n\n## Manual test \nOnce you think you're done...\n- Run the Monkey Island\n- You should be able to see your new PBA under the \"Monkey\" tab in the configuration, along with its information when you click on it\n- Further, when you enable/disable the associated MITRE techniques under the ATT&CK tab in the configuration, the PBA should also be enabled/disabled\n\n<img src=\"https://i.imgur.com/a5VSkL5.gif\" height=400>"
"text": "In order to make sure that the new `ScheduleJobs` PBA is shown in the configuration on the Monkey Island, you need to add its details to the configuration file(s). <br><br>\n\nSince this particular PBA is related to the MITRE techniques [T1168](https://attack.mitre.org/techniques/T1168) and [T1053](https://attack.mitre.org/techniques/T1053), make sure to link the PBA with these techniques in the configuration as well. <br><br>\n\nEach part of the configuration has an important role \n- *enum* — contains the relevant PBA's class name(s)\n- *title* — holds the name of the PBA which is displayed in the configuration on the Monkey Island\n- *info* — consists of an elaboration on the PBA's working which is displayed in the configuration on the Monkey Island\n- *attack_techniques* — has the IDs of the MITRE techniques associated with the PBA\n\n## Manual test \nOnce you think you're done...\n- Run the Monkey Island\n- You should be able to see your new PBA under the \"Monkey\" tab in the configuration, along with its information when you click on it\n\n<img src=\"https://i.imgur.com/a5VSkL5.gif\" height=400>"
},
{
"type": "snippet",

View File

@ -7,7 +7,7 @@
"hints": [
"See `ScheduleJobs` PBA for an example of a PBA which only uses shell commands.",
"Make sure to add the PBA to the configuration as well.",
"MITRE ATT&CK technique T1087 articulates that adversaries may attempt to get a listing of accounts on a system or within an environment which can help them determine which accounts can aid in follow-on behavior. Therefore, the AccountDiscovery PBA is relevant to it. Make sure to map this PBA to the MITRE ATT&CK configuration and report."
"MITRE ATT&CK technique T1087 articulates that adversaries may attempt to get a listing of accounts on a system or within an environment which can help them determine which accounts can aid in follow-on behavior. Therefore, the AccountDiscovery PBA is relevant to it which will enable the ATT&CK technique and show it in ATT&CK report."
]
},
"content": [

View File

@ -30,6 +30,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
- Custom monkey directory name config option. #1537
- Hostname system info collector. #1535
- Max iterations and timeout between iterations config options. #1600
- MITRE ATT&CK configuration screen. #1532
### Fixed
- A bug in network map page that caused delay of telemetry log loading. #1545

View File

@ -1,29 +0,0 @@
---
title: "MITRE ATT&CK assessment"
date: 2020-10-22T16:58:22+03:00
draft: false
description: "Assess your network security detection and prevention capabilities."
weight: 2
---
## Overview
The Infection Monkey can simulate various [ATT&CK](https://attack.mitre.org/matrices/enterprise/) techniques on the network. Use it to assess your security solutions' detection and prevention capabilities. The Infection Monkey will help you find which ATT&CK techniques go unnoticed and provide specific details along with suggested mitigations.
## Configuration
- **ATT&CK matrix** You can use the ATT&CK configuration section to select which techniques you want the Infection Monkey to simulate.
For the full simulation, use the default settings.
- **Exploits -> Credentials** This configuration value will be used for brute-forcing. The Infection Monkey uses the most popular default passwords and usernames, but feel free to adjust it according to the default passwords common in your network. Keep in mind a longer list means longer scanning times.
- **Network -> Scope** Disable “Local network scan” and instead provide specific network ranges in the “Scan target list”.
![ATT&CK matrix](/images/usage/scenarios/attack-matrix.png "ATT&CK matrix")
## Suggested run mode
Run the Infection Monkey on as many machines as you can. You can easily achieve this by selecting the “Manual” run option and executing the command shown on different machines in your environment manually or with your deployment tool. Additionally, you can use any other run options you see fit.
## Assessing results
The **ATT&CK Report** shows the status of simulations using ATT&CK techniques. Click on a technique to see more details about it and potential mitigations. Keep in mind that each technique display contains a question mark symbol that will take you to the official documentation of the specific ATT&CK technique used, where you can learn more about it.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

View File

@ -8,8 +8,6 @@ class ScanStatus(Enum):
SCANNED = 1
# Technique was attempted and succeeded
USED = 2
# Techique was disabled
DISABLED = 3
class UsageEnum(Enum):

View File

@ -8,7 +8,6 @@ from werkzeug.exceptions import NotFound
import monkey_island.cc.environment.environment_singleton as env_singleton
from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH
from monkey_island.cc.database import database, mongo
from monkey_island.cc.resources.attack.attack_config import AttackConfiguration
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
@ -93,9 +92,8 @@ def init_app_config(app, mongo_url):
# deciding to reset credentials and then still logging in with the old JWT.
app.config["JWT_SECRET_KEY"] = str(uuid.uuid4())
# By default, Flask sorts keys of JSON objects alphabetically, which messes with the ATT&CK
# matrix in the
# configuration. See https://flask.palletsprojects.com/en/1.1.x/config/#JSON_SORT_KEYS.
# By default, Flask sorts keys of JSON objects alphabetically.
# See https://flask.palletsprojects.com/en/1.1.x/config/#JSON_SORT_KEYS.
app.config["JSON_SORT_KEYS"] = False
app.json_encoder = CustomJSONEncoder
@ -166,7 +164,6 @@ def init_api_resources(api):
"/api/fileUpload/<string:file_type>?restore=<string:filename>",
)
api.add_resource(RemoteRun, "/api/remote-monkey", "/api/remote-monkey/")
api.add_resource(AttackConfiguration, "/api/attack")
api.add_resource(VersionUpdate, "/api/version-update", "/api/version-update/")
api.add_resource(RemotePortCheck, "/api/monkey_control/check_remote_port/<string:port>")
api.add_resource(StartedOnIsland, "/api/monkey_control/started_on_island")

View File

@ -1,35 +0,0 @@
import flask_restful
from flask import current_app, json, jsonify, request
from monkey_island.cc.resources.auth.auth import jwt_required
from monkey_island.cc.services.attack.attack_config import AttackConfig
class AttackConfiguration(flask_restful.Resource):
@jwt_required
def get(self):
return current_app.response_class(
json.dumps(
{"configuration": AttackConfig.get_config()},
indent=None,
separators=(",", ":"),
sort_keys=False,
)
+ "\n",
mimetype=current_app.config["JSONIFY_MIMETYPE"],
)
@jwt_required
def post(self):
"""
Based on request content this endpoint either resets ATT&CK configuration or updates it.
:return: Technique types dict with techniques on reset and nothing on update
"""
config_json = json.loads(request.data)
if "reset_attack_matrix" in config_json:
AttackConfig.reset_config()
return jsonify(configuration=AttackConfig.get_config())
else:
AttackConfig.update_config({"properties": json.loads(request.data)})
AttackConfig.apply_to_monkey_config()
return {}

View File

@ -1,205 +0,0 @@
import logging
from dpath import util
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.attack_schema import SCHEMA
from monkey_island.cc.services.config import ConfigService
logger = logging.getLogger(__name__)
class AttackConfig(object):
def __init__(self):
pass
@staticmethod
def get_config():
config = mongo.db.attack.find_one({"name": "newconfig"})["properties"]
return config
@staticmethod
def get_technique(technique_id):
"""
Gets technique by id
:param technique_id: E.g. T1210
:return: Technique object or None if technique is not found
"""
attack_config = AttackConfig.get_config()
for config_key, attack_type in list(attack_config.items()):
for type_key, technique in list(attack_type["properties"].items()):
if type_key == technique_id:
return technique
return None
@staticmethod
def get_config_schema():
return SCHEMA
@staticmethod
def reset_config():
AttackConfig.update_config(SCHEMA)
@staticmethod
def update_config(config_json):
mongo.db.attack.update({"name": "newconfig"}, {"$set": config_json}, upsert=True)
return True
@staticmethod
def apply_to_monkey_config():
"""
Applies ATT&CK matrix to the monkey configuration
:return:
"""
attack_techniques = AttackConfig.get_technique_values()
monkey_config = ConfigService.get_config(False, True, True)
monkey_schema = ConfigService.get_config_schema()
AttackConfig.set_arrays(attack_techniques, monkey_config, monkey_schema)
AttackConfig.set_booleans(attack_techniques, monkey_config, monkey_schema)
ConfigService.update_config(monkey_config, True)
@staticmethod
def set_arrays(attack_techniques, monkey_config, monkey_schema):
"""
Sets exploiters/scanners/PBAs and other array type fields in monkey's config according to
ATT&CK matrix
:param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...}
:param monkey_config: Monkey island's configuration
:param monkey_schema: Monkey configuration schema
"""
for key, definition in list(monkey_schema["definitions"].items()):
for array_field in definition["anyOf"]:
# Check if current array field has attack_techniques assigned to it
if "attack_techniques" in array_field and array_field["attack_techniques"]:
should_remove = not AttackConfig.should_enable_field(
array_field["attack_techniques"], attack_techniques
)
# If exploiter's attack technique is disabled, disable the exploiter/scanner/PBA
AttackConfig.r_alter_array(
monkey_config, key, array_field["enum"][0], remove=should_remove
)
@staticmethod
def set_booleans(attack_techniques, monkey_config, monkey_schema):
"""
Sets boolean type fields, like "should use mimikatz?" in monkey's config according to
ATT&CK matrix
:param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...}
:param monkey_config: Monkey island's configuration
:param monkey_schema: Monkey configuration schema
"""
for key, value in list(monkey_schema["properties"].items()):
AttackConfig.r_set_booleans([key], value, attack_techniques, monkey_config)
@staticmethod
def r_set_booleans(path, value, attack_techniques, monkey_config):
"""
Recursively walks through monkey configuration (DFS) to find which boolean fields needs to
be set and sets them
according to ATT&CK matrix.
:param path: Property names that leads to current value. E.g. ['monkey', 'system_info',
'should_use_mimikatz']
:param value: Value of config property
:param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...}
:param monkey_config: Monkey island's configuration
"""
if isinstance(value, dict):
dictionary = {}
# If 'value' is a boolean value that should be set:
if (
"type" in value
and value["type"] == "boolean"
and "attack_techniques" in value
and value["attack_techniques"]
):
AttackConfig.set_bool_conf_val(
path,
AttackConfig.should_enable_field(value["attack_techniques"], attack_techniques),
monkey_config,
)
# If 'value' is dict, we go over each of it's fields to search for booleans
elif "properties" in value:
dictionary = value["properties"]
else:
dictionary = value
for key, item in list(dictionary.items()):
path.append(key)
AttackConfig.r_set_booleans(path, item, attack_techniques, monkey_config)
# Method enumerated everything in current path, goes back a level.
del path[-1]
@staticmethod
def set_bool_conf_val(path, val, monkey_config):
"""
Changes monkey's configuration by setting one of its boolean fields value
:param path: Path to boolean value in monkey's configuration. ['monkey', 'system_info',
'should_use_mimikatz']
:param val: Boolean
:param monkey_config: Monkey's configuration
"""
util.set(monkey_config, "/".join(path), val)
@staticmethod
def should_enable_field(field_techniques, users_techniques):
"""
Determines whether a single config field should be enabled or not.
:param field_techniques: ATT&CK techniques that field uses
:param users_techniques: ATT&CK techniques that user chose
:return: True, if user enabled all techniques used by the field, false otherwise
"""
for technique in field_techniques:
try:
if not users_techniques[technique]:
return False
except KeyError:
logger.error(
"Attack technique %s is defined in schema, but not implemented." % technique
)
return True
@staticmethod
def r_alter_array(config_value, array_name, field, remove=True):
"""
Recursively searches config (DFS) for array and removes/adds a field.
:param config_value: Some object/value from config
:param array_name: Name of array this method should search
:param field: Field in array that this method should add/remove
:param remove: Removes field from array if true, adds it if false
"""
if isinstance(config_value, dict):
if array_name in config_value and isinstance(config_value[array_name], list):
if remove and field in config_value[array_name]:
config_value[array_name].remove(field)
elif not remove and field not in config_value[array_name]:
config_value[array_name].append(field)
else:
for prop in list(config_value.items()):
AttackConfig.r_alter_array(prop[1], array_name, field, remove)
@staticmethod
def get_technique_values():
"""
Parses ATT&CK config into a dict of techniques and corresponding values.
:return: Dictionary of techniques. Format: {"T1110": True, "T1075": False, ...}
"""
attack_config = AttackConfig.get_config()
techniques = {}
for type_name, attack_type in list(attack_config.items()):
for key, technique in list(attack_type["properties"].items()):
techniques[key] = technique["value"]
return techniques
@staticmethod
def get_techniques_for_report():
"""
:return: Format: {"T1110": {"selected": True, "type": "Credential Access", "T1075": ...}
"""
attack_config = AttackConfig.get_config()
techniques = {}
for type_name, attack_type in list(attack_config.items()):
for key, technique in list(attack_type["properties"].items()):
techniques[key] = {
"selected": technique["value"],
"type": SCHEMA["properties"][type_name]["title"],
}
return techniques

View File

@ -2,7 +2,7 @@ import logging
from monkey_island.cc.database import mongo
from monkey_island.cc.models import Monkey
from monkey_island.cc.services.attack.attack_config import AttackConfig
from monkey_island.cc.services.attack.attack_schema import SCHEMA
from monkey_island.cc.services.attack.technique_reports import (
T1003,
T1005,
@ -102,7 +102,7 @@ class AttackReportService:
"meta": {"latest_monkey_modifytime": Monkey.get_latest_modifytime()},
"name": REPORT_NAME,
}
for tech_id, tech_info in list(AttackConfig.get_techniques_for_report().items()):
for tech_id, tech_info in list(AttackReportService.get_techniques_for_report().items()):
try:
technique_report_data = TECHNIQUES[tech_id].get_report_data()
technique_report_data.update(tech_info)
@ -146,3 +146,17 @@ class AttackReportService:
raise RuntimeError(
"Attack Report cache not cleared. DeleteResult: " + delete_result.raw_result
)
@staticmethod
def get_techniques_for_report():
"""
:return: Format: {"T1110": {"type": "Credential Access", "T1075": ...}
"""
attack_config = SCHEMA["properties"]
techniques = {}
for type_name, attack_type in list(attack_config.items()):
for key, technique in list(attack_type["properties"].items()):
techniques[key] = {
"type": SCHEMA["properties"][type_name]["title"],
}
return techniques

View File

@ -10,8 +10,6 @@ SCHEMA = {
"T1059": {
"title": "Command line interface",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1059",
"description": "Adversaries may use command-line interfaces to interact with "
"systems "
@ -20,8 +18,6 @@ SCHEMA = {
"T1106": {
"title": "Execution through API",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1106",
"description": "Adversary tools may directly use the Windows application "
"programming interface (API) to execute binaries.",
@ -30,8 +26,6 @@ SCHEMA = {
"T1086": {
"title": "PowerShell",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1086",
"description": "Adversaries can use PowerShell to perform a number of actions,"
" including discovery of information and execution of code.",
@ -39,8 +33,6 @@ SCHEMA = {
"T1064": {
"title": "Scripting",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1064",
"description": "Adversaries may use scripts to aid in operations and "
"perform multiple actions that would otherwise be manual.",
@ -48,8 +40,6 @@ SCHEMA = {
"T1035": {
"title": "Service execution",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1035",
"description": "Adversaries may execute a binary, command, or script via a "
"method "
@ -60,8 +50,6 @@ SCHEMA = {
"T1154": {
"title": "Trap",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1154",
"description": "Adversaries can use the trap command to register code to be "
"executed "
@ -77,8 +65,6 @@ SCHEMA = {
"T1156": {
"title": ".bash_profile and .bashrc",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1156",
"description": "Adversaries may abuse shell scripts by "
"inserting arbitrary shell commands to gain persistence, which "
@ -89,8 +75,6 @@ SCHEMA = {
"T1136": {
"title": "Create account",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1136",
"description": "Adversaries with a sufficient level of access "
"may create a local system, domain, or cloud tenant account.",
@ -98,8 +82,6 @@ SCHEMA = {
"T1158": {
"title": "Hidden files and directories",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1158",
"description": "Adversaries can hide files and folders on the system "
"and evade a typical user or system analysis that does not "
@ -108,8 +90,6 @@ SCHEMA = {
"T1168": {
"title": "Local job scheduling",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1168/",
"description": "Linux supports multiple methods for creating pre-scheduled and "
"periodic background jobs. Job scheduling can be used by "
@ -121,8 +101,6 @@ SCHEMA = {
"T1504": {
"title": "PowerShell profile",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1504",
"description": "Adversaries may gain persistence and elevate privileges "
"in certain situations by abusing PowerShell profiles which "
@ -132,8 +110,6 @@ SCHEMA = {
"T1053": {
"title": "Scheduled task",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1053",
"description": "Windows utilities can be used to schedule programs or scripts "
"to "
@ -146,8 +122,6 @@ SCHEMA = {
"T1166": {
"title": "Setuid and Setgid",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1166",
"description": "Adversaries can set the setuid or setgid bits to get code "
"running in "
@ -163,8 +137,6 @@ SCHEMA = {
"T1197": {
"title": "BITS jobs",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1197",
"description": "Adversaries may abuse BITS to download, execute, "
"and even clean up after running malicious code.",
@ -172,8 +144,6 @@ SCHEMA = {
"T1146": {
"title": "Clear command history",
"type": "bool",
"value": False,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1146",
"description": "Adversaries may clear/disable command history of a compromised "
"account to conceal the actions undertaken during an intrusion.",
@ -181,8 +151,6 @@ SCHEMA = {
"T1107": {
"title": "File Deletion",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1107",
"description": "Adversaries may remove files over the course of an intrusion "
"to keep their footprint low or remove them at the end as part "
@ -191,8 +159,6 @@ SCHEMA = {
"T1222": {
"title": "File permissions modification",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1222",
"description": "Adversaries may modify file permissions/attributes to evade "
"intended DACLs.",
@ -200,8 +166,6 @@ SCHEMA = {
"T1099": {
"title": "Timestomping",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1099",
"description": "Adversaries may modify file time attributes to hide "
"new/changes to existing "
@ -211,8 +175,6 @@ SCHEMA = {
"T1216": {
"title": "Signed script proxy execution",
"type": "bool",
"value": False,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1216",
"description": "Adversaries may use scripts signed with trusted certificates "
"to proxy execution of malicious files on Windows systems. This behavior could "
@ -229,8 +191,6 @@ SCHEMA = {
"T1110": {
"title": "Brute force",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1110",
"description": "Adversaries may use brute force techniques to attempt access "
"to accounts "
@ -241,8 +201,6 @@ SCHEMA = {
"T1003": {
"title": "Credential dumping",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1003",
"description": "Mapped with T1078 Valid Accounts because "
"both techniques require"
@ -257,8 +215,6 @@ SCHEMA = {
"T1145": {
"title": "Private keys",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1145",
"description": "Adversaries may gather private keys from compromised systems "
"for use in "
@ -277,8 +233,6 @@ SCHEMA = {
"T1087": {
"title": "Account Discovery",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1087",
"description": "Adversaries may attempt to get a listing of accounts on a "
"system or "
@ -289,8 +243,6 @@ SCHEMA = {
"T1018": {
"title": "Remote System Discovery",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1018",
"description": "Adversaries will likely attempt to get a listing of other "
"systems by IP address, "
@ -300,8 +252,6 @@ SCHEMA = {
"T1082": {
"title": "System information discovery",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1082",
"depends_on": ["T1016", "T1005"],
"description": "An adversary may attempt to get detailed information about the "
@ -312,8 +262,6 @@ SCHEMA = {
"T1016": {
"title": "System network configuration discovery",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1016",
"depends_on": ["T1005", "T1082"],
"description": "Adversaries will likely look for details about the network "
@ -332,8 +280,6 @@ SCHEMA = {
"T1210": {
"title": "Exploitation of Remote services",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1210",
"description": "Exploitation of a software vulnerability occurs when an "
"adversary "
@ -345,8 +291,6 @@ SCHEMA = {
"T1075": {
"title": "Pass the hash",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1075",
"description": "Pass the hash (PtH) is a method of authenticating as a user "
"without "
@ -355,8 +299,6 @@ SCHEMA = {
"T1105": {
"title": "Remote file copy",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1105",
"description": "Files may be copied from one system to another to stage "
"adversary tools or other files over the course of an operation.",
@ -364,8 +306,6 @@ SCHEMA = {
"T1021": {
"title": "Remote services",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1021",
"depends_on": ["T1110"],
"description": "An adversary may use Valid Accounts to log into a service"
@ -381,8 +321,6 @@ SCHEMA = {
"T1005": {
"title": "Data from local system",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1005",
"depends_on": ["T1016", "T1082"],
"description": "Sensitive data can be collected from local system sources, "
@ -400,8 +338,6 @@ SCHEMA = {
"T1090": {
"title": "Connection proxy",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1090",
"description": "A connection proxy is used to direct network traffic between "
"systems "
@ -410,8 +346,6 @@ SCHEMA = {
"T1065": {
"title": "Uncommonly used port",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1065",
"description": "Adversaries may conduct C2 communications over a non-standard "
"port to bypass proxies and firewalls that have been improperly "
@ -420,8 +354,6 @@ SCHEMA = {
"T1188": {
"title": "Multi-hop proxy",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1188",
"description": "To disguise the source of malicious traffic, "
"adversaries may chain together multiple proxies.",
@ -436,8 +368,6 @@ SCHEMA = {
"T1041": {
"title": "Exfiltration Over Command and Control Channel",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1041",
"description": "Data exfiltration is performed over the Command and Control "
"channel.",

View File

@ -35,7 +35,6 @@ class T1003(AttackTechnique):
@staticmethod
def get_report_data():
@T1003.is_status_disabled
def get_technique_status_and_data():
if mongo.db.telemetry.count_documents(T1003.query):
status = ScanStatus.USED.value

View File

@ -36,7 +36,6 @@ class T1016(AttackTechnique):
@staticmethod
def get_report_data():
@T1016.is_status_disabled
def get_technique_status_and_data():
network_info = list(mongo.db.telemetry.aggregate(T1016.query))
status = ScanStatus.USED.value if network_info else ScanStatus.UNSCANNED.value

View File

@ -40,7 +40,6 @@ class T1018(AttackTechnique):
@staticmethod
def get_report_data():
@T1018.is_status_disabled
def get_technique_status_and_data():
scan_info = list(mongo.db.telemetry.aggregate(T1018.query))
if scan_info:

View File

@ -35,7 +35,6 @@ class T1021(AttackTechnique):
@staticmethod
def get_report_data():
@T1021.is_status_disabled
def get_technique_status_and_data():
attempts = []
if mongo.db.telemetry.count_documents(T1021.scanned_query):

View File

@ -12,7 +12,6 @@ class T1041(AttackTechnique):
@staticmethod
def get_report_data():
@T1041.is_status_disabled
def get_technique_status_and_data():
monkeys = list(Monkey.objects())
info = [

View File

@ -26,7 +26,6 @@ class T1059(AttackTechnique):
@staticmethod
def get_report_data():
@T1059.is_status_disabled
def get_technique_status_and_data():
cmd_data = list(mongo.db.telemetry.aggregate(T1059.query))
if cmd_data:

View File

@ -51,7 +51,6 @@ class T1075(AttackTechnique):
@staticmethod
def get_report_data():
@T1075.is_status_disabled
def get_technique_status_and_data():
successful_logins = list(mongo.db.telemetry.aggregate(T1075.query))
if successful_logins:

View File

@ -64,7 +64,6 @@ class T1082(AttackTechnique):
@staticmethod
def get_report_data():
@T1082.is_status_disabled
def get_technique_status_and_data():
system_info = list(mongo.db.telemetry.aggregate(T1082.query))
if system_info:

View File

@ -60,7 +60,6 @@ class T1086(AttackTechnique):
@staticmethod
def get_report_data():
@T1086.is_status_disabled
def get_technique_status_and_data():
exploit_cmd_data = list(mongo.db.telemetry.aggregate(T1086.query_for_exploits))
pba_cmd_data = list(mongo.db.telemetry.aggregate(T1086.query_for_pbas))

View File

@ -12,7 +12,6 @@ class T1090(AttackTechnique):
@staticmethod
def get_report_data():
@T1090.is_status_disabled
def get_technique_status_and_data():
monkeys = Monkey.get_tunneled_monkeys()
monkeys = [monkey.get_network_info() for monkey in monkeys]

View File

@ -33,7 +33,6 @@ class T1110(AttackTechnique):
@staticmethod
def get_report_data():
@T1110.is_status_disabled
def get_technique_status_and_data():
attempts = list(mongo.db.telemetry.aggregate(T1110.query))
succeeded = False

View File

@ -29,7 +29,6 @@ class T1145(AttackTechnique):
@staticmethod
def get_report_data():
@T1145.is_status_disabled
def get_technique_status_and_data():
ssh_info = list(mongo.db.telemetry.aggregate(T1145.query))
if ssh_info:

View File

@ -12,7 +12,6 @@ class T1188(AttackTechnique):
@staticmethod
def get_report_data():
@T1188.is_status_disabled
def get_technique_status_and_data():
monkeys = Monkey.get_tunneled_monkeys()
hops = []

View File

@ -16,7 +16,6 @@ class T1210(AttackTechnique):
@staticmethod
def get_report_data():
@T1210.is_status_disabled
def get_technique_status_and_data():
scanned_services = T1210.get_scanned_services()
exploited_services = T1210.get_exploited_services()
@ -30,9 +29,7 @@ class T1210(AttackTechnique):
status_and_data = get_technique_status_and_data()
status = status_and_data[0]
if status == ScanStatus.DISABLED.value:
scanned_services, exploited_services = [], []
else:
scanned_services, exploited_services = status_and_data[1], status_and_data[2]
data = {"title": T1210.technique_title()}

View File

@ -6,19 +6,14 @@ from common.utils.attack_utils import ScanStatus
from common.utils.code_utils import abstractstatic
from monkey_island.cc.database import mongo
from monkey_island.cc.models.attack.attack_mitigations import AttackMitigations
from monkey_island.cc.services.attack.attack_config import AttackConfig
from monkey_island.cc.services.config_schema.config_schema import SCHEMA
from monkey_island.cc.services.attack.attack_schema import SCHEMA as ATTACK_SCHEMA
from monkey_island.cc.services.config_schema.config_schema_per_attack_technique import (
ConfigSchemaPerAttackTechnique,
)
logger = logging.getLogger(__name__)
disabled_msg = (
"This technique has been disabled. "
+ "You can enable it from the [configuration page](../../configure)."
)
class AttackTechnique(object, metaclass=abc.ABCMeta):
""" Abstract class for ATT&CK report components """
@ -81,9 +76,7 @@ class AttackTechnique(object, metaclass=abc.ABCMeta):
Gets the status of a certain attack technique.
:return: ScanStatus numeric value
"""
if not cls._is_enabled_in_config():
return ScanStatus.DISABLED.value
elif mongo.db.telemetry.find_one(
if mongo.db.telemetry.find_one(
{
"telem_category": "attack",
"data.status": ScanStatus.USED.value,
@ -118,8 +111,6 @@ class AttackTechnique(object, metaclass=abc.ABCMeta):
:param status: Enum from common/attack_utils.py integer value
:return: message string
"""
if status == ScanStatus.DISABLED.value:
return disabled_msg
if status == ScanStatus.UNSCANNED.value:
if not cls.config_schema_per_attack_technique:
cls.config_schema_per_attack_technique = (
@ -172,7 +163,7 @@ class AttackTechnique(object, metaclass=abc.ABCMeta):
"""
:return: techniques title. E.g. "T1110 Brute force"
"""
return AttackConfig.get_technique(cls.tech_id)["title"]
return get_technique(cls.tech_id)["title"]
@classmethod
def get_tech_base_data(cls):
@ -205,17 +196,16 @@ class AttackTechnique(object, metaclass=abc.ABCMeta):
else:
return {}
@classmethod
def is_status_disabled(cls, get_technique_status_and_data) -> bool:
def check_if_disabled_in_config():
return (
(ScanStatus.DISABLED.value, [])
if not cls._is_enabled_in_config()
else get_technique_status_and_data()
)
return check_if_disabled_in_config
@classmethod
def _is_enabled_in_config(cls) -> bool:
return AttackConfig.get_technique_values()[cls.tech_id]
def get_technique(technique_id):
"""
Gets technique by id
:param technique_id: E.g. T1210
:return: Technique object or None if technique is not found
"""
attack_config = ATTACK_SCHEMA["properties"]
for config_key, attack_type in list(attack_config.items()):
for type_key, technique in list(attack_type["properties"].items()):
if type_key == technique_id:
return technique
return None

View File

@ -54,7 +54,6 @@ class PostBreachTechnique(AttackTechnique, metaclass=abc.ABCMeta):
:return: Technique's report data aggregated from the database
"""
@cls.is_status_disabled
def get_technique_status_and_data():
info = list(
mongo.db.telemetry.aggregate(cls.get_pba_query(cls.pba_names, cls.relevant_systems))

View File

@ -4,7 +4,6 @@ from flask import jsonify
from monkey_island.cc.database import mongo
from monkey_island.cc.models.attack.attack_mitigations import AttackMitigations
from monkey_island.cc.services.attack.attack_config import AttackConfig
from monkey_island.cc.services.config import ConfigService
logger = logging.getLogger(__name__)
@ -24,7 +23,6 @@ class Database(object):
if not x.startswith("system.") and not x == AttackMitigations.COLLECTION_NAME
]
ConfigService.init_config()
AttackConfig.reset_config()
logger.info("DB was reset")
return jsonify(status="OK")

View File

@ -1,123 +0,0 @@
import React from 'react';
import Checkbox from '../ui-components/Checkbox'
import Tooltip from 'react-tooltip-lite'
import AuthComponent from '../AuthComponent';
import ReactTable from 'react-table';
import 'filepond/dist/filepond.min.css';
import '../../styles/components/Tooltip.scss';
import {Col} from 'react-bootstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCircle as faCircle } from '@fortawesome/free-solid-svg-icons/faCircle';
import { faCircle as faCircleThin } from '@fortawesome/free-regular-svg-icons/faCircle';
class ConfigMatrixComponent extends AuthComponent {
constructor(props) {
super(props);
this.state = {lastAction: 'none'}
}
// Finds which attack type has most techniques and returns that number
static findMaxTechniques(data) {
let maxLen = 0;
data.forEach(function (techType) {
if (Object.keys(techType.properties).length > maxLen) {
maxLen = Object.keys(techType.properties).length
}
});
return maxLen
}
// Parses ATT&CK config schema into data suitable for react-table (ATT&CK matrix)
static parseTechniques(data, maxLen) {
let techniques = [];
// Create rows with attack techniques
for (let i = 0; i < maxLen; i++) {
let row = {};
data.forEach(function (techType) {
let rowColumn = {};
rowColumn.techName = techType.title;
if (i <= Object.keys(techType.properties).length) {
rowColumn.technique = Object.values(techType.properties)[i];
if (rowColumn.technique) {
rowColumn.technique.name = Object.keys(techType.properties)[i];
}
} else {
rowColumn.technique = null;
}
row[rowColumn.techName] = rowColumn;
});
techniques.push(row)
}
return techniques;
}
getColumns(matrixData) {
return Object.keys(matrixData[0]).map((key) => {
return {
Header: key,
id: key,
accessor: x => this.renderTechnique(x[key].technique),
style: {'whiteSpace': 'unset'}
};
});
}
renderTechnique(technique) {
if (technique == null) {
return (<div/>)
} else {
return (<Tooltip content={technique.description} direction="down">
<Checkbox checked={technique.value}
necessary={technique.necessary}
name={technique.name}
changeHandler={this.props.change}>
{technique.title}
</Checkbox>
</Tooltip>)
}
}
getTableData = (config) => {
let configCopy = JSON.parse(JSON.stringify(config));
let maxTechniques = ConfigMatrixComponent.findMaxTechniques(Object.values(configCopy));
let matrixTableData = ConfigMatrixComponent.parseTechniques(Object.values(configCopy), maxTechniques);
let columns = this.getColumns(matrixTableData);
return {'columns': columns, 'matrixTableData': matrixTableData, 'maxTechniques': maxTechniques}
};
renderLegend = () => {
return (
<div id="header" className="row justify-content-between attack-legend">
<Col xs={4}>
<FontAwesomeIcon icon={faCircleThin} className="icon-unchecked"/>
<span> - Disabled</span>
</Col>
<Col xs={4}>
<FontAwesomeIcon icon={faCircle} className="icon-checked"/>
<span> - Enabled</span>
</Col>
<Col xs={4}>
<FontAwesomeIcon icon={faCircle} className="icon-mandatory"/>
<span> - Mandatory</span>
</Col>
</div>)
};
render() {
let tableData = this.getTableData(this.props.configuration);
return (
<div>
{this.renderLegend()}
<div className={'attack-matrix'}>
<ReactTable columns={tableData['columns']}
data={tableData['matrixTableData']}
showPagination={false}
defaultPageSize={tableData['maxTechniques']}/>
</div>
</div>);
}
}
export default ConfigMatrixComponent;

View File

@ -1,5 +1,4 @@
const CONFIGURATION_TABS = {
ATTACK: 'attack',
BASIC: 'basic',
BASIC_NETWORK: 'basic_network',
RANSOMWARE: 'ransomware',
@ -8,7 +7,6 @@ const CONFIGURATION_TABS = {
};
const advancedModeConfigTabs = [
CONFIGURATION_TABS.ATTACK,
CONFIGURATION_TABS.BASIC,
CONFIGURATION_TABS.BASIC_NETWORK,
CONFIGURATION_TABS.RANSOMWARE,

View File

@ -2,7 +2,6 @@ import React from 'react';
import Form from 'react-jsonschema-form-bs4';
import {Button, Col, Modal, Nav} from 'react-bootstrap';
import AuthComponent from '../AuthComponent';
import ConfigMatrixComponent from '../attack/ConfigMatrixComponent';
import UiSchema from '../configuration-components/UiSchema';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
@ -20,7 +19,6 @@ import applyUiSchemaManipulators from '../configuration-components/UISchemaManip
import HtmlFieldDescription from '../configuration-components/HtmlFieldDescription.js';
import CONFIGURATION_TABS_PER_MODE from '../configuration-components/ConfigurationTabs.js';
const ATTACK_URL = '/api/attack';
const CONFIG_URL = '/api/configuration/island';
export const API_PBA_LINUX = '/api/fileUpload/PBAlinux';
export const API_PBA_WINDOWS = '/api/fileUpload/PBAwindows';
@ -30,11 +28,9 @@ class ConfigurePageComponent extends AuthComponent {
constructor(props) {
super(props);
this.initialConfig = {};
this.initialAttackConfig = {};
this.currentSection = this.getSectionsOrder()[0];
this.state = {
attackConfig: {},
configuration: {},
currentFormData: {},
importCandidateConfig: null,
@ -42,7 +38,7 @@ class ConfigurePageComponent extends AuthComponent {
schema: {},
sections: [],
selectedSection: this.currentSection,
showAttackAlert: false,
showUnsubmittedConfigWarning: false,
showUnsafeOptionsConfirmation: false,
showUnsafeAttackOptionsWarning: false,
showConfigExportModal: false,
@ -64,39 +60,26 @@ class ConfigurePageComponent extends AuthComponent {
setInitialConfig(config) {
// Sets a reference to know if config was changed
config['attack'] = {}
this.initialConfig = JSON.parse(JSON.stringify(config));
}
setInitialAttackConfig(attackConfig) {
// Sets a reference to know if attack config was changed
this.initialAttackConfig = JSON.parse(JSON.stringify(attackConfig));
}
componentDidMount = () => {
let urls = [CONFIG_URL, ATTACK_URL];
let urls = [CONFIG_URL];
// ??? Why fetch config here and not in `render()`?
Promise.all(urls.map(url => this.authFetch(url).then(res => res.json())))
.then(data => {
let sections = [];
let attackConfig = data[1];
let monkeyConfig = data[0];
this.setInitialConfig(monkeyConfig.configuration);
this.setInitialAttackConfig(attackConfig.configuration);
for (let sectionKey of this.getSectionsOrder()) {
if (sectionKey === 'attack') {
sections.push({key: sectionKey, title: 'ATT&CK'})
} else {
sections.push({
key: sectionKey,
title: monkeyConfig.schema.properties[sectionKey].title
});
}
}
this.setState({
schema: monkeyConfig.schema,
configuration: monkeyConfig.configuration,
attackConfig: attackConfig.configuration,
sections: sections,
currentFormData: monkeyConfig.configuration[this.state.selectedSection]
})
@ -130,42 +113,13 @@ class ConfigurePageComponent extends AuthComponent {
};
onSubmit = () => {
if (this.state.selectedSection === 'attack') {
this.matrixSubmit();
} else {
this.attemptConfigSubmit();
}
};
canSafelySubmitConfig(config) {
return !isUnsafeOptionSelected(this.state.schema, config);
}
matrixSubmit = () => {
// Submit attack matrix
this.authFetch(ATTACK_URL,
{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(this.state.attackConfig)
})
.then(res => {
if (!res.ok) {
throw Error()
}
return res;
})
.then(() => {
this.setInitialAttackConfig(this.state.attackConfig);
})
.then(() => this.updateConfig(this.checkAndShowUnsafeAttackWarning))
.then(() => this.setState({lastAction: 'saved'}))
.catch(error => {
console.log('Bad configuration: ' + error.toString());
this.setState({lastAction: 'invalid_configuration'});
});
};
checkAndShowUnsafeAttackWarning = () => {
if (isUnsafeOptionSelected(this.state.schema, this.state.configuration)) {
this.setState({showUnsafeAttackOptionsWarning: true});
@ -201,38 +155,8 @@ class ConfigurePageComponent extends AuthComponent {
});
}
// Alters attack configuration when user toggles technique
attackTechniqueChange = (technique, value, mapped = false) => {
// Change value in attack configuration
// Go trough each column in matrix, searching for technique
Object.entries(this.state.attackConfig).forEach(techType => {
if (Object.prototype.hasOwnProperty.call(techType[1].properties, technique)) {
let tempMatrix = this.state.attackConfig;
tempMatrix[techType[0]].properties[technique].value = value;
this.setState({attackConfig: tempMatrix});
// Toggle all mapped techniques
if (!mapped) {
// Loop trough each column and each row
Object.entries(this.state.attackConfig).forEach(otherType => {
Object.entries(otherType[1].properties).forEach(otherTech => {
// If this technique depends on a technique that was changed
if (Object.prototype.hasOwnProperty.call(otherTech[1], 'depends_on') &&
otherTech[1]['depends_on'].includes(technique)) {
this.attackTechniqueChange(otherTech[0], value, true)
}
})
});
}
}
});
};
onChange = ({formData}) => {
let configuration = this.state.configuration;
if (this.state.selectedSection === 'attack'){
formData = {};
}
configuration[this.state.selectedSection] = formData;
this.setState({currentFormData: formData, configuration: configuration});
};
@ -270,8 +194,8 @@ class ConfigurePageComponent extends AuthComponent {
}
renderAttackAlertModal = () => {
return (<Modal show={this.state.showAttackAlert} onHide={() => {
this.setState({showAttackAlert: false})
return (<Modal show={this.state.showUnsubmittedConfigWarning} onHide={() => {
this.setState({showUnsubmittedConfigWarning: false})
}}>
<Modal.Body>
<h2>
@ -286,7 +210,7 @@ class ConfigurePageComponent extends AuthComponent {
size='lg'
style={{margin: '5px'}}
onClick={() => {
this.setState({showAttackAlert: false})
this.setState({showUnsubmittedConfigWarning: false})
}}>
Cancel
</Button>
@ -330,16 +254,13 @@ class ConfigurePageComponent extends AuthComponent {
return true;
}
userChangedMatrix() {
return (JSON.stringify(this.state.attackConfig) !== JSON.stringify(this.initialAttackConfig))
}
setSelectedSection = (key) => {
if ((key === 'attack' && this.userChangedConfig()) ||
(this.currentSection === 'attack' && this.userChangedMatrix())) {
this.setState({showAttackAlert: true});
return;
}
// TODO: Fix https://github.com/guardicore/monkey/issues/1621
//if ( key === 'basic' & this.userChangedConfig()) {
// this.setState({showUnsubmittedConfigWarning: true});
// return;
//}
this.updateConfigSection();
this.currentSection = key;
@ -358,7 +279,6 @@ class ConfigurePageComponent extends AuthComponent {
})
.then(res => res.json())
.then(res => {
res.configuration['attack'] = {}
this.setState({
lastAction: 'reset',
schema: res.schema,
@ -372,16 +292,6 @@ class ConfigurePageComponent extends AuthComponent {
this.removePBAfile(API_PBA_WINDOWS, this.setPbaFilenameWindows)
this.removePBAfile(API_PBA_LINUX, this.setPbaFilenameLinux)
});
this.authFetch(ATTACK_URL, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify('reset_attack_matrix')
})
.then(res => res.json())
.then(res => {
this.setState({attackConfig: res.configuration});
this.setInitialAttackConfig(res.configuration);
})
};
removePBAfile(apiEndpoint, setFilenameFnc) {
@ -421,13 +331,6 @@ class ConfigurePageComponent extends AuthComponent {
}));
}
renderMatrix = () => {
return (<ConfigMatrixComponent configuration={this.state.attackConfig}
submit={this.componentDidMount}
reset={this.resetConfig}
change={this.attackTechniqueChange}/>)
};
renderConfigContent = (displayedSchema) => {
let formProperties = {};
formProperties['schema'] = displayedSchema
@ -497,15 +400,13 @@ class ConfigurePageComponent extends AuthComponent {
render() {
let displayedSchema = {};
if (Object.prototype.hasOwnProperty.call(this.state.schema, 'properties') && this.state.selectedSection !== 'attack') {
if (Object.prototype.hasOwnProperty.call(this.state.schema, 'properties')) {
displayedSchema = this.state.schema['properties'][this.state.selectedSection];
displayedSchema['definitions'] = this.state.schema['definitions'];
}
let content = '';
if (this.state.selectedSection === 'attack' && Object.entries(this.state.attackConfig).length !== 0) {
content = this.renderMatrix()
} else if (this.state.selectedSection !== 'attack' && Object.entries(this.state.configuration).length !== 0) {
if (Object.entries(this.state.configuration).length !== 0) {
content = this.renderConfigContent(displayedSchema)
}
return (

View File

@ -3,10 +3,7 @@ from enum import Enum
import pytest
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.services.attack.technique_reports.__init__ import (
AttackTechnique,
disabled_msg,
)
from monkey_island.cc.services.attack.technique_reports.__init__ import AttackTechnique
@pytest.fixture(scope="function", autouse=True)
@ -68,13 +65,6 @@ class ExpectedMsgs_OneRelevantSystem(Enum):
USED: str = "USED"
def test_get_message_by_status_disabled_two_relevant_systems():
technique_msg = FakeAttackTechnique_TwoRelevantSystems.get_message_by_status(
ScanStatus.DISABLED.value
)
assert technique_msg == disabled_msg
def test_get_message_by_status_unscanned_two_relevant_systems():
technique_msg = FakeAttackTechnique_TwoRelevantSystems.get_message_by_status(
ScanStatus.UNSCANNED.value
@ -96,13 +86,6 @@ def test_get_message_by_status_used_two_relevant_systems():
assert technique_msg == ExpectedMsgs_TwoRelevantSystems.USED.value
def test_get_message_by_status_disabled_one_relevant_system():
technique_msg = FakeAttackTechnique_OneRelevantSystem.get_message_by_status(
ScanStatus.DISABLED.value
)
assert technique_msg == disabled_msg
def test_get_message_by_status_unscanned_one_relevant_system():
technique_msg = FakeAttackTechnique_OneRelevantSystem.get_message_by_status(
ScanStatus.UNSCANNED.value

View File

@ -22,9 +22,6 @@ def patch_dependencies(monkeypatch_session):
monkeypatch_session.setattr(
"monkey_island.cc.services.config.ConfigService.init_config", lambda: None
)
monkeypatch_session.setattr(
"monkey_island.cc.services.attack.attack_config.AttackConfig.reset_config", lambda: None
)
monkeypatch_session.setattr(
"monkey_island.cc.services.database.jsonify", MagicMock(return_value=True)
)