Island: Remove attack endpoint and config functions
This commit is contained in:
parent
23d05c37ed
commit
ee16fa82a0
|
@ -8,7 +8,6 @@ from werkzeug.exceptions import NotFound
|
||||||
import monkey_island.cc.environment.environment_singleton as env_singleton
|
import monkey_island.cc.environment.environment_singleton as env_singleton
|
||||||
from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH
|
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.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.attack.attack_report import AttackReport
|
||||||
from monkey_island.cc.resources.auth.auth import Authenticate, init_jwt
|
from monkey_island.cc.resources.auth.auth import Authenticate, init_jwt
|
||||||
from monkey_island.cc.resources.auth.registration import Registration
|
from monkey_island.cc.resources.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.
|
# deciding to reset credentials and then still logging in with the old JWT.
|
||||||
app.config["JWT_SECRET_KEY"] = str(uuid.uuid4())
|
app.config["JWT_SECRET_KEY"] = str(uuid.uuid4())
|
||||||
|
|
||||||
# By default, Flask sorts keys of JSON objects alphabetically, which messes with the ATT&CK
|
# By default, Flask sorts keys of JSON objects alphabetically.
|
||||||
# matrix in the
|
# See https://flask.palletsprojects.com/en/1.1.x/config/#JSON_SORT_KEYS.
|
||||||
# configuration. See https://flask.palletsprojects.com/en/1.1.x/config/#JSON_SORT_KEYS.
|
|
||||||
app.config["JSON_SORT_KEYS"] = False
|
app.config["JSON_SORT_KEYS"] = False
|
||||||
|
|
||||||
app.json_encoder = CustomJSONEncoder
|
app.json_encoder = CustomJSONEncoder
|
||||||
|
@ -166,7 +164,6 @@ def init_api_resources(api):
|
||||||
"/api/fileUpload/<string:file_type>?restore=<string:filename>",
|
"/api/fileUpload/<string:file_type>?restore=<string:filename>",
|
||||||
)
|
)
|
||||||
api.add_resource(RemoteRun, "/api/remote-monkey", "/api/remote-monkey/")
|
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(VersionUpdate, "/api/version-update", "/api/version-update/")
|
||||||
api.add_resource(RemotePortCheck, "/api/monkey_control/check_remote_port/<string:port>")
|
api.add_resource(RemotePortCheck, "/api/monkey_control/check_remote_port/<string:port>")
|
||||||
api.add_resource(StartedOnIsland, "/api/monkey_control/started_on_island")
|
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,10 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from dpath import util
|
|
||||||
|
|
||||||
from monkey_island.cc.database import mongo
|
from monkey_island.cc.database import mongo
|
||||||
from monkey_island.cc.services.attack.attack_schema import SCHEMA
|
from monkey_island.cc.services.attack.attack_schema import SCHEMA
|
||||||
from monkey_island.cc.services.config import ConfigService
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -32,10 +29,6 @@ class AttackConfig(object):
|
||||||
return technique
|
return technique
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_config_schema():
|
|
||||||
return SCHEMA
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def reset_config():
|
def reset_config():
|
||||||
AttackConfig.update_config(SCHEMA)
|
AttackConfig.update_config(SCHEMA)
|
||||||
|
@ -45,137 +38,6 @@ class AttackConfig(object):
|
||||||
mongo.db.attack.update({"name": "newconfig"}, {"$set": config_json}, upsert=True)
|
mongo.db.attack.update({"name": "newconfig"}, {"$set": config_json}, upsert=True)
|
||||||
return 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
|
@staticmethod
|
||||||
def get_technique_values():
|
def get_technique_values():
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue