forked from p15670423/monkey
Merge pull request #1618 from guardicore/1532-remove-mitre-attack-config
1532 remove mitre attack config
This commit is contained in:
commit
6cc983dc21
|
@ -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",
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 |
|
@ -8,8 +8,6 @@ class ScanStatus(Enum):
|
|||
SCANNED = 1
|
||||
# Technique was attempted and succeeded
|
||||
USED = 2
|
||||
# Techique was disabled
|
||||
DISABLED = 3
|
||||
|
||||
|
||||
class UsageEnum(Enum):
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 {}
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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,10 +29,8 @@ 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]
|
||||
|
||||
scanned_services, exploited_services = status_and_data[1], status_and_data[2]
|
||||
data = {"title": T1210.technique_title()}
|
||||
|
||||
data.update(T1210.get_message_and_status(status))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
});
|
||||
}
|
||||
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();
|
||||
}
|
||||
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 (
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue