forked from p15670423/monkey
Merge pull request #285 from VakarisZ/attack_configuration
Attack configuration
This commit is contained in:
commit
8d9432e817
|
@ -205,6 +205,7 @@ class Configuration(object):
|
||||||
# exploiters config
|
# exploiters config
|
||||||
###########################
|
###########################
|
||||||
|
|
||||||
|
should_exploit = True
|
||||||
skip_exploit_if_file_exist = False
|
skip_exploit_if_file_exist = False
|
||||||
|
|
||||||
ms08_067_exploit_attempts = 5
|
ms08_067_exploit_attempts = 5
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"should_exploit": true,
|
||||||
"command_servers": [
|
"command_servers": [
|
||||||
"192.0.2.0:5000"
|
"192.0.2.0:5000"
|
||||||
],
|
],
|
||||||
|
|
|
@ -183,6 +183,7 @@ class InfectionMonkey(object):
|
||||||
LOG.debug("Default server: %s set to machine: %r" % (self._default_server, machine))
|
LOG.debug("Default server: %s set to machine: %r" % (self._default_server, machine))
|
||||||
|
|
||||||
# Order exploits according to their type
|
# Order exploits according to their type
|
||||||
|
if WormConfiguration.should_exploit:
|
||||||
self._exploiters = sorted(self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value)
|
self._exploiters = sorted(self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value)
|
||||||
host_exploited = False
|
host_exploited = False
|
||||||
for exploiter in [exploiter(machine) for exploiter in self._exploiters]:
|
for exploiter in [exploiter(machine) for exploiter in self._exploiters]:
|
||||||
|
|
|
@ -29,11 +29,12 @@ from monkey_island.cc.resources.telemetry import Telemetry
|
||||||
from monkey_island.cc.resources.telemetry_feed import TelemetryFeed
|
from monkey_island.cc.resources.telemetry_feed import TelemetryFeed
|
||||||
from monkey_island.cc.resources.pba_file_download import PBAFileDownload
|
from monkey_island.cc.resources.pba_file_download import PBAFileDownload
|
||||||
from monkey_island.cc.resources.version_update import VersionUpdate
|
from monkey_island.cc.resources.version_update import VersionUpdate
|
||||||
from monkey_island.cc.services.config import ConfigService
|
from monkey_island.cc.services.database import Database
|
||||||
from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH
|
from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH
|
||||||
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
|
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
|
||||||
from monkey_island.cc.resources.pba_file_upload import FileUpload
|
from monkey_island.cc.resources.pba_file_upload import FileUpload
|
||||||
from monkey_island.cc.resources.attack_telem import AttackTelem
|
from monkey_island.cc.resources.attack_telem import AttackTelem
|
||||||
|
from monkey_island.cc.resources.attack_config import AttackConfiguration
|
||||||
|
|
||||||
__author__ = 'Barak'
|
__author__ = 'Barak'
|
||||||
|
|
||||||
|
@ -97,7 +98,7 @@ def init_app_services(app):
|
||||||
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
database.init()
|
database.init()
|
||||||
ConfigService.init_config()
|
Database.init_db()
|
||||||
|
|
||||||
# If running on AWS, this will initialize the instance data, which is used "later" in the execution of the island.
|
# If running on AWS, this will initialize the instance data, which is used "later" in the execution of the island.
|
||||||
RemoteRunAwsService.init()
|
RemoteRunAwsService.init()
|
||||||
|
@ -130,6 +131,7 @@ def init_api_resources(api):
|
||||||
'/api/fileUpload/<string:file_type>?load=<string:filename>',
|
'/api/fileUpload/<string:file_type>?load=<string:filename>',
|
||||||
'/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(AttackTelem, '/api/attack/<string:technique>')
|
api.add_resource(AttackTelem, '/api/attack/<string:technique>')
|
||||||
api.add_resource(VersionUpdate, '/api/version-update', '/api/version-update/')
|
api.add_resource(VersionUpdate, '/api/version-update', '/api/version-update/')
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import flask_restful
|
||||||
|
import json
|
||||||
|
from flask import jsonify, request
|
||||||
|
|
||||||
|
from monkey_island.cc.auth import jwt_required
|
||||||
|
from monkey_island.cc.services.attack.attack_config import AttackConfig
|
||||||
|
|
||||||
|
__author__ = "VakarisZ"
|
||||||
|
|
||||||
|
|
||||||
|
class AttackConfiguration(flask_restful.Resource):
|
||||||
|
@jwt_required()
|
||||||
|
def get(self):
|
||||||
|
return jsonify(configuration=AttackConfig.get_config()['properties'])
|
||||||
|
|
||||||
|
@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()['properties'])
|
||||||
|
else:
|
||||||
|
AttackConfig.update_config({'properties': json.loads(request.data)})
|
||||||
|
AttackConfig.apply_to_monkey_config()
|
||||||
|
return {}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import flask_restful
|
import flask_restful
|
||||||
from flask import request
|
from flask import request
|
||||||
import json
|
import json
|
||||||
from monkey_island.cc.services.attack.attack_telem import set_results
|
from monkey_island.cc.services.attack.attack_telem import AttackTelemService
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
__author__ = 'VakarisZ'
|
__author__ = 'VakarisZ'
|
||||||
|
@ -20,5 +20,5 @@ class AttackTelem(flask_restful.Resource):
|
||||||
:param technique: Technique ID, e.g. T1111
|
:param technique: Technique ID, e.g. T1111
|
||||||
"""
|
"""
|
||||||
data = json.loads(request.data)
|
data = json.loads(request.data)
|
||||||
set_results(technique, data)
|
AttackTelemService.set_results(technique, data)
|
||||||
return {}
|
return {}
|
||||||
|
|
|
@ -6,11 +6,10 @@ from flask import request, make_response, jsonify
|
||||||
|
|
||||||
from monkey_island.cc.auth import jwt_required
|
from monkey_island.cc.auth import jwt_required
|
||||||
from monkey_island.cc.database import mongo
|
from monkey_island.cc.database import mongo
|
||||||
from monkey_island.cc.services.config import ConfigService
|
|
||||||
from monkey_island.cc.services.node import NodeService
|
from monkey_island.cc.services.node import NodeService
|
||||||
from monkey_island.cc.services.report import ReportService
|
from monkey_island.cc.services.report import ReportService
|
||||||
from monkey_island.cc.utils import local_ip_addresses
|
from monkey_island.cc.utils import local_ip_addresses
|
||||||
from monkey_island.cc.services.post_breach_files import remove_PBA_files
|
from monkey_island.cc.services.database import Database
|
||||||
|
|
||||||
__author__ = 'Barak'
|
__author__ = 'Barak'
|
||||||
|
|
||||||
|
@ -26,7 +25,7 @@ class Root(flask_restful.Resource):
|
||||||
if not action:
|
if not action:
|
||||||
return Root.get_server_info()
|
return Root.get_server_info()
|
||||||
elif action == "reset":
|
elif action == "reset":
|
||||||
return Root.reset_db()
|
return jwt_required()(Database.reset_db)()
|
||||||
elif action == "killall":
|
elif action == "killall":
|
||||||
return Root.kill_all()
|
return Root.kill_all()
|
||||||
elif action == "is-up":
|
elif action == "is-up":
|
||||||
|
@ -40,16 +39,6 @@ class Root(flask_restful.Resource):
|
||||||
return jsonify(ip_addresses=local_ip_addresses(), mongo=str(mongo.db),
|
return jsonify(ip_addresses=local_ip_addresses(), mongo=str(mongo.db),
|
||||||
completed_steps=Root.get_completed_steps())
|
completed_steps=Root.get_completed_steps())
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
@jwt_required()
|
|
||||||
def reset_db():
|
|
||||||
remove_PBA_files()
|
|
||||||
# We can't drop system collections.
|
|
||||||
[mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')]
|
|
||||||
ConfigService.init_config()
|
|
||||||
logger.info('DB was reset')
|
|
||||||
return jsonify(status='OK')
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@jwt_required()
|
@jwt_required()
|
||||||
def kill_all():
|
def kill_all():
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
__author__ = "VakarisZ"
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AttackConfig(object):
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_config():
|
||||||
|
config = mongo.db.attack.find_one({'name': 'newconfig'})
|
||||||
|
return config
|
||||||
|
|
||||||
|
@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 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 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 trough 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 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. E.g. ['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 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 attack_config['properties'].items():
|
||||||
|
for key, technique in attack_type['properties'].items():
|
||||||
|
techniques[key] = technique['value']
|
||||||
|
return techniques
|
|
@ -0,0 +1,88 @@
|
||||||
|
SCHEMA = {
|
||||||
|
"title": "ATT&CK configuration",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"initial_access": {
|
||||||
|
"title": "Initial access",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"T1078": {
|
||||||
|
"title": "T1078 Valid accounts",
|
||||||
|
"type": "bool",
|
||||||
|
"value": True,
|
||||||
|
"necessary": False,
|
||||||
|
"description": "Mapped with T1003 Credential dumping because both techniques "
|
||||||
|
"require same credential harvesting modules. "
|
||||||
|
"Adversaries may steal the credentials of a specific user or service account using "
|
||||||
|
"Credential Access techniques or capture credentials earlier in their "
|
||||||
|
"reconnaissance process.",
|
||||||
|
"depends_on": ["T1003"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lateral_movement": {
|
||||||
|
"title": "Lateral movement",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"T1210": {
|
||||||
|
"title": "T1210 Exploitation of Remote services",
|
||||||
|
"type": "bool",
|
||||||
|
"value": True,
|
||||||
|
"necessary": False,
|
||||||
|
"description": "Exploitation of a software vulnerability occurs when an adversary "
|
||||||
|
"takes advantage of a programming error in a program, service, or within the "
|
||||||
|
"operating system software or kernel itself to execute adversary-controlled code."
|
||||||
|
},
|
||||||
|
"T1075": {
|
||||||
|
"title": "T1075 Pass the hash",
|
||||||
|
"type": "bool",
|
||||||
|
"value": True,
|
||||||
|
"necessary": False,
|
||||||
|
"description": "Pass the hash (PtH) is a method of authenticating as a user without "
|
||||||
|
"having access to the user's cleartext password."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"credential_access": {
|
||||||
|
"title": "Credential access",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"T1110": {
|
||||||
|
"title": "T1110 Brute force",
|
||||||
|
"type": "bool",
|
||||||
|
"value": True,
|
||||||
|
"necessary": False,
|
||||||
|
"description": "Adversaries may use brute force techniques to attempt access to accounts "
|
||||||
|
"when passwords are unknown or when password hashes are obtained.",
|
||||||
|
"depends_on": ["T1210"]
|
||||||
|
},
|
||||||
|
"T1003": {
|
||||||
|
"title": "T1003 Credential dumping",
|
||||||
|
"type": "bool",
|
||||||
|
"value": True,
|
||||||
|
"necessary": False,
|
||||||
|
"description": "Mapped with T1078 Valid Accounts because both techniques require"
|
||||||
|
" same credential harvesting modules. "
|
||||||
|
"Credential dumping is the process of obtaining account login and password "
|
||||||
|
"information, normally in the form of a hash or a clear text password, "
|
||||||
|
"from the operating system and software.",
|
||||||
|
"depends_on": ["T1078"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defence_evasion": {
|
||||||
|
"title": "Defence evasion",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"T1197": {
|
||||||
|
"title": "T1197 Bits jobs",
|
||||||
|
"type": "bool",
|
||||||
|
"value": True,
|
||||||
|
"necessary": True,
|
||||||
|
"description": "Adversaries may abuse BITS to download, execute, "
|
||||||
|
"and even clean up after running malicious code."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,11 @@ __author__ = "VakarisZ"
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AttackTelemService(object):
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def set_results(technique, data):
|
def set_results(technique, data):
|
||||||
"""
|
"""
|
||||||
Adds ATT&CK technique results(telemetry) to the database
|
Adds ATT&CK technique results(telemetry) to the database
|
||||||
|
|
|
@ -13,42 +13,48 @@ SCHEMA = {
|
||||||
"enum": [
|
"enum": [
|
||||||
"SmbExploiter"
|
"SmbExploiter"
|
||||||
],
|
],
|
||||||
"title": "SMB Exploiter"
|
"title": "SMB Exploiter",
|
||||||
|
"attack_techniques": ["T1110", "T1075"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"WmiExploiter"
|
"WmiExploiter"
|
||||||
],
|
],
|
||||||
"title": "WMI Exploiter"
|
"title": "WMI Exploiter",
|
||||||
|
"attack_techniques": ["T1110"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"MSSQLExploiter"
|
"MSSQLExploiter"
|
||||||
],
|
],
|
||||||
"title": "MSSQL Exploiter"
|
"title": "MSSQL Exploiter",
|
||||||
|
"attack_techniques": ["T1110"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"RdpExploiter"
|
"RdpExploiter"
|
||||||
],
|
],
|
||||||
"title": "RDP Exploiter (UNSAFE)"
|
"title": "RDP Exploiter (UNSAFE)",
|
||||||
|
"attack_techniques": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"Ms08_067_Exploiter"
|
"Ms08_067_Exploiter"
|
||||||
],
|
],
|
||||||
"title": "MS08-067 Exploiter (UNSAFE)"
|
"title": "MS08-067 Exploiter (UNSAFE)",
|
||||||
|
"attack_techniques": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"SSHExploiter"
|
"SSHExploiter"
|
||||||
],
|
],
|
||||||
"title": "SSH Exploiter"
|
"title": "SSH Exploiter",
|
||||||
|
"attack_techniques": ["T1110"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -111,6 +117,7 @@ SCHEMA = {
|
||||||
"BackdoorUser"
|
"BackdoorUser"
|
||||||
],
|
],
|
||||||
"title": "Back door user",
|
"title": "Back door user",
|
||||||
|
"attack_techniques": []
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -123,14 +130,16 @@ SCHEMA = {
|
||||||
"enum": [
|
"enum": [
|
||||||
"SMBFinger"
|
"SMBFinger"
|
||||||
],
|
],
|
||||||
"title": "SMBFinger"
|
"title": "SMBFinger",
|
||||||
|
"attack_techniques": ["T1210"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"SSHFinger"
|
"SSHFinger"
|
||||||
],
|
],
|
||||||
"title": "SSHFinger"
|
"title": "SSHFinger",
|
||||||
|
"attack_techniques": ["T1210"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -151,14 +160,16 @@ SCHEMA = {
|
||||||
"enum": [
|
"enum": [
|
||||||
"MySQLFinger"
|
"MySQLFinger"
|
||||||
],
|
],
|
||||||
"title": "MySQLFinger"
|
"title": "MySQLFinger",
|
||||||
|
"attack_techniques": ["T1210"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"MSSQLFinger"
|
"MSSQLFinger"
|
||||||
],
|
],
|
||||||
"title": "MSSQLFinger"
|
"title": "MSSQLFinger",
|
||||||
|
"attack_techniques": ["T1210"]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -166,16 +177,30 @@ SCHEMA = {
|
||||||
"enum": [
|
"enum": [
|
||||||
"ElasticFinger"
|
"ElasticFinger"
|
||||||
],
|
],
|
||||||
"title": "ElasticFinger"
|
"title": "ElasticFinger",
|
||||||
|
"attack_techniques": ["T1210"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"properties": {
|
"properties": {
|
||||||
"basic": {
|
"basic": {
|
||||||
"title": "Basic - Credentials",
|
"title": "Basic - Exploits",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"general": {
|
||||||
|
"title": "General",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"should_exploit": {
|
||||||
|
"title": "Exploit network machines",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": True,
|
||||||
|
"attack_techniques": ["T1210"],
|
||||||
|
"description": "Determines if monkey should try to safely exploit machines on the network"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"credentials": {
|
"credentials": {
|
||||||
"title": "Credentials",
|
"title": "Credentials",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -389,6 +414,7 @@ SCHEMA = {
|
||||||
"title": "Harvest Azure Credentials",
|
"title": "Harvest Azure Credentials",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": True,
|
"default": True,
|
||||||
|
"attack_techniques": ["T1003", "T1078"],
|
||||||
"description":
|
"description":
|
||||||
"Determine if the Monkey should try to harvest password credentials from Azure VMs"
|
"Determine if the Monkey should try to harvest password credentials from Azure VMs"
|
||||||
},
|
},
|
||||||
|
@ -402,6 +428,7 @@ SCHEMA = {
|
||||||
"title": "Should use Mimikatz",
|
"title": "Should use Mimikatz",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": True,
|
"default": True,
|
||||||
|
"attack_techniques": ["T1003", "T1078"],
|
||||||
"description": "Determines whether to use Mimikatz"
|
"description": "Determines whether to use Mimikatz"
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from monkey_island.cc.services.config import ConfigService
|
||||||
|
from monkey_island.cc.services.attack.attack_config import AttackConfig
|
||||||
|
from monkey_island.cc.services.post_breach_files import remove_PBA_files
|
||||||
|
from flask import jsonify
|
||||||
|
from monkey_island.cc.database import mongo
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Database(object):
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def reset_db():
|
||||||
|
remove_PBA_files()
|
||||||
|
# We can't drop system collections.
|
||||||
|
[mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')]
|
||||||
|
ConfigService.init_config()
|
||||||
|
AttackConfig.reset_config()
|
||||||
|
logger.info('DB was reset')
|
||||||
|
return jsonify(status='OK')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def init_db():
|
||||||
|
if not mongo.db.collection_names():
|
||||||
|
Database.reset_db()
|
||||||
|
|
|
@ -73,6 +73,7 @@
|
||||||
"json-loader": "^0.5.7",
|
"json-loader": "^0.5.7",
|
||||||
"jwt-decode": "^2.2.0",
|
"jwt-decode": "^2.2.0",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.2",
|
||||||
|
"node-sass": "^4.11.0",
|
||||||
"normalize.css": "^8.0.0",
|
"normalize.css": "^8.0.0",
|
||||||
"npm": "^6.4.1",
|
"npm": "^6.4.1",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
|
@ -92,7 +93,9 @@
|
||||||
"react-router-dom": "^4.3.1",
|
"react-router-dom": "^4.3.1",
|
||||||
"react-table": "^6.8.6",
|
"react-table": "^6.8.6",
|
||||||
"react-toggle": "^4.0.1",
|
"react-toggle": "^4.0.1",
|
||||||
|
"react-tooltip-lite": "^1.9.1",
|
||||||
"redux": "^4.0.0",
|
"redux": "^4.0.0",
|
||||||
|
"sass-loader": "^7.1.0",
|
||||||
"sha3": "^2.0.0",
|
"sha3": "^2.0.0",
|
||||||
"react-spinners": "^0.5.4",
|
"react-spinners": "^0.5.4",
|
||||||
"@emotion/core": "^10.0.10"
|
"@emotion/core": "^10.0.10"
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
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/Tooltip.scss';
|
||||||
|
import {Col} from "react-bootstrap";
|
||||||
|
|
||||||
|
class MatrixComponent 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 = MatrixComponent.findMaxTechniques(Object.values(configCopy));
|
||||||
|
let matrixTableData = MatrixComponent.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}>
|
||||||
|
<i className="fa fa-circle-thin icon-unchecked"></i>
|
||||||
|
<span> - Dissabled</span>
|
||||||
|
</Col>
|
||||||
|
<Col xs={4}>
|
||||||
|
<i className="fa fa-circle icon-checked"></i>
|
||||||
|
<span> - Enabled</span>
|
||||||
|
</Col>
|
||||||
|
<Col xs={4}>
|
||||||
|
<i className="fa fa-circle icon-mandatory"></i>
|
||||||
|
<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 MatrixComponent;
|
|
@ -1,20 +1,47 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Form from 'react-jsonschema-form';
|
import Form from 'react-jsonschema-form';
|
||||||
import {Col, Nav, NavItem} from 'react-bootstrap';
|
import {Col, Modal, Nav, NavItem} from 'react-bootstrap';
|
||||||
import fileDownload from 'js-file-download';
|
import fileDownload from 'js-file-download';
|
||||||
import AuthComponent from '../AuthComponent';
|
import AuthComponent from '../AuthComponent';
|
||||||
import { FilePond } from 'react-filepond';
|
import { FilePond } from 'react-filepond';
|
||||||
import 'filepond/dist/filepond.min.css';
|
import 'filepond/dist/filepond.min.css';
|
||||||
|
import MatrixComponent from "../attack/MatrixComponent";
|
||||||
|
|
||||||
|
const ATTACK_URL = '/api/attack';
|
||||||
|
const CONFIG_URL = '/api/configuration/island';
|
||||||
|
|
||||||
class ConfigurePageComponent extends AuthComponent {
|
class ConfigurePageComponent extends AuthComponent {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.PBAwindowsPond = null;
|
this.PBAwindowsPond = null;
|
||||||
this.PBAlinuxPond = null;
|
this.PBAlinuxPond = null;
|
||||||
this.currentSection = 'basic';
|
this.currentSection = 'attack';
|
||||||
this.currentFormData = {};
|
this.currentFormData = {};
|
||||||
this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal'];
|
this.initialConfig = {};
|
||||||
this.uiSchema = {
|
this.initialAttackConfig = {};
|
||||||
|
this.sectionsOrder = ['attack', 'basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal'];
|
||||||
|
this.uiSchemas = ConfigurePageComponent.getUiSchemas();
|
||||||
|
// set schema from server
|
||||||
|
this.state = {
|
||||||
|
schema: {},
|
||||||
|
configuration: {},
|
||||||
|
attackConfig: {},
|
||||||
|
lastAction: 'none',
|
||||||
|
sections: [],
|
||||||
|
selectedSection: 'attack',
|
||||||
|
allMonkeysAreDead: true,
|
||||||
|
PBAwinFile: [],
|
||||||
|
PBAlinuxFile: [],
|
||||||
|
showAttackAlert: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getUiSchemas(){
|
||||||
|
return ({
|
||||||
|
basic: {"ui:order": ["general", "credentials"]},
|
||||||
|
basic_network: {},
|
||||||
|
monkey: {
|
||||||
behaviour: {
|
behaviour: {
|
||||||
custom_PBA_linux_cmd: {
|
custom_PBA_linux_cmd: {
|
||||||
"ui:widget": "textarea",
|
"ui:widget": "textarea",
|
||||||
|
@ -39,40 +66,90 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
"ui:emptyValue": ""
|
"ui:emptyValue": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
// set schema from server
|
cnc: {},
|
||||||
this.state = {
|
network: {},
|
||||||
schema: {},
|
exploits: {},
|
||||||
configuration: {},
|
internal: {}
|
||||||
lastAction: 'none',
|
})
|
||||||
sections: [],
|
|
||||||
selectedSection: 'basic',
|
|
||||||
allMonkeysAreDead: true,
|
|
||||||
PBAwinFile: [],
|
|
||||||
PBAlinuxFile: []
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
setInitialConfig(config) {
|
||||||
this.authFetch('/api/configuration/island')
|
// Sets a reference to know if config was changed
|
||||||
.then(res => res.json())
|
this.initialConfig = JSON.parse(JSON.stringify(config));
|
||||||
.then(res => {
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
Promise.all(urls.map(url => this.authFetch(url).then(res => res.json())))
|
||||||
|
.then(data => {
|
||||||
let sections = [];
|
let sections = [];
|
||||||
|
let attackConfig = data[1];
|
||||||
|
let monkeyConfig = data[0];
|
||||||
|
this.setInitialConfig(monkeyConfig.configuration);
|
||||||
|
this.setInitialAttackConfig(attackConfig.configuration);
|
||||||
for (let sectionKey of this.sectionsOrder) {
|
for (let sectionKey of this.sectionsOrder) {
|
||||||
sections.push({key: sectionKey, title: res.schema.properties[sectionKey].title});
|
if (sectionKey === 'attack') {sections.push({key:sectionKey, title: "ATT&CK"})}
|
||||||
|
else {sections.push({key: sectionKey, title: monkeyConfig.schema.properties[sectionKey].title});}
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
schema: res.schema,
|
schema: monkeyConfig.schema,
|
||||||
configuration: res.configuration,
|
configuration: monkeyConfig.configuration,
|
||||||
|
attackConfig: attackConfig.configuration,
|
||||||
sections: sections,
|
sections: sections,
|
||||||
selectedSection: 'basic'
|
selectedSection: 'attack'
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
this.updateMonkeysRunning();
|
this.updateMonkeysRunning();
|
||||||
}
|
};
|
||||||
|
|
||||||
onSubmit = ({formData}) => {
|
updateConfig = () => {
|
||||||
this.currentFormData = formData;
|
this.authFetch(CONFIG_URL)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
this.setInitialConfig(data.configuration);
|
||||||
|
this.setState({configuration: data.configuration})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmit = () => {
|
||||||
|
if (this.state.selectedSection === 'attack'){
|
||||||
|
this.matrixSubmit()
|
||||||
|
} else {
|
||||||
|
this.configSubmit()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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())
|
||||||
|
.then(this.setState({lastAction: 'saved'}))
|
||||||
|
.catch(error => {
|
||||||
|
this.setState({lastAction: 'invalid_configuration'});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
configSubmit = () => {
|
||||||
|
// Submit monkey configuration
|
||||||
this.updateConfigSection();
|
this.updateConfigSection();
|
||||||
this.sendConfig()
|
this.sendConfig()
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
|
@ -82,6 +159,7 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
schema: res.schema,
|
schema: res.schema,
|
||||||
configuration: res.configuration
|
configuration: res.configuration
|
||||||
});
|
});
|
||||||
|
this.setInitialConfig(res.configuration);
|
||||||
this.props.onStatusChange();
|
this.props.onStatusChange();
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.log('bad configuration');
|
console.log('bad configuration');
|
||||||
|
@ -89,6 +167,32 @@ 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(techType[1].properties.hasOwnProperty(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 (otherTech[1].hasOwnProperty('depends_on') && otherTech[1]['depends_on'].includes(technique)){
|
||||||
|
this.attackTechniqueChange(otherTech[0], value, true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
onChange = ({formData}) => {
|
onChange = ({formData}) => {
|
||||||
this.currentFormData = formData;
|
this.currentFormData = formData;
|
||||||
};
|
};
|
||||||
|
@ -99,10 +203,48 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
newConfig[this.currentSection] = this.currentFormData;
|
newConfig[this.currentSection] = this.currentFormData;
|
||||||
this.currentFormData = {};
|
this.currentFormData = {};
|
||||||
}
|
}
|
||||||
this.setState({configuration: newConfig});
|
this.setState({configuration: newConfig, lastAction: 'none'});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
renderAttackAlertModal = () => {
|
||||||
|
return (<Modal show={this.state.showAttackAlert} onHide={() => {this.setState({showAttackAlert: false})}}>
|
||||||
|
<Modal.Body>
|
||||||
|
<h2><div className="text-center">Warning</div></h2>
|
||||||
|
<p className = "text-center" style={{'fontSize': '1.2em', 'marginBottom': '2em'}}>
|
||||||
|
You have unsubmitted changes. Submit them before proceeding.
|
||||||
|
</p>
|
||||||
|
<div className="text-center">
|
||||||
|
<button type="button"
|
||||||
|
className="btn btn-success btn-lg"
|
||||||
|
style={{margin: '5px'}}
|
||||||
|
onClick={() => {this.setState({showAttackAlert: false})}} >
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal>)
|
||||||
|
};
|
||||||
|
|
||||||
|
userChangedConfig(){
|
||||||
|
if(JSON.stringify(this.state.configuration) === JSON.stringify(this.initialConfig)){
|
||||||
|
if(Object.keys(this.currentFormData).length === 0 ||
|
||||||
|
JSON.stringify(this.initialConfig[this.currentSection]) === JSON.stringify(this.currentFormData)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
userChangedMatrix(){
|
||||||
|
return (JSON.stringify(this.state.attackConfig) !== JSON.stringify(this.initialAttackConfig))
|
||||||
|
}
|
||||||
|
|
||||||
setSelectedSection = (key) => {
|
setSelectedSection = (key) => {
|
||||||
|
if ((key === 'attack' && this.userChangedConfig()) ||
|
||||||
|
(this.currentSection === 'attack' && this.userChangedMatrix())){
|
||||||
|
this.setState({showAttackAlert: true});
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.updateConfigSection();
|
this.updateConfigSection();
|
||||||
this.currentSection = key;
|
this.currentSection = key;
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -112,7 +254,7 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
|
|
||||||
resetConfig = () => {
|
resetConfig = () => {
|
||||||
this.removePBAfiles();
|
this.removePBAfiles();
|
||||||
this.authFetch('/api/configuration/island',
|
this.authFetch(CONFIG_URL,
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
@ -125,8 +267,17 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
schema: res.schema,
|
schema: res.schema,
|
||||||
configuration: res.configuration
|
configuration: res.configuration
|
||||||
});
|
});
|
||||||
|
this.setInitialConfig(res.configuration);
|
||||||
this.props.onStatusChange();
|
this.props.onStatusChange();
|
||||||
});
|
});
|
||||||
|
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);
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
removePBAfiles(){
|
removePBAfiles(){
|
||||||
|
@ -278,39 +429,29 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
return pbaFile
|
return pbaFile
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
renderMatrix = () => {
|
||||||
let displayedSchema = {};
|
return (<MatrixComponent configuration={this.state.attackConfig}
|
||||||
if (this.state.schema.hasOwnProperty('properties')) {
|
submit={this.componentDidMount}
|
||||||
displayedSchema = this.state.schema['properties'][this.state.selectedSection];
|
reset={this.resetConfig}
|
||||||
displayedSchema['definitions'] = this.state.schema['definitions'];
|
change={this.attackTechniqueChange}/>)
|
||||||
}
|
};
|
||||||
return (
|
|
||||||
<Col xs={12} lg={8}>
|
|
||||||
<h1 className="page-title">Monkey Configuration</h1>
|
renderConfigContent = (displayedSchema) => {
|
||||||
<Nav bsStyle="tabs" justified
|
return (<div>
|
||||||
activeKey={this.state.selectedSection} onSelect={this.setSelectedSection}
|
{this.renderBasicNetworkWarning()}
|
||||||
style={{'marginBottom': '2em'}}>
|
|
||||||
{this.state.sections.map(section =>
|
|
||||||
<NavItem key={section.key} eventKey={section.key}>{section.title}</NavItem>
|
|
||||||
)}
|
|
||||||
</Nav>
|
|
||||||
{
|
|
||||||
this.state.selectedSection === 'basic_network' ?
|
|
||||||
<div className="alert alert-info">
|
|
||||||
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
|
|
||||||
The Monkey scans its subnet if "Local network scan" is ticked. Additionally the monkey scans machines
|
|
||||||
according to its range class.
|
|
||||||
</div>
|
|
||||||
: <div />
|
|
||||||
}
|
|
||||||
{ this.state.selectedSection ?
|
|
||||||
<Form schema={displayedSchema}
|
<Form schema={displayedSchema}
|
||||||
uiSchema={this.uiSchema}
|
uiSchema={this.uiSchemas[this.state.selectedSection]}
|
||||||
formData={this.state.configuration[this.state.selectedSection]}
|
formData={this.state.configuration[this.state.selectedSection]}
|
||||||
onSubmit={this.onSubmit}
|
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
noValidate={true} >
|
noValidate={true} >
|
||||||
<div>
|
<button type="submit" className={"hidden"}>Submit</button>
|
||||||
|
</Form>
|
||||||
|
</div> )
|
||||||
|
};
|
||||||
|
|
||||||
|
renderRunningMonkeysWarning = () => {
|
||||||
|
return (<div>
|
||||||
{ this.state.allMonkeysAreDead ?
|
{ this.state.allMonkeysAreDead ?
|
||||||
'' :
|
'' :
|
||||||
<div className="alert alert-warning">
|
<div className="alert alert-warning">
|
||||||
|
@ -319,17 +460,57 @@ class ConfigurePageComponent extends AuthComponent {
|
||||||
infections.
|
infections.
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
</div>)
|
||||||
|
};
|
||||||
|
|
||||||
|
renderBasicNetworkWarning = () => {
|
||||||
|
if (this.state.selectedSection === 'basic_network'){
|
||||||
|
return (<div className="alert alert-info">
|
||||||
|
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
|
||||||
|
The Monkey scans its subnet if "Local network scan" is ticked. Additionally the monkey scans machines
|
||||||
|
according to its range class.
|
||||||
|
</div>)
|
||||||
|
} else {
|
||||||
|
return (<div />)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
renderNav = () => {
|
||||||
|
return (<Nav bsStyle="tabs" justified
|
||||||
|
activeKey={this.state.selectedSection} onSelect={this.setSelectedSection}
|
||||||
|
style={{'marginBottom': '2em'}}>
|
||||||
|
{this.state.sections.map(section => <NavItem key={section.key} eventKey={section.key}>{section.title}</NavItem>)}
|
||||||
|
</Nav>)
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let displayedSchema = {};
|
||||||
|
if (this.state.schema.hasOwnProperty('properties') && this.state.selectedSection !== 'attack') {
|
||||||
|
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') {
|
||||||
|
content = this.renderConfigContent(displayedSchema)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col xs={12} lg={8}>
|
||||||
|
{this.renderAttackAlertModal()}
|
||||||
|
<h1 className="page-title">Monkey Configuration</h1>
|
||||||
|
{this.renderNav()}
|
||||||
|
{ this.renderRunningMonkeysWarning()}
|
||||||
|
{ content }
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<button type="submit" className="btn btn-success btn-lg" style={{margin: '5px'}}>
|
<button type="submit" onClick={this.onSubmit} className="btn btn-success btn-lg" style={{margin: '5px'}}>
|
||||||
Submit
|
Submit
|
||||||
</button>
|
</button>
|
||||||
<button type="button" onClick={this.resetConfig} className="btn btn-danger btn-lg" style={{margin: '5px'}}>
|
<button type="button" onClick={this.resetConfig} className="btn btn-danger btn-lg" style={{margin: '5px'}}>
|
||||||
Reset to defaults
|
Reset to defaults
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
: ''}
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<button onClick={() => document.getElementById('uploadInputInternal').click()}
|
<button onClick={() => document.getElementById('uploadInputInternal').click()}
|
||||||
className="btn btn-info btn-lg" style={{margin: '5px'}}>
|
className="btn btn-info btn-lg" style={{margin: '5px'}}>
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
import '../../styles/Checkbox.scss'
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
class CheckboxComponent extends React.PureComponent {
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (this.props.checked !== prevProps.checked) {
|
||||||
|
this.setState({checked: this.props.checked});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Parent component can pass a name and a changeHandler (function) for this component in props.
|
||||||
|
changeHandler(name, checked) function will be called with these parameters:
|
||||||
|
this.props.name (the name of this component) and
|
||||||
|
this.state.checked (boolean indicating if this component is checked or not)
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
checked: this.props.checked,
|
||||||
|
necessary: this.props.necessary,
|
||||||
|
isAnimating: false
|
||||||
|
};
|
||||||
|
this.toggleChecked = this.toggleChecked.bind(this);
|
||||||
|
this.stopAnimation = this.stopAnimation.bind(this);
|
||||||
|
this.composeStateClasses = this.composeStateClasses.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Toggles component.
|
||||||
|
toggleChecked() {
|
||||||
|
if (this.state.isAnimating) {return false;}
|
||||||
|
this.setState({
|
||||||
|
checked: !this.state.checked,
|
||||||
|
isAnimating: true,
|
||||||
|
}, () => { this.props.changeHandler ? this.props.changeHandler(this.props.name, this.state.checked) : null});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stops ping animation on checkbox after click
|
||||||
|
stopAnimation() {
|
||||||
|
this.setState({ isAnimating: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates class string for component
|
||||||
|
composeStateClasses(core) {
|
||||||
|
let result = core;
|
||||||
|
if (this.state.necessary){
|
||||||
|
return result + ' blocked'
|
||||||
|
}
|
||||||
|
if (this.state.checked) { result += ' is-checked'; }
|
||||||
|
else { result += ' is-unchecked' }
|
||||||
|
|
||||||
|
if (this.state.isAnimating) { result += ' do-ping'; }
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const cl = this.composeStateClasses('ui-checkbox-btn');
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={ cl }
|
||||||
|
onClick={ this.state.necessary ? void(0) : this.toggleChecked}>
|
||||||
|
<input className="ui ui-checkbox"
|
||||||
|
type="checkbox" value={this.state.checked}
|
||||||
|
name={this.props.name}/>
|
||||||
|
<label className="text">{ this.props.children }</label>
|
||||||
|
<div className="ui-btn-ping" onTransitionEnd={this.stopAnimation}></div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CheckboxComponent;
|
|
@ -186,6 +186,10 @@ body {
|
||||||
.nav-tabs > li > a {
|
.nav-tabs > li > a {
|
||||||
height: 63px
|
height: 63px
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav > li > a:focus {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
* Run Monkey Page
|
* Run Monkey Page
|
||||||
*/
|
*/
|
||||||
|
@ -516,6 +520,16 @@ body {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Attack config page */
|
||||||
|
.attack-matrix .messages {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attack-legend {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.version-text {
|
.version-text {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
// colors
|
||||||
|
$light-grey: #EAF4F4;
|
||||||
|
$medium-grey: #7B9EA8;
|
||||||
|
$dark-green: #007d02;
|
||||||
|
$green: #44CF6C;
|
||||||
|
$black: #000000;
|
||||||
|
|
||||||
|
.ui-checkbox-btn {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
background-color: rgba(red, .6);
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
input { display: none; }
|
||||||
|
|
||||||
|
.icon,
|
||||||
|
.text {
|
||||||
|
display: inline-block;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
padding-top: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// color states
|
||||||
|
&.is-unchecked {
|
||||||
|
background-color: transparent;
|
||||||
|
color: $black;
|
||||||
|
fill: $black;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.blocked {
|
||||||
|
background-color: $dark-green;
|
||||||
|
color: $light-grey;
|
||||||
|
fill: $light-grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-checked {
|
||||||
|
background-color: $green;
|
||||||
|
color: white;
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; right: 0; bottom: 0; left: 0;
|
||||||
|
margin: auto;
|
||||||
|
width: 16px;
|
||||||
|
height: auto;
|
||||||
|
fill: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-checked & {
|
||||||
|
color: white;
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ping animation magic
|
||||||
|
.ui-btn-ping {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 100%;
|
||||||
|
transform: translate3d(-50%, -50%, 0); // center center by default
|
||||||
|
|
||||||
|
// set the square
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
transform: scale(0, 0); // center center by default
|
||||||
|
transition-property: background-color transform;
|
||||||
|
transition-timing-function: cubic-bezier(0.0, 0.0, 0.2, 1);
|
||||||
|
display: block;
|
||||||
|
padding-bottom: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgba(white, .84);;
|
||||||
|
}
|
||||||
|
|
||||||
|
.do-ping &:before {
|
||||||
|
transform: scale(2.5, 2.5);
|
||||||
|
transition-duration: .35s;
|
||||||
|
background-color: rgba(white, .08);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-checked{
|
||||||
|
color:$green
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-mandatory{
|
||||||
|
color:$dark-green
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-unchecked{
|
||||||
|
color:$black;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
$background: #000000;
|
||||||
|
$font: #fff;
|
||||||
|
|
||||||
|
.react-tooltip-lite {
|
||||||
|
background: $background;
|
||||||
|
color: $font;
|
||||||
|
max-width: 400px !important;
|
||||||
|
}
|
|
@ -18,6 +18,14 @@ module.exports = {
|
||||||
'css-loader'
|
'css-loader'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.scss$/,
|
||||||
|
use: [
|
||||||
|
'style-loader',
|
||||||
|
'css-loader',
|
||||||
|
'sass-loader'
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||||
use: {
|
use: {
|
||||||
|
|
Loading…
Reference in New Issue