Merge remote-tracking branch 'shreya/modifications-T1156-T1504' into modifications-T1156-T1504

# Conflicts:
#	monkey/monkey_island/cc/services/attack/technique_reports/T1136.py
#	monkey/monkey_island/cc/services/attack/technique_reports/T1154.py
#	monkey/monkey_island/cc/services/attack/technique_reports/T1156.py
#	monkey/monkey_island/cc/services/attack/technique_reports/T1158.py
#	monkey/monkey_island/cc/services/attack/technique_reports/T1166.py
#	monkey/monkey_island/cc/services/attack/technique_reports/T1168.py
#	monkey/monkey_island/cc/services/attack/technique_reports/T1504.py
#	monkey/monkey_island/cc/services/telemetry/processing/post_breach.py
#	monkey/monkey_island/cc/ui/src/components/report-components/security/PostBreach.js
This commit is contained in:
VakarisZ 2020-07-29 12:52:17 +03:00
commit 10b15b3efe
120 changed files with 2336 additions and 1718 deletions

View File

@ -83,7 +83,7 @@ script:
- cd monkey_island/cc/ui
- npm ci # See https://docs.npmjs.com/cli/ci.html
- eslint ./src --quiet # Test for errors
- JS_WARNINGS_AMOUNT_UPPER_LIMIT=70
- JS_WARNINGS_AMOUNT_UPPER_LIMIT=28
- eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT # Test for max warnings
# Build documentation

View File

@ -5,4 +5,4 @@ draft: false
tags: ["exploit", "windows"]
---
Brute forces WMI (Windows Management Instrumentation) using credentials provided by user ((see ["Configuration"](../usage/configuration))) and hashes gathered by mimikatz.
Brute forces WMI (Windows Management Instrumentation) using credentials provided by user (see ["Configuration"](../usage/configuration)) and hashes gathered by mimikatz.

View File

@ -2,3 +2,5 @@ AWS_COLLECTOR = "AwsCollector"
HOSTNAME_COLLECTOR = "HostnameCollector"
ENVIRONMENT_COLLECTOR = "EnvironmentCollector"
PROCESS_LIST_COLLECTOR = "ProcessListCollector"
MIMIKATZ_COLLECTOR = "MimikatzCollector"
AZURE_CRED_COLLECTOR = "AzureCollector"

View File

@ -0,0 +1,3 @@
# Defined in UI on ValidationFormats.js
IP_RANGE = "ip-range"
IP = "ip"

View File

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

View File

@ -129,13 +129,13 @@ class Configuration(object):
finger_classes = []
exploiter_classes = []
system_info_collectors_classes = []
system_info_collector_classes = []
# how many victims to look for in a single scan iteration
victims_max_find = 100
# how many victims to exploit before stopping
victims_max_exploit = 15
victims_max_exploit = 100
# depth of propagation
depth = 2
@ -267,16 +267,6 @@ class Configuration(object):
# Shares to not check if they're writable.
sambacry_shares_not_to_check = ["IPC$", "print$"]
# system info collection
collect_system_info = True
should_use_mimikatz = True
###########################
# systeminfo config
###########################
extract_azure_creds = True
###########################
# post breach actions
###########################

View File

@ -99,7 +99,7 @@
],
"timeout_between_iterations": 10,
"use_file_logging": true,
"victims_max_exploit": 15,
"victims_max_exploit": 100,
"victims_max_find": 100,
"post_breach_actions": []
custom_PBA_linux_cmd = ""

View File

@ -190,13 +190,13 @@ class InfectionMonkey(object):
if self._default_server:
if self._network.on_island(self._default_server):
machine.set_default_server(get_interface_to_target(machine.ip_addr) +
(':' + self._default_server_port if self._default_server_port else ''))
(
':' + self._default_server_port if self._default_server_port else ''))
else:
machine.set_default_server(self._default_server)
LOG.debug("Default server for machine: %r set to %s" % (machine, machine.default_server))
# Order exploits according to their type
if WormConfiguration.should_exploit:
self._exploiters = sorted(self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value)
host_exploited = False
for exploiter in [exploiter(machine) for exploiter in self._exploiters]:
@ -242,7 +242,6 @@ class InfectionMonkey(object):
LOG.debug("Running with depth: %d" % WormConfiguration.depth)
def collect_system_info_if_configured(self):
if WormConfiguration.collect_system_info:
LOG.debug("Calling system info collection")
system_info_collector = SystemInfoCollector()
system_info = system_info_collector.get_info()
@ -387,7 +386,8 @@ class InfectionMonkey(object):
:raises PlannedShutdownException if couldn't find the server.
"""
if not ControlClient.find_server(default_tunnel=self._default_tunnel):
raise PlannedShutdownException("Monkey couldn't find server with {} default tunnel.".format(self._default_tunnel))
raise PlannedShutdownException(
"Monkey couldn't find server with {} default tunnel.".format(self._default_tunnel))
self._default_server = WormConfiguration.current_server
LOG.debug("default server set to: %s" % self._default_server)

View File

@ -4,6 +4,7 @@ from enum import IntEnum
import psutil
from common.data.system_info_collectors_names import AZURE_CRED_COLLECTOR
from infection_monkey.network.info import get_host_subnets
from infection_monkey.system_info.azure_cred_collector import AzureCollector
from infection_monkey.system_info.netstat_collector import NetstatCollector
@ -91,7 +92,7 @@ class InfoCollector(object):
# noinspection PyBroadException
try:
from infection_monkey.config import WormConfiguration
if not WormConfiguration.extract_azure_creds:
if AZURE_CRED_COLLECTOR not in WormConfiguration.system_info_collector_classes:
return
LOG.debug("Harvesting creds if on an Azure machine")
azure_collector = AzureCollector()

View File

@ -19,7 +19,7 @@ class SystemInfoCollector(Plugin, metaclass=ABCMeta):
@staticmethod
def should_run(class_name) -> bool:
return class_name in WormConfiguration.system_info_collectors_classes
return class_name in WormConfiguration.system_info_collector_classes
@staticmethod
def base_package_file():

View File

@ -2,6 +2,7 @@ import logging
import os
import sys
from common.data.system_info_collectors_names import MIMIKATZ_COLLECTOR
from infection_monkey.system_info.windows_cred_collector.mimikatz_cred_collector import \
MimikatzCredentialCollector
@ -44,7 +45,7 @@ class WindowsInfoCollector(InfoCollector):
# TODO: Think about returning self.get_wmi_info()
self.get_installed_packages()
from infection_monkey.config import WormConfiguration
if WormConfiguration.should_use_mimikatz:
if MIMIKATZ_COLLECTOR in WormConfiguration.system_info_collector_classes:
self.get_mimikatz_info()
return self.info

View File

@ -18,11 +18,17 @@ class T1003(AttackTechnique):
@staticmethod
def get_report_data():
data = {'title': T1003.technique_title()}
@T1003.is_status_disabled
def get_technique_status_and_data():
if mongo.db.telemetry.count_documents(T1003.query):
status = ScanStatus.USED.value
else:
status = ScanStatus.UNSCANNED.value
return (status, [])
data = {'title': T1003.technique_title()}
status, _ = get_technique_status_and_data()
data.update(T1003.get_message_and_status(status))
data.update(T1003.get_mitigation_by_status(status))
data['stolen_creds'] = ReportService.get_stolen_creds()

View File

@ -27,8 +27,14 @@ 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
return (status, network_info)
status, network_info = get_technique_status_and_data()
data = T1016.get_base_data_by_status(status)
data.update({'network_info': network_info})
return data

View File

@ -28,11 +28,17 @@ 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:
status = ScanStatus.USED.value
else:
status = ScanStatus.UNSCANNED.value
return (status, scan_info)
status, scan_info = get_technique_status_and_data()
data = T1018.get_base_data_by_status(status)
data.update({'scan_info': scan_info})
return data

View File

@ -33,6 +33,8 @@ 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):
attempts = list(mongo.db.telemetry.aggregate(T1021.query))
@ -46,6 +48,10 @@ class T1021(AttackTechnique):
status = ScanStatus.SCANNED.value
else:
status = ScanStatus.UNSCANNED.value
return (status, attempts)
status, attempts = get_technique_status_and_data()
data = T1021.get_base_data_by_status(status)
data.update({'services': attempts})
return data

View File

@ -6,7 +6,7 @@ __author__ = "VakarisZ"
class T1035(UsageTechnique):
tech_id = "T1035"
unscanned_msg = "Monkey didn't try to interact with Windows services."
unscanned_msg = "Monkey didn't try to interact with Windows services since it didn't run on any Windows machines."
scanned_msg = "Monkey tried to interact with Windows services, but failed."
used_msg = "Monkey successfully interacted with Windows services."

View File

@ -13,6 +13,8 @@ class T1041(AttackTechnique):
@staticmethod
def get_report_data():
@T1041.is_status_disabled
def get_technique_status_and_data():
monkeys = list(Monkey.objects())
info = [{'src': monkey['command_control_channel']['src'],
'dst': monkey['command_control_channel']['dst']}
@ -21,6 +23,10 @@ class T1041(AttackTechnique):
status = ScanStatus.USED.value
else:
status = ScanStatus.UNSCANNED.value
return (status, info)
status, info = get_technique_status_and_data()
data = T1041.get_base_data_by_status(status)
data.update({'command_control_channel': info})
return data

View File

@ -1,34 +1,13 @@
from common.data.post_breach_consts import POST_BREACH_JOB_SCHEDULING
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.services.attack.technique_reports.pba_technique import \
PostBreachTechnique
__author__ = "shreyamalviya"
class T1053(AttackTechnique):
class T1053(PostBreachTechnique):
tech_id = "T1053"
unscanned_msg = "Monkey did not try scheduling a job on Windows."
unscanned_msg = "Monkey didn't try scheduling a job on Windows since it didn't run on any Windows machines."
scanned_msg = "Monkey tried scheduling a job on the Windows system but failed."
used_msg = "Monkey scheduled a job on the Windows system."
query = [{'$match': {'telem_category': 'post_breach',
'data.name': POST_BREACH_JOB_SCHEDULING,
'data.command': {'$regex': 'schtasks'}}},
{'$project': {'_id': 0,
'machine': {'hostname': '$data.hostname',
'ips': ['$data.ip']},
'result': '$data.result'}}]
@staticmethod
def get_report_data():
data = {'title': T1053.technique_title()}
job_scheduling_info = list(mongo.db.telemetry.aggregate(T1053.query))
status = (ScanStatus.USED.value if job_scheduling_info[0]['result'][1]
else ScanStatus.SCANNED.value) if job_scheduling_info else ScanStatus.UNSCANNED.value
data.update(T1053.get_base_data_by_status(status))
data.update({'info': job_scheduling_info})
return data
pba_names = [POST_BREACH_JOB_SCHEDULING]

View File

@ -23,12 +23,18 @@ 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))
data = {'title': T1059.technique_title(), 'cmds': cmd_data}
if cmd_data:
status = ScanStatus.USED.value
else:
status = ScanStatus.UNSCANNED.value
return (status, cmd_data)
status, cmd_data = get_technique_status_and_data()
data = {'title': T1059.technique_title(), 'cmds': cmd_data}
data.update(T1059.get_message_and_status(status))
data.update(T1059.get_mitigation_by_status(status))
return data

View File

@ -14,6 +14,6 @@ class T1065(AttackTechnique):
@staticmethod
def get_report_data():
port = ConfigService.get_config_value(['cnc', 'servers', 'current_server']).split(':')[1]
port = ConfigService.get_config_value(['internal', 'island_server', 'current_server']).split(':')[1]
T1065.used_msg = T1065.message % port
return T1065.get_base_data_by_status(ScanStatus.USED.value)

View File

@ -7,7 +7,7 @@ __author__ = "VakarisZ"
class T1075(AttackTechnique):
tech_id = "T1075"
unscanned_msg = "Monkey didn't try to use pass the hash attack."
unscanned_msg = "Monkey didn't try to use pass the hash attack since it didn't run on any Windows machines."
scanned_msg = "Monkey tried to use hashes while logging in but didn't succeed."
used_msg = "Monkey successfully used hashed credentials."
@ -30,15 +30,21 @@ class T1075(AttackTechnique):
@staticmethod
def get_report_data():
data = {'title': T1075.technique_title()}
@T1075.is_status_disabled
def get_technique_status_and_data():
successful_logins = list(mongo.db.telemetry.aggregate(T1075.query))
data.update({'successful_logins': successful_logins})
if successful_logins:
status = ScanStatus.USED.value
elif mongo.db.telemetry.count_documents(T1075.login_attempt_query):
status = ScanStatus.SCANNED.value
else:
status = ScanStatus.UNSCANNED.value
return (status, successful_logins)
status, successful_logins = get_technique_status_and_data()
data = {'title': T1075.technique_title()}
data.update({'successful_logins': successful_logins})
data.update(T1075.get_message_and_status(status))
data.update(T1075.get_mitigation_by_status(status))
return data

View File

@ -38,13 +38,19 @@ class T1082(AttackTechnique):
@staticmethod
def get_report_data():
data = {'title': T1082.technique_title()}
@T1082.is_status_disabled
def get_technique_status_and_data():
system_info = list(mongo.db.telemetry.aggregate(T1082.query))
data.update({'system_info': system_info})
if system_info:
status = ScanStatus.USED.value
else:
status = ScanStatus.UNSCANNED.value
return (status, system_info)
status, system_info = get_technique_status_and_data()
data = {'title': T1082.technique_title()}
data.update({'system_info': system_info})
data.update(T1082.get_mitigation_by_status(status))
data.update(T1082.get_message_and_status(status))
return data

View File

@ -7,7 +7,7 @@ __author__ = "VakarisZ"
class T1086(AttackTechnique):
tech_id = "T1086"
unscanned_msg = "Monkey didn't run powershell."
unscanned_msg = "Monkey didn't run powershell since it didn't run on any Windows machines."
scanned_msg = ""
used_msg = "Monkey successfully ran powershell commands on exploited machines in the network."
@ -25,12 +25,17 @@ class T1086(AttackTechnique):
@staticmethod
def get_report_data():
@T1086.is_status_disabled
def get_technique_status_and_data():
cmd_data = list(mongo.db.telemetry.aggregate(T1086.query))
data = {'title': T1086.technique_title(), 'cmds': cmd_data}
if cmd_data:
status = ScanStatus.USED.value
else:
status = ScanStatus.UNSCANNED.value
return (status, cmd_data)
status, cmd_data = get_technique_status_and_data()
data = {'title': T1086.technique_title(), 'cmds': cmd_data}
data.update(T1086.get_mitigation_by_status(status))
data.update(T1086.get_message_and_status(status))

View File

@ -13,9 +13,15 @@ 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]
status = ScanStatus.USED.value if monkeys else ScanStatus.UNSCANNED.value
return (status, monkeys)
status, monkeys = get_technique_status_and_data()
data = T1090.get_base_data_by_status(status)
data.update({'proxies': monkeys})
return data

View File

@ -26,6 +26,8 @@ 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
@ -41,6 +43,10 @@ class T1110(AttackTechnique):
status = ScanStatus.SCANNED.value
else:
status = ScanStatus.UNSCANNED.value
return (status, attempts)
status, attempts = get_technique_status_and_data()
data = T1110.get_base_data_by_status(status)
# Remove data with no successful brute force attempts
attempts = [attempt for attempt in attempts if attempt['attempts']]

View File

@ -6,9 +6,9 @@ __author__ = "VakarisZ"
class T1129(UsageTechnique):
tech_id = "T1129"
unscanned_msg = "Monkey didn't try to load any DLL's."
scanned_msg = "Monkey tried to load DLL's, but failed."
used_msg = "Monkey successfully loaded DLL's using Windows module loader."
unscanned_msg = "Monkey didn't try to load any DLLs since it didn't run on any Windows machines."
scanned_msg = "Monkey tried to load DLLs, but failed."
used_msg = "Monkey successfully loaded DLLs using Windows module loader."
@staticmethod
def get_report_data():

View File

@ -1,39 +1,14 @@
from common.data.post_breach_consts import (
POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER)
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.services.attack.technique_reports.pba_technique import \
PostBreachTechnique
__author__ = "shreyamalviya"
class T1136(AttackTechnique):
class T1136(PostBreachTechnique):
tech_id = "T1136"
unscanned_msg = "Monkey didn't try creating a new user on the network's systems."
scanned_msg = "Monkey tried creating a new user on the network's systems, but failed."
used_msg = "Monkey created a new user on the network's systems."
query = [{'$match': {'telem_category': 'post_breach',
'$or': [{'data.name': POST_BREACH_BACKDOOR_USER},
{'data.name': POST_BREACH_COMMUNICATE_AS_NEW_USER}]}},
{'$project': {'_id': 0,
'machine': {'hostname': '$data.hostname',
'ips': '$data.ip'},
'result': '$data.result'}}]
@staticmethod
def get_report_data():
data = {'title': T1136.technique_title()}
create_user_info = list(mongo.db.telemetry.aggregate(T1136.query))
status = ScanStatus.UNSCANNED.value
if create_user_info:
successful_PBAs = mongo.db.telemetry.count({'$or': [{'data.name': POST_BREACH_BACKDOOR_USER},
{'data.name': POST_BREACH_COMMUNICATE_AS_NEW_USER}],
'data.result.1': True})
status = ScanStatus.USED.value if successful_PBAs else ScanStatus.SCANNED.value
data.update(T1136.get_base_data_by_status(status))
data.update({'info': create_user_info})
return data
pba_names = [POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER]

View File

@ -20,12 +20,17 @@ 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:
status = ScanStatus.USED.value
else:
status = ScanStatus.UNSCANNED.value
return (status, ssh_info)
status, ssh_info = get_technique_status_and_data()
data = T1145.get_base_data_by_status(status)
data.update({'ssh_info': ssh_info})
return data

View File

@ -1,36 +1,13 @@
from common.data.post_breach_consts import POST_BREACH_TRAP_COMMAND
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.services.attack.technique_reports.pba_technique import \
PostBreachTechnique
__author__ = "shreyamalviya"
class T1154(AttackTechnique):
class T1154(PostBreachTechnique):
tech_id = "T1154"
unscanned_msg = "Monkey did not use the trap command."
unscanned_msg = "Monkey didn't use the trap command since it didn't run on any Linux machines."
scanned_msg = "Monkey tried using the trap command but failed."
used_msg = "Monkey used the trap command successfully."
query = [{'$match': {'telem_category': 'post_breach',
'data.name': POST_BREACH_TRAP_COMMAND}},
{'$project': {'_id': 0,
'machine': {'hostname': '$data.hostname',
'ips': '$data.ip'},
'result': '$data.result'}}]
@staticmethod
def get_report_data():
data = {'title': T1154.technique_title(), 'info': []}
trap_command_info = list(mongo.db.telemetry.aggregate(T1154.query))
status = ScanStatus.UNSCANNED.value
if trap_command_info:
successful_PBAs = mongo.db.telemetry.count({'data.name': POST_BREACH_TRAP_COMMAND,
'data.result.1': True})
status = ScanStatus.USED.value if successful_PBAs else ScanStatus.SCANNED.value
data.update(T1154.get_base_data_by_status(status))
data.update({'info': trap_command_info})
return data
pba_names = [POST_BREACH_TRAP_COMMAND]

View File

@ -1,19 +1,21 @@
from common.data.post_breach_consts import \
POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.services.attack.technique_reports.pba_technique import \
PostBreachTechnique
__author__ = "shreyamalviya"
class T1156(AttackTechnique):
class T1156(PostBreachTechnique):
tech_id = "T1156"
unscanned_msg = "Monkey did not try modifying bash startup files on the system."
scanned_msg = "Monkey tried modifying bash startup files on the system but failed."
used_msg = "Monkey modified bash startup files on the system."
unscanned_msg = "Monkey didn't try modifying bash startup files since it didn't run on any Linux machines."
scanned_msg = "Monkey tried modifying bash startup files but failed."
used_msg = "Monkey successfully modified bash startup files."
pba_names = [POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION]
query = [{'$match': {'telem_category': 'post_breach',
@staticmethod
def get_pba_query(*args):
return [{'$match': {'telem_category': 'post_breach',
'data.name': POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION}},
{'$project': {'_id': 0,
'machine': {'hostname': {'$arrayElemAt': ['$data.hostname', 0]},
@ -22,19 +24,3 @@ class T1156(AttackTechnique):
{'$unwind': '$result'},
{'$match': {'$or': [{'result': {'$regex': r'\.bash'}},
{'result': {'$regex': r'\.profile'}}]}}]
@staticmethod
def get_report_data():
data = {'title': T1156.technique_title(), 'info': []}
bash_startup_modification_info = list(mongo.db.telemetry.aggregate(T1156.query))
status = ScanStatus.UNSCANNED.value
if bash_startup_modification_info:
successful_PBAs = mongo.db.telemetry.count({'data.name': POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION,
'data.result.1': True})
status = ScanStatus.USED.value if successful_PBAs else ScanStatus.SCANNED.value
data.update(T1156.get_base_data_by_status(status))
data.update({'info': bash_startup_modification_info})
return data

View File

@ -1,36 +1,13 @@
from common.data.post_breach_consts import POST_BREACH_HIDDEN_FILES
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.services.attack.technique_reports.pba_technique import \
PostBreachTechnique
__author__ = "shreyamalviya"
class T1158(AttackTechnique):
class T1158(PostBreachTechnique):
tech_id = "T1158"
unscanned_msg = "Monkey did not try creating hidden files or folders."
unscanned_msg = "Monkey didn't try creating hidden files or folders."
scanned_msg = "Monkey tried creating hidden files and folders on the system but failed."
used_msg = "Monkey created hidden files and folders on the system."
query = [{'$match': {'telem_category': 'post_breach',
'data.name': POST_BREACH_HIDDEN_FILES}},
{'$project': {'_id': 0,
'machine': {'hostname': '$data.hostname',
'ips': '$data.ip'},
'result': '$data.result'}}]
@staticmethod
def get_report_data():
data = {'title': T1158.technique_title(), 'info': []}
hidden_file_info = list(mongo.db.telemetry.aggregate(T1158.query))
status = ScanStatus.UNSCANNED.value
if hidden_file_info:
successful_PBAs = mongo.db.telemetry.count({'data.name': POST_BREACH_HIDDEN_FILES,
'data.result.1': True})
status = ScanStatus.USED.value if successful_PBAs else ScanStatus.SCANNED.value
data.update(T1158.get_base_data_by_status(status))
data.update({'info': hidden_file_info})
return data
pba_names = [POST_BREACH_HIDDEN_FILES]

View File

@ -1,36 +1,13 @@
from common.data.post_breach_consts import POST_BREACH_SETUID_SETGID
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.services.attack.technique_reports.pba_technique import \
PostBreachTechnique
__author__ = "shreyamalviya"
class T1166(AttackTechnique):
class T1166(PostBreachTechnique):
tech_id = "T1166"
unscanned_msg = "Monkey did not try creating hidden files or folders."
scanned_msg = "Monkey tried creating hidden files and folders on the system but failed."
used_msg = "Monkey created hidden files and folders on the system."
query = [{'$match': {'telem_category': 'post_breach',
'data.name': POST_BREACH_SETUID_SETGID}},
{'$project': {'_id': 0,
'machine': {'hostname': '$data.hostname',
'ips': '$data.ip'},
'result': '$data.result'}}]
@staticmethod
def get_report_data():
data = {'title': T1166.technique_title(), 'info': []}
setuid_setgid_info = list(mongo.db.telemetry.aggregate(T1166.query))
status = ScanStatus.UNSCANNED.value
if setuid_setgid_info:
successful_PBAs = mongo.db.telemetry.count({'data.name': POST_BREACH_SETUID_SETGID,
'data.result.1': True})
status = ScanStatus.USED.value if successful_PBAs else ScanStatus.SCANNED.value
data.update(T1166.get_base_data_by_status(status))
data.update({'info': setuid_setgid_info})
return data
unscanned_msg = "Monkey didn't try setting the setuid or setgid bits since it didn't run on any Linux machines."
scanned_msg = "Monkey tried setting the setuid or setgid bits but failed."
used_msg = "Monkey successfully set the setuid or setgid bits."
pba_names = [POST_BREACH_SETUID_SETGID]

View File

@ -1,37 +1,13 @@
from common.data.post_breach_consts import POST_BREACH_JOB_SCHEDULING
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.services.attack.technique_reports.pba_technique import \
PostBreachTechnique
__author__ = "shreyamalviya"
class T1168(AttackTechnique):
class T1168(PostBreachTechnique):
tech_id = "T1168"
unscanned_msg = "Monkey did not try scheduling a job on Linux."
unscanned_msg = "Monkey didn't try scheduling a job on Linux since it didn't run on any Linux machines."
scanned_msg = "Monkey tried scheduling a job on the Linux system but failed."
used_msg = "Monkey scheduled a job on the Linux system."
query = [{'$match': {'telem_category': 'post_breach',
'data.name': POST_BREACH_JOB_SCHEDULING,
'data.command': {'$regex': 'crontab'}}},
{'$project': {'_id': 0,
'machine': {'hostname': '$data.hostname',
'ips': '$data.ip'},
'result': '$data.result'}}]
@staticmethod
def get_report_data():
data = {'title': T1168.technique_title()}
job_scheduling_info = list(mongo.db.telemetry.aggregate(T1168.query))
status = ScanStatus.UNSCANNED.value
if job_scheduling_info:
successful_PBAs = mongo.db.telemetry.count({'data.name': POST_BREACH_JOB_SCHEDULING,
'data.result.1': True})
status = ScanStatus.USED.value if successful_PBAs else ScanStatus.SCANNED.value
data.update(T1168.get_base_data_by_status(status))
data.update({'info': job_scheduling_info})
return data
pba_names = [POST_BREACH_JOB_SCHEDULING]

View File

@ -13,6 +13,8 @@ class T1188(AttackTechnique):
@staticmethod
def get_report_data():
@T1188.is_status_disabled
def get_technique_status_and_data():
monkeys = Monkey.get_tunneled_monkeys()
hops = []
for monkey in monkeys:
@ -26,6 +28,10 @@ class T1188(AttackTechnique):
'to': proxy.get_network_info(),
'count': proxy_count})
status = ScanStatus.USED.value if hops else ScanStatus.UNSCANNED.value
return (status, hops)
status, hops = get_technique_status_and_data()
data = T1188.get_base_data_by_status(status)
data.update({'hops': hops})
return data

View File

@ -6,7 +6,7 @@ __author__ = "VakarisZ"
class T1197(AttackTechnique):
tech_id = "T1197"
unscanned_msg = "Monkey didn't try to use any bits jobs."
unscanned_msg = "Monkey didn't try to use any bits jobs since it didn't run on any Windows machines."
scanned_msg = "Monkey tried to use bits jobs but failed."
used_msg = "Monkey successfully used bits jobs at least once in the network."

View File

@ -13,7 +13,8 @@ class T1210(AttackTechnique):
@staticmethod
def get_report_data():
data = {'title': T1210.technique_title()}
@T1210.is_status_disabled
def get_technique_status_and_data():
scanned_services = T1210.get_scanned_services()
exploited_services = T1210.get_exploited_services()
if exploited_services:
@ -22,6 +23,16 @@ class T1210(AttackTechnique):
status = ScanStatus.SCANNED.value
else:
status = ScanStatus.UNSCANNED.value
return (status, scanned_services, exploited_services)
status_and_data = get_technique_status_and_data()
status = status_and_data[0]
if status == ScanStatus.DISABLED.value:
scanned_services, exploited_services = [], []
else:
scanned_services, exploited_services = status_and_data[1], status_and_data[2]
data = {'title': T1210.technique_title()}
data.update(T1210.get_message_and_status(status))
data.update(T1210.get_mitigation_by_status(status))
data.update({'scanned_services': scanned_services, 'exploited_services': exploited_services})

View File

@ -1,19 +1,21 @@
from common.data.post_breach_consts import \
POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
from monkey_island.cc.services.attack.technique_reports.pba_technique import \
PostBreachTechnique
__author__ = "shreyamalviya"
class T1504(AttackTechnique):
class T1504(PostBreachTechnique):
tech_id = "T1504"
unscanned_msg = "Monkey did not try modifying powershell startup files on the system."
scanned_msg = "Monkey tried modifying powershell startup files on the system but failed."
used_msg = "Monkey modified powershell startup files on the system."
unscanned_msg = "Monkey didn't try modifying powershell startup files since it didn't run on any Windows machines."
scanned_msg = "Monkey tried modifying powershell startup files but failed."
used_msg = "Monkey successfully modified powershell startup files."
pba_names = [POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION]
query = [{'$match': {'telem_category': 'post_breach',
@staticmethod
def get_pba_query(*args):
return [{'$match': {'telem_category': 'post_breach',
'data.name': POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION}},
{'$project': {'_id': 0,
'machine': {'hostname': {'$arrayElemAt': ['$data.hostname', 0]},
@ -21,19 +23,3 @@ class T1504(AttackTechnique):
'result': '$data.result'}},
{'$unwind': '$result'},
{'$match': {'result': {'$regex': r'profile\.ps1'}}}]
@staticmethod
def get_report_data():
data = {'title': T1504.technique_title(), 'info': []}
powershell_startup_modification_info = list(mongo.db.telemetry.aggregate(T1504.query))
status = ScanStatus.UNSCANNED.value
if powershell_startup_modification_info:
successful_PBAs = mongo.db.telemetry.count({'data.name': POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION,
'data.result.1': True})
status = ScanStatus.USED.value if successful_PBAs else ScanStatus.SCANNED.value
data.update(T1504.get_base_data_by_status(status))
data.update({'info': powershell_startup_modification_info})
return data

View File

@ -10,6 +10,10 @@ from monkey_island.cc.services.attack.attack_config import AttackConfig
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 """
@ -59,7 +63,9 @@ class AttackTechnique(object, metaclass=abc.ABCMeta):
Gets the status of a certain attack technique.
:return: ScanStatus numeric value
"""
if mongo.db.telemetry.find_one({'telem_category': 'attack',
if not cls._is_enabled_in_config():
return ScanStatus.DISABLED.value
elif mongo.db.telemetry.find_one({'telem_category': 'attack',
'data.status': ScanStatus.USED.value,
'data.technique': cls.tech_id}):
return ScanStatus.USED.value
@ -86,6 +92,8 @@ 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:
return cls.unscanned_msg
elif status == ScanStatus.SCANNED.value:
@ -129,3 +137,13 @@ class AttackTechnique(object, metaclass=abc.ABCMeta):
return {'mitigations': mitigation_document.to_mongo().to_dict()['mitigations']}
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]

View File

@ -0,0 +1,57 @@
import abc
from typing import List
from common.utils.attack_utils import ScanStatus
from monkey_island.cc.database import mongo
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
class PostBreachTechnique(AttackTechnique, metaclass=abc.ABCMeta):
""" Class for ATT&CK report components of post-breach actions """
@property
@abc.abstractmethod
def pba_names(self) -> List[str]:
"""
:return: names of post breach action
"""
pass
@classmethod
def get_pba_query(cls, post_breach_action_names):
"""
:param post_breach_action_names: Names of post-breach actions with which the technique is associated
(example - `["Communicate as new user", "Backdoor user"]` for T1136)
:return: Mongo query that parses attack telemetries for a simple report component
(gets machines and post-breach action usage).
"""
return [{'$match': {'telem_category': 'post_breach',
'$or': [{'data.name': pba_name} for pba_name in post_breach_action_names]}},
{'$project': {'_id': 0,
'machine': {'hostname': '$data.hostname',
'ips': ['$data.ip']},
'result': '$data.result'}}]
@classmethod
def get_report_data(cls):
"""
: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)))
status = ScanStatus.UNSCANNED.value
if info:
successful_PBAs = mongo.db.telemetry.count({
'$or': [{'data.name': pba_name} for pba_name in cls.pba_names],
'data.result.1': True
})
status = ScanStatus.USED.value if successful_PBAs else ScanStatus.SCANNED.value
return (status, info)
data = {'title': cls.technique_title()}
status, info = get_technique_status_and_data()
data.update(cls.get_base_data_by_status(status))
data.update({'info': info})
return data

View File

@ -10,8 +10,7 @@ import monkey_island.cc.services.post_breach_files
from monkey_island.cc.database import mongo
from monkey_island.cc.encryptor import encryptor
from monkey_island.cc.network_utils import local_ip_addresses
from .config_schema import SCHEMA
from monkey_island.cc.services.config_schema.config_schema import SCHEMA
__author__ = "itay.mizeretz"
@ -218,8 +217,8 @@ class ConfigService:
@staticmethod
def set_server_ips_in_config(config):
ips = local_ip_addresses()
config["cnc"]["servers"]["command_servers"] = ["%s:%d" % (ip, env_singleton.env.get_island_port()) for ip in ips]
config["cnc"]["servers"]["current_server"] = "%s:%d" % (ips[0], env_singleton.env.get_island_port())
config["internal"]["island_server"]["command_servers"] = ["%s:%d" % (ip, env_singleton.env.get_island_port()) for ip in ips]
config["internal"]["island_server"]["current_server"] = "%s:%d" % (ips[0], env_singleton.env.get_island_port())
@staticmethod
def save_initial_config_if_needed():

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,75 @@
BASIC = {
"title": "Exploits",
"type": "object",
"primary": True,
"properties": {
"exploiters": {
"title": "Exploiters",
"type": "object",
"description": "Choose which exploiters the Monkey will attempt.",
"properties": {
"exploiter_classes": {
"title": "Exploiters",
"type": "array",
"uniqueItems": True,
"items": {
"$ref": "#/definitions/exploiter_classes"
},
"default": [
"SmbExploiter",
"WmiExploiter",
"SSHExploiter",
"ShellShockExploiter",
"SambaCryExploiter",
"ElasticGroovyExploiter",
"Struts2Exploiter",
"WebLogicExploiter",
"HadoopExploiter",
"VSFTPDExploiter",
"MSSQLExploiter"
]
}
}
},
"credentials": {
"title": "Credentials",
"type": "object",
"properties": {
"exploit_user_list": {
"title": "Exploit user list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"Administrator",
"root",
"user"
],
"description": "List of user names that will be used by exploiters that need credentials, like "
"SSH brute-forcing."
},
"exploit_password_list": {
"title": "Exploit password list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"root",
"123456",
"password",
"123456789",
"qwerty",
"111111",
"iloveyou"
],
"description": "List of passwords that will be used by exploiters that need credentials, like "
"SSH brute-forcing."
}
}
}
}
}

View File

@ -0,0 +1,95 @@
from common.data.validation_formats import IP, IP_RANGE
from monkey_island.cc.services.utils.typographic_symbols import WARNING_SIGN
BASIC_NETWORK = {
"title": "Network",
"type": "object",
"properties": {
"scope": {
"title": "Scope",
"type": "object",
"properties": {
"blocked_ips": {
"title": "Blocked IPs",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string",
"format": IP,
},
"default": [
],
"description": "List of IPs that the Monkey will not scan.",
"info": "The Monkey scans its subnet if \"Local network scan\" is ticked. "
"Additionally the monkey scans machines according to \"Scan target list\"."
},
"local_network_scan": {
"title": "Local network scan",
"type": "boolean",
"default": True,
"description": "Determines whether the Monkey will scan the local subnets of machines it runs on, "
"in addition to the IPs that are configured manually in the \"Scan target list\"."
},
"depth": {
"title": "Scan depth",
"type": "integer",
"minimum": 1,
"default": 2,
"description":
"Amount of hops allowed for the Monkey to spread from the Island server. \n"
+ WARNING_SIGN
+ " Note that setting this value too high may result in the Monkey propagating too far, "
"if the \"Local network scan\" is enabled."
},
"subnet_scan_list": {
"title": "Scan target list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string",
"format": IP_RANGE
},
"default": [
],
"description":
"List of targets the Monkey will try to scan. Targets can be IPs, subnets or hosts."
" Examples:\n"
"\tTarget a specific IP: \"192.168.0.1\"\n"
"\tTarget a subnet using a network range: \"192.168.0.5-192.168.0.20\"\n"
"\tTarget a subnet using an IP mask: \"192.168.0.5/24\"\n"
"\tTarget a specific host: \"printer.example\""
}
}
},
"network_analysis": {
"title": "Network Analysis",
"type": "object",
"properties": {
"inaccessible_subnets": {
"title": "Network segmentation testing",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string",
"format": IP_RANGE
},
"default": [
],
"description":
"Test for network segmentation by providing a list of network segments "
"that should NOT be accessible to each other.\n\n"
"For example, if you configured the following three segments: "
"\"10.0.0.0/24\", \"11.0.0.2/32\", and \"12.2.3.0/24\", "
"a Monkey running on 10.0.0.5 will try to access machines in the following subnets: "
"11.0.0.2/32, 12.2.3.0/24. An alert on successful cross-segment connections "
"will be shown in the reports. \n\n"
"Network segments can be IPs, subnets or hosts. Examples:\n"
"\tDefine a single-IP segment: \"192.168.0.1\"\n"
"\tDefine a segment using a network range: \"192.168.0.5-192.168.0.20\"\n"
"\tDefine a segment using an subnet IP mask: \"192.168.0.5/24\"\n"
"\tDefine a single-host segment: \"printer.example\""
}
}
}
}
}

View File

@ -0,0 +1,33 @@
from monkey_island.cc.services.config_schema.basic import BASIC
from monkey_island.cc.services.config_schema.basic_network import BASIC_NETWORK
from monkey_island.cc.services.config_schema.definitions.exploiter_classes import \
EXPLOITER_CLASSES
from monkey_island.cc.services.config_schema.definitions.finger_classes import \
FINGER_CLASSES
from monkey_island.cc.services.config_schema.definitions.post_breach_actions import \
POST_BREACH_ACTIONS
from monkey_island.cc.services.config_schema.definitions.system_info_collector_classes import \
SYSTEM_INFO_COLLECTOR_CLASSES
from monkey_island.cc.services.config_schema.internal import INTERNAL
from monkey_island.cc.services.config_schema.monkey import MONKEY
SCHEMA = {
"title": "Monkey",
"type": "object",
"definitions": {
"exploiter_classes": EXPLOITER_CLASSES,
"system_info_collector_classes": SYSTEM_INFO_COLLECTOR_CLASSES,
"post_breach_actions": POST_BREACH_ACTIONS,
"finger_classes": FINGER_CLASSES
},
"properties": {
"basic": BASIC,
"basic_network": BASIC_NETWORK,
"monkey": MONKEY,
"internal": INTERNAL,
},
"options": {
"collapsed": True
}
}

View File

@ -0,0 +1,130 @@
from monkey_island.cc.services.utils.typographic_symbols import WARNING_SIGN
EXPLOITER_CLASSES = {
"title": "Exploit class",
"description": "Click on exploiter to get more information about it." + WARNING_SIGN +
" Note that using unsafe exploits may cause crashes of the exploited machine/service.",
"type": "string",
"anyOf": [
{
"type": "string",
"enum": [
"SmbExploiter"
],
"title": "SMB Exploiter",
"attack_techniques": ["T1110", "T1075", "T1035"],
"info": "Brute forces using credentials provided by user and"
" hashes gathered by mimikatz.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/smbexec/"
},
{
"type": "string",
"enum": [
"WmiExploiter"
],
"title": "WMI Exploiter",
"attack_techniques": ["T1110", "T1106"],
"info": "Brute forces WMI (Windows Management Instrumentation) "
"using credentials provided by user and hashes gathered by mimikatz.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/wmiexec/"
},
{
"type": "string",
"enum": [
"MSSQLExploiter"
],
"title": "MSSQL Exploiter",
"attack_techniques": ["T1110"],
"info": "Tries to brute force into MsSQL server and uses insecure "
"configuration to execute commands on server.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/mssql/"
},
{
"type": "string",
"enum": [
"Ms08_067_Exploiter"
],
"title": "MS08-067 Exploiter (UNSAFE)",
"info": "Unsafe exploiter, that might cause system crash due to the use of buffer overflow. "
"Uses MS08-067 vulnerability.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/ms08-067/"
},
{
"type": "string",
"enum": [
"SSHExploiter"
],
"title": "SSH Exploiter",
"attack_techniques": ["T1110", "T1145", "T1106"],
"info": "Brute forces using credentials provided by user and SSH keys gathered from systems.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/sshexec/"
},
{
"type": "string",
"enum": [
"ShellShockExploiter"
],
"title": "ShellShock Exploiter",
"info": "CVE-2014-6271, based on logic from "
"https://github.com/nccgroup/shocker/blob/master/shocker.py .",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/shellshock/"
},
{
"type": "string",
"enum": [
"SambaCryExploiter"
],
"title": "SambaCry Exploiter",
"info": "Bruteforces and searches for anonymous shares. Uses Impacket.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/sambacry/"
},
{
"type": "string",
"enum": [
"ElasticGroovyExploiter"
],
"title": "ElasticGroovy Exploiter",
"info": "CVE-2015-1427. Logic is based on Metasploit module.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/elasticgroovy/"
},
{
"type": "string",
"enum": [
"Struts2Exploiter"
],
"title": "Struts2 Exploiter",
"info": "Exploits struts2 java web framework. CVE-2017-5638. Logic based on "
"https://www.exploit-db.com/exploits/41570 .",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/struts2/"
},
{
"type": "string",
"enum": [
"WebLogicExploiter"
],
"title": "WebLogic Exploiter",
"info": "Exploits CVE-2017-10271 and CVE-2019-2725 vulnerabilities on WebLogic server.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/weblogic/"
},
{
"type": "string",
"enum": [
"HadoopExploiter"
],
"title": "Hadoop/Yarn Exploiter",
"info": "Remote code execution on HADOOP server with YARN and default settings. "
"Logic based on https://github.com/vulhub/vulhub/tree/master/hadoop/unauthorized-yarn.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/hadoop/"
},
{
"type": "string",
"enum": [
"VSFTPDExploiter"
],
"title": "VSFTPD Exploiter",
"info": "Exploits a malicious backdoor that was added to the VSFTPD download archive. "
"Logic based on Metasploit module.",
"link": "https://www.guardicore.com/infectionmonkey/docs/reference/exploiters/vsftpd/"
}
]
}

View File

@ -0,0 +1,70 @@
FINGER_CLASSES = {
"title": "Fingerprint class",
"description": "Fingerprint modules collect info about external services "
"Infection Monkey scans.",
"type": "string",
"anyOf": [
{
"type": "string",
"enum": [
"SMBFinger"
],
"title": "SMBFinger",
"info": "Figures out if SMB is running and what's the version of it.",
"attack_techniques": ["T1210"]
},
{
"type": "string",
"enum": [
"SSHFinger"
],
"title": "SSHFinger",
"info": "Figures out if SSH is running.",
"attack_techniques": ["T1210"]
},
{
"type": "string",
"enum": [
"PingScanner"
],
"title": "PingScanner",
"info": "Tries to identify if host is alive and which OS it's running by ping scan."
},
{
"type": "string",
"enum": [
"HTTPFinger"
],
"title": "HTTPFinger",
"info": "Checks if host has HTTP/HTTPS ports open."
},
{
"type": "string",
"enum": [
"MySQLFinger"
],
"title": "MySQLFinger",
"info": "Checks if MySQL server is running and tries to get it's version.",
"attack_techniques": ["T1210"]
},
{
"type": "string",
"enum": [
"MSSQLFinger"
],
"title": "MSSQLFinger",
"info": "Checks if Microsoft SQL service is running and tries to gather information about it.",
"attack_techniques": ["T1210"]
},
{
"type": "string",
"enum": [
"ElasticFinger"
],
"title": "ElasticFinger",
"info": "Checks if ElasticSearch is running and attempts to find it's version.",
"attack_techniques": ["T1210"]
}
]
}

View File

@ -0,0 +1,75 @@
POST_BREACH_ACTIONS = {
"title": "Post breach actions",
"description": "Runs scripts/commands on infected machines. These actions safely simulate what an adversary"
"might do after breaching a new machine. Used in ATT&CK and Zero trust reports.",
"type": "string",
"anyOf": [
{
"type": "string",
"enum": [
"BackdoorUser"
],
"title": "Back door user",
"info": "Attempts to create a new user on the system and delete it afterwards.",
"attack_techniques": ["T1136"]
},
{
"type": "string",
"enum": [
"CommunicateAsNewUser"
],
"title": "Communicate as new user",
"info": "Attempts to create a new user, create HTTPS requests as that user and delete the user "
"afterwards.",
"attack_techniques": ["T1136"]
},
{
"type": "string",
"enum": [
"ModifyShellStartupFiles"
],
"title": "Modify shell startup files",
"info": "Attempts to modify shell startup files, like ~/.profile, ~/.bashrc, ~/.bash_profile "
"in linux, and profile.ps1 in windows. Reverts modifications done afterwards.",
"attack_techniques": ["T1156", "T1504"]
},
{
"type": "string",
"enum": [
"HiddenFiles"
],
"title": "Hidden files and directories",
"info": "Attempts to create a hidden file and remove it afterward.",
"attack_techniques": ["T1158"]
},
{
"type": "string",
"enum": [
"TrapCommand"
],
"title": "Trap",
"info": "On Linux systems, attempts to trap an interrupt signal in order to execute a command "
"upon receiving that signal. Removes the trap afterwards.",
"attack_techniques": ["T1154"]
},
{
"type": "string",
"enum": [
"ChangeSetuidSetgid"
],
"title": "Setuid and Setgid",
"info": "On Linux systems, attempts to set the setuid and setgid bits of a new file. "
"Removes the file afterwards.",
"attack_techniques": ["T1166"]
},
{
"type": "string",
"enum": [
"ScheduleJobs"
],
"title": "Job scheduling",
"info": "Attempts to create a scheduled job on the system and remove it.",
"attack_techniques": ["T1168", "T1053"]
}
]
}

View File

@ -0,0 +1,68 @@
from common.data.system_info_collectors_names import (AWS_COLLECTOR,
AZURE_CRED_COLLECTOR,
ENVIRONMENT_COLLECTOR,
HOSTNAME_COLLECTOR,
MIMIKATZ_COLLECTOR,
PROCESS_LIST_COLLECTOR)
SYSTEM_INFO_COLLECTOR_CLASSES = {
"title": "System Information Collectors",
"description": "Click on a system info collector to find out what it collects.",
"type": "string",
"anyOf": [
{
"type": "string",
"enum": [
ENVIRONMENT_COLLECTOR
],
"title": "Environment collector",
"info": "Collects information about machine's environment (on premise/GCP/AWS).",
"attack_techniques": ["T1082"]
},
{
"type": "string",
"enum": [
MIMIKATZ_COLLECTOR
],
"title": "Mimikatz collector",
"info": "Collects credentials from Windows credential manager.",
"attack_techniques": ["T1003", "T1005"]
},
{
"type": "string",
"enum": [
AWS_COLLECTOR
],
"title": "AWS collector",
"info": "If on AWS, collects more information about the AWS instance currently running on.",
"attack_techniques": ["T1082"]
},
{
"type": "string",
"enum": [
HOSTNAME_COLLECTOR
],
"title": "Hostname collector",
"info": "Collects machine's hostname.",
"attack_techniques": ["T1082", "T1016"]
},
{
"type": "string",
"enum": [
PROCESS_LIST_COLLECTOR
],
"title": "Process list collector",
"info": "Collects a list of running processes on the machine.",
"attack_techniques": ["T1082"]
},
{
"type": "string",
"enum": [
AZURE_CRED_COLLECTOR
],
"title": "Azure credential collector",
"info": "Collects password credentials from Azure VMs",
"attack_techniques": ["T1003", "T1005"]
}
]
}

View File

@ -0,0 +1,490 @@
from monkey_island.cc.services.utils.typographic_symbols import WARNING_SIGN
INTERNAL = {
"title": "Internal",
"type": "object",
"properties": {
"general": {
"title": "General",
"type": "object",
"properties": {
"singleton_mutex_name": {
"title": "Singleton mutex name",
"type": "string",
"default": "{2384ec59-0df8-4ab9-918c-843740924a28}",
"description":
"The name of the mutex used to determine whether the monkey is already running"
},
"keep_tunnel_open_time": {
"title": "Keep tunnel open time",
"type": "integer",
"default": 60,
"description": "Time to keep tunnel open before going down after last exploit (in seconds)"
},
"monkey_dir_name": {
"title": "Monkey's directory name",
"type": "string",
"default": r"monkey_dir",
"description": "Directory name for the directory which will contain all of the monkey files"
},
"started_on_island": {
"title": "Started on island",
"type": "boolean",
"default": False,
"description": "Was exploitation started from island"
"(did monkey with max depth ran on island)"
},
}
},
"monkey": {
"title": "Monkey",
"type": "object",
"properties": {
"victims_max_find": {
"title": "Max victims to find",
"type": "integer",
"default": 100,
"description": "Determines the maximum number of machines the monkey is allowed to scan"
},
"victims_max_exploit": {
"title": "Max victims to exploit",
"type": "integer",
"default": 100,
"description":
"Determines the maximum number of machines the monkey"
" is allowed to successfully exploit. " + WARNING_SIGN
+ " Note that setting this value too high may result in the monkey propagating to "
"a high number of machines"
},
"internet_services": {
"title": "Internet services",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"monkey.guardicore.com",
"www.google.com"
],
"description":
"List of internet services to try and communicate with to determine internet"
" connectivity (use either ip or domain)"
},
"self_delete_in_cleanup": {
"title": "Self delete on cleanup",
"type": "boolean",
"default": True,
"description": "Should the monkey delete its executable when going down"
},
"use_file_logging": {
"title": "Use file logging",
"type": "boolean",
"default": True,
"description": "Should the monkey dump to a log file"
},
"serialize_config": {
"title": "Serialize config",
"type": "boolean",
"default": False,
"description": "Should the monkey dump its config on startup"
},
"alive": {
"title": "Alive",
"type": "boolean",
"default": True,
"description": "Is the monkey alive"
}
}
},
"island_server": {
"title": "Island server",
"type": "object",
"properties": {
"command_servers": {
"title": "Island server's IP's",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"192.0.2.0:5000"
],
"description": "List of command servers/network interfaces to try to communicate with "
"(format is <ip>:<port>)"
},
"current_server": {
"title": "Current server",
"type": "string",
"default": "192.0.2.0:5000",
"description": "The current command server the monkey is communicating with"
}
}
},
"network": {
"title": "Network",
"type": "object",
"properties": {
"tcp_scanner": {
"title": "TCP scanner",
"type": "object",
"properties": {
"HTTP_PORTS": {
"title": "HTTP ports",
"type": "array",
"uniqueItems": True,
"items": {
"type": "integer"
},
"default": [
80,
8080,
443,
8008,
7001
],
"description": "List of ports the monkey will check if are being used for HTTP"
},
"tcp_target_ports": {
"title": "TCP target ports",
"type": "array",
"uniqueItems": True,
"items": {
"type": "integer"
},
"default": [
22,
2222,
445,
135,
3389,
80,
8080,
443,
8008,
3306,
9200,
7001,
8088
],
"description": "List of TCP ports the monkey will check whether they're open"
},
"tcp_scan_interval": {
"title": "TCP scan interval",
"type": "integer",
"default": 0,
"description": "Time to sleep (in milliseconds) between scans"
},
"tcp_scan_timeout": {
"title": "TCP scan timeout",
"type": "integer",
"default": 3000,
"description": "Maximum time (in milliseconds) to wait for TCP response"
},
"tcp_scan_get_banner": {
"title": "TCP scan - get banner",
"type": "boolean",
"default": True,
"description": "Determines whether the TCP scan should try to get the banner"
}
}
},
"ping_scanner": {
"title": "Ping scanner",
"type": "object",
"properties": {
"ping_scan_timeout": {
"title": "Ping scan timeout",
"type": "integer",
"default": 1000,
"description": "Maximum time (in milliseconds) to wait for ping response"
}
}
}
}
},
"classes": {
"title": "Classes",
"type": "object",
"properties": {
"finger_classes": {
"title": "Fingerprint classes",
"type": "array",
"uniqueItems": True,
"items": {
"$ref": "#/definitions/finger_classes"
},
"default": [
"SMBFinger",
"SSHFinger",
"PingScanner",
"HTTPFinger",
"MySQLFinger",
"MSSQLFinger",
"ElasticFinger"
]
}
}
},
"kill_file": {
"title": "Kill file",
"type": "object",
"properties": {
"kill_file_path_windows": {
"title": "Kill file path on Windows",
"type": "string",
"default": "%windir%\\monkey.not",
"description": "Path of file which kills monkey if it exists (on Windows)"
},
"kill_file_path_linux": {
"title": "Kill file path on Linux",
"type": "string",
"default": "/var/run/monkey.not",
"description": "Path of file which kills monkey if it exists (on Linux)"
}
}
},
"dropper": {
"title": "Dropper",
"type": "object",
"properties": {
"dropper_set_date": {
"title": "Dropper sets date",
"type": "boolean",
"default": True,
"description":
"Determines whether the dropper should set the monkey's file date to be the same as"
" another file"
},
"dropper_date_reference_path_windows": {
"title": "Dropper date reference path (Windows)",
"type": "string",
"default": "%windir%\\system32\\kernel32.dll",
"description":
"Determines which file the dropper should copy the date from if it's configured to do"
" so on Windows (use fullpath)"
},
"dropper_date_reference_path_linux": {
"title": "Dropper date reference path (Linux)",
"type": "string",
"default": "/bin/sh",
"description":
"Determines which file the dropper should copy the date from if it's configured to do"
" so on Linux (use fullpath)"
},
"dropper_target_path_linux": {
"title": "Dropper target path on Linux",
"type": "string",
"default": "/tmp/monkey",
"description": "Determines where should the dropper place the monkey on a Linux machine"
},
"dropper_target_path_win_32": {
"title": "Dropper target path on Windows (32bit)",
"type": "string",
"default": "C:\\Windows\\temp\\monkey32.exe",
"description": "Determines where should the dropper place the monkey on a Windows machine "
"(32bit)"
},
"dropper_target_path_win_64": {
"title": "Dropper target path on Windows (64bit)",
"type": "string",
"default": "C:\\Windows\\temp\\monkey64.exe",
"description": "Determines where should the dropper place the monkey on a Windows machine "
"(64 bit)"
},
"dropper_try_move_first": {
"title": "Try to move first",
"type": "boolean",
"default": True,
"description":
"Determines whether the dropper should try to move itself instead of copying itself"
" to target path"
}
}
},
"logging": {
"title": "Logging",
"type": "object",
"properties": {
"dropper_log_path_linux": {
"title": "Dropper log file path on Linux",
"type": "string",
"default": "/tmp/user-1562",
"description": "The fullpath of the dropper log file on Linux"
},
"dropper_log_path_windows": {
"title": "Dropper log file path on Windows",
"type": "string",
"default": "%temp%\\~df1562.tmp",
"description": "The fullpath of the dropper log file on Windows"
},
"monkey_log_path_linux": {
"title": "Monkey log file path on Linux",
"type": "string",
"default": "/tmp/user-1563",
"description": "The fullpath of the monkey log file on Linux"
},
"monkey_log_path_windows": {
"title": "Monkey log file path on Windows",
"type": "string",
"default": "%temp%\\~df1563.tmp",
"description": "The fullpath of the monkey log file on Windows"
},
"send_log_to_server": {
"title": "Send log to server",
"type": "boolean",
"default": True,
"description": "Determines whether the monkey sends its log to the Monkey Island server"
}
}
},
"exploits": {
"title": "Exploits",
"type": "object",
"properties": {
"exploit_lm_hash_list": {
"title": "Exploit LM hash list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [],
"description": "List of LM hashes to use on exploits using credentials"
},
"exploit_ntlm_hash_list": {
"title": "Exploit NTLM hash list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [],
"description": "List of NTLM hashes to use on exploits using credentials"
},
"exploit_ssh_keys": {
"title": "SSH key pairs list",
"type": "array",
"uniqueItems": True,
"default": [],
"items": {
"type": "string"
},
"description": "List of SSH key pairs to use, when trying to ssh into servers"
}, "general": {
"title": "General",
"type": "object",
"properties": {
"skip_exploit_if_file_exist": {
"title": "Skip exploit if file exists",
"type": "boolean",
"default": False,
"description": "Determines whether the monkey should skip the exploit if the monkey's file"
" is already on the remote machine"
}
}
},
"ms08_067": {
"title": "MS08_067",
"type": "object",
"properties": {
"ms08_067_exploit_attempts": {
"title": "MS08_067 exploit attempts",
"type": "integer",
"default": 5,
"description": "Number of attempts to exploit using MS08_067"
},
"user_to_add": {
"title": "Remote user",
"type": "string",
"default": "Monkey_IUSER_SUPPORT",
"description": "Username to add on successful exploit"
},
"remote_user_pass": {
"title": "Remote user password",
"type": "string",
"default": "Password1!",
"description": "Password to use for created user"
}
}
},
"sambacry": {
"title": "SambaCry",
"type": "object",
"properties": {
"sambacry_trigger_timeout": {
"title": "SambaCry trigger timeout",
"type": "integer",
"default": 5,
"description": "Timeout (in seconds) of SambaCry trigger"
},
"sambacry_folder_paths_to_guess": {
"title": "SambaCry folder paths to guess",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
'/',
'/mnt',
'/tmp',
'/storage',
'/export',
'/share',
'/shares',
'/home'
],
"description": "List of full paths to share folder for SambaCry to guess"
},
"sambacry_shares_not_to_check": {
"title": "SambaCry shares not to check",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"IPC$", "print$"
],
"description": "These shares won't be checked when exploiting with SambaCry"
}
}
}
},
"smb_service": {
"title": "SMB service",
"type": "object",
"properties": {
"smb_download_timeout": {
"title": "SMB download timeout",
"type": "integer",
"default": 300,
"description":
"Timeout (in seconds) for SMB download operation (used in various exploits using SMB)"
},
"smb_service_name": {
"title": "SMB service name",
"type": "string",
"default": "InfectionMonkey",
"description": "Name of the SMB service that will be set up to download monkey"
}
}
}
},
"testing": {
"title": "Testing",
"type": "object",
"properties": {
"export_monkey_telems": {
"title": "Export monkey telemetries",
"type": "boolean",
"default": False,
"description": "Exports unencrypted telemetries that can be used for tests in development."
" Do not turn on!"
}
}
}
}
}

View File

@ -0,0 +1,128 @@
from common.data.system_info_collectors_names import (AWS_COLLECTOR,
AZURE_CRED_COLLECTOR,
ENVIRONMENT_COLLECTOR,
HOSTNAME_COLLECTOR,
MIMIKATZ_COLLECTOR,
PROCESS_LIST_COLLECTOR)
MONKEY = {
"title": "Monkey",
"type": "object",
"properties": {
"post_breach": {
"title": "Post breach",
"type": "object",
"properties": {
"custom_PBA_linux_cmd": {
"title": "Linux post breach command",
"type": "string",
"default": "",
"description": "Linux command to be executed after breaching."
},
"PBA_linux_file": {
"title": "Linux post breach file",
"type": "string",
"format": "data-url",
"description": "File to be executed after breaching. "
"If you want custom execution behavior, "
"specify it in 'Linux post breach command' field. "
"Reference your file by filename."
},
"custom_PBA_windows_cmd": {
"title": "Windows post breach command",
"type": "string",
"default": "",
"description": "Windows command to be executed after breaching."
},
"PBA_windows_file": {
"title": "Windows post breach file",
"type": "string",
"format": "data-url",
"description": "File to be executed after breaching. "
"If you want custom execution behavior, "
"specify it in 'Windows post breach command' field. "
"Reference your file by filename."
},
"PBA_windows_filename": {
"title": "Windows PBA filename",
"type": "string",
"default": ""
},
"PBA_linux_filename": {
"title": "Linux PBA filename",
"type": "string",
"default": ""
},
"post_breach_actions": {
"title": "Post breach actions",
"type": "array",
"uniqueItems": True,
"items": {
"$ref": "#/definitions/post_breach_actions"
},
"default": [
"BackdoorUser",
"CommunicateAsNewUser",
"ModifyShellStartupFiles",
"HiddenFiles",
"TrapCommand",
"ChangeSetuidSetgid",
"ScheduleJobs"
]
},
}
},
"system_info": {
"title": "System info",
"type": "object",
"properties": {
"system_info_collector_classes": {
"title": "System info collectors",
"type": "array",
"uniqueItems": True,
"items": {
"$ref": "#/definitions/system_info_collector_classes"
},
"default": [
ENVIRONMENT_COLLECTOR,
AWS_COLLECTOR,
HOSTNAME_COLLECTOR,
PROCESS_LIST_COLLECTOR,
MIMIKATZ_COLLECTOR,
AZURE_CRED_COLLECTOR
]
},
}
},
"persistent_scanning": {
"title": "Persistent scanning",
"type": "object",
"properties": {
"max_iterations": {
"title": "Max iterations",
"type": "integer",
"default": 1,
"minimum": 1,
"description": "Determines how many iterations of the monkey's full lifecycle should occur "
"(how many times to do the scan)"
},
"timeout_between_iterations": {
"title": "Wait time between iterations",
"type": "integer",
"default": 100,
"minimum": 0,
"description":
"Determines for how long (in seconds) should the monkey wait before starting another scan"
},
"retry_failed_explotation": {
"title": "Retry failed exploitation",
"type": "boolean",
"default": True,
"description":
"Determines whether the monkey should retry exploiting machines"
" it didn't successfully exploit on previous scans"
}
}
}
}
}

View File

@ -9,8 +9,8 @@ __author__ = "VakarisZ"
logger = logging.getLogger(__name__)
# Where to find file names in config
PBA_WINDOWS_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_windows_filename']
PBA_LINUX_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_linux_filename']
PBA_WINDOWS_FILENAME_PATH = ['monkey', 'post_breach', 'PBA_windows_filename']
PBA_LINUX_FILENAME_PATH = ['monkey', 'post_breach', 'PBA_linux_filename']
UPLOADS_DIR = Path('monkey_island', 'cc', 'userUploads')
@ -41,5 +41,5 @@ def set_config_PBA_files(config_json):
if monkey_island.cc.services.config.ConfigService.get_config():
linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH)
windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH)
config_json['monkey']['behaviour']['PBA_linux_filename'] = linux_filename
config_json['monkey']['behaviour']['PBA_windows_filename'] = windows_filename
config_json['monkey']['post_breach']['PBA_linux_filename'] = linux_filename
config_json['monkey']['post_breach']['PBA_windows_filename'] = windows_filename

View File

@ -618,7 +618,7 @@ class ReportService:
@staticmethod
def get_config_exploits():
exploits_config_value = ['exploits', 'general', 'exploiter_classes']
exploits_config_value = ['basic', 'exploiters', 'exploiter_classes']
default_exploits = ConfigService.get_default_config(False)
for namespace in exploits_config_value:
default_exploits = default_exploits[namespace]
@ -632,11 +632,11 @@ class ReportService:
@staticmethod
def get_config_ips():
return ConfigService.get_config_value(['basic_network', 'general', 'subnet_scan_list'], True, True)
return ConfigService.get_config_value(['basic_network', 'scope', 'subnet_scan_list'], True, True)
@staticmethod
def get_config_scan():
return ConfigService.get_config_value(['basic_network', 'general', 'local_network_scan'], True, True)
return ConfigService.get_config_value(['basic_network', 'scope', 'local_network_scan'], True, True)
@staticmethod
def get_issues_overview(issues, config_users, config_passwords):

View File

@ -1,8 +1,6 @@
import copy
from common.data.post_breach_consts import (
POST_BREACH_COMMUNICATE_AS_NEW_USER,
POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION)
from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER
from monkey_island.cc.database import mongo
from monkey_island.cc.models import Monkey
from monkey_island.cc.services.telemetry.zero_trust_tests.communicate_as_new_user import \

View File

@ -0,0 +1 @@
WARNING_SIGN = " \u26A0"

View File

@ -41,7 +41,7 @@
"global-strict": 0,
"no-underscore-dangle": 0,
"no-console": 0,
"no-unused-vars": 1,
"no-unused-vars": [1, {"vars": "all", "args": "all", "argsIgnorePattern": "^_", "varsIgnorePattern": "^React$" }],
"no-trailing-spaces": [
1,
{

View File

@ -8220,9 +8220,9 @@
}
},
"marked": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz",
"integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw=="
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/marked/-/marked-1.1.1.tgz",
"integrity": "sha512-mJzT8D2yPxoPh7h0UXkB+dBj4FykPJ2OIfxAWeIHrvoHDkFxukV/29QxoFQoPM6RLEwhIFdJpmKBlqVM3s2ZIw=="
},
"mathml-tag-names": {
"version": "2.1.3",
@ -13649,28 +13649,6 @@
"react-base16-styling": "^0.5.1"
}
},
"react-jsonschema-form": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/react-jsonschema-form/-/react-jsonschema-form-1.8.1.tgz",
"integrity": "sha512-aaDloxNAcGXOOOcdKOxxqEEn5oDlPUZgWcs8unXXB9vjBRgCF8rCm/wVSv1u2G5ih0j/BX6Ewd/WjI2g00lPdg==",
"requires": {
"@babel/runtime-corejs2": "^7.4.5",
"ajv": "^6.7.0",
"core-js": "^2.5.7",
"lodash": "^4.17.15",
"prop-types": "^15.5.8",
"react-is": "^16.8.4",
"react-lifecycles-compat": "^3.0.4",
"shortid": "^2.2.14"
},
"dependencies": {
"core-js": {
"version": "2.6.11",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz",
"integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg=="
}
}
},
"react-jsonschema-form-bs4": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/react-jsonschema-form-bs4/-/react-jsonschema-form-bs4-1.7.1.tgz",

View File

@ -74,7 +74,8 @@
"file-saver": "^2.0.2",
"filepond": "^4.18.0",
"jwt-decode": "^2.2.0",
"marked": "^0.8.2",
"lodash": "^4.17.15",
"marked": "^1.1.1",
"normalize.css": "^8.0.0",
"npm": "^6.14.6",
"pluralize": "^7.0.0",
@ -94,7 +95,6 @@
"react-graph-vis": "^1.0.5",
"react-hot-loader": "^4.12.20",
"react-json-tree": "^0.11.2",
"react-jsonschema-form": "^1.8.0",
"react-jsonschema-form-bs4": "^1.7.1",
"react-particles-js": "^3.2.1",
"react-redux": "^5.1.2",

View File

@ -97,7 +97,7 @@ class AppComponent extends AuthComponent {
};
redirectTo = (userPath, targetPath) => {
let pathQuery = new RegExp(userPath + '[\/]?$', 'g');
let pathQuery = new RegExp(userPath + '[/]?$', 'g');
if (window.location.pathname.match(pathQuery)) {
return <Redirect to={{pathname: targetPath}}/>
}

View File

@ -49,7 +49,7 @@ class SideNavComponent extends React.Component {
</li>
<li>
<NavLink to='/report/security'
isActive={(match, location) => {
isActive={(_match, location) => {
return (location.pathname === '/report/attack'
|| location.pathname === '/report/zeroTrust'
|| location.pathname === '/report/security')

View File

@ -4,7 +4,7 @@ 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 '../../styles/components/Tooltip.scss';
import {Col} from 'react-bootstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

View File

@ -65,5 +65,6 @@ export function renderUsageFields(usages) {
export const ScanStatus = {
UNSCANNED: 0,
SCANNED: 1,
USED: 2
USED: 2,
DISABLED: 3
};

View File

@ -1,7 +1,7 @@
import React from 'react';
import ReactTable from 'react-table';
import {renderMachineFromSystemData, ScanStatus} from './Helpers';
import MitigationsComponent from "./MitigationsComponent";
import MitigationsComponent from './MitigationsComponent';
class T1136 extends React.Component {

View File

@ -0,0 +1,20 @@
import ObjectField from 'react-jsonschema-form-bs4/lib/components/fields/ArrayField';
import * as React from 'react';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle';
class FieldWithInfo extends React.Component {
render() {
return (
<>
<div className='alert alert-info'>
<FontAwesomeIcon icon={faInfoCircle} style={{'marginRight': '5px'}}/>
{this.props.schema.info}
</div>
<ObjectField {...this.props} />
</>);
}
}
export default FieldWithInfo;

View File

@ -0,0 +1,79 @@
import Form from 'react-jsonschema-form-bs4';
import React, {useState} from 'react';
import {Nav} from 'react-bootstrap';
const sectionOrder = [
'network',
'monkey',
'island_server',
'logging',
'exploits',
'dropper',
'classes',
'general',
'kill_file',
'testing'
];
const initialSection = sectionOrder[0];
export default function InternalConfig(props) {
const {
schema,
uiSchema,
onChange,
customFormats,
className,
formData
} = props;
const [selectedSection, setSelectedSection] = useState(initialSection);
const [displayedSchema, setDisplayedSchema] = useState(getSchemaByKey(schema, initialSection));
const [displayedSchemaUi, setDisplayedSchemaUi] = useState(uiSchema[initialSection]);
const onInnerDataChange = (innerData) => {
formData[selectedSection] = innerData.formData;
onChange({formData: formData});
}
const setSection = (sectionKey) => {
setSelectedSection(sectionKey);
setDisplayedSchema(getSchemaByKey(schema, sectionKey));
setDisplayedSchemaUi(uiSchema[sectionKey]);
}
const renderNav = () => {
return (<Nav variant='tabs'
fill
activeKey={selectedSection} onSelect={setSection}
style={{'marginBottom': '2em'}}
className={'config-nav'}>
{sectionOrder.map(section => {
return (
<Nav.Item key={section}>
<Nav.Link eventKey={section}>{getNavTitle(schema, section)}</Nav.Link>
</Nav.Item>);
})}
</Nav>)
}
return (<div>
{renderNav()}
<Form schema={displayedSchema}
uiSchema={displayedSchemaUi}
formData={formData[selectedSection]}
onChange={onInnerDataChange}
customFormats={customFormats}
className={className}
liveValidate>
<button type='submit' className={'hidden'}>Submit</button>
</Form>
</div>)
}
function getSchemaByKey(schema, key) {
let definitions = schema['definitions'];
return {definitions: definitions, properties: schema['properties'][key]['properties']};
}
function getNavTitle(schema, key) {
return schema.properties[key].title;
}

View File

@ -0,0 +1,79 @@
import React from 'react';
import AuthComponent from '../AuthComponent';
import {FilePond} from 'react-filepond';
import 'filepond/dist/filepond.min.css';
class PbaInput extends AuthComponent {
constructor(props) {
super(props);
// set schema from server
this.state = this.getStateFromProps(this.props);
}
getStateFromProps(props){
let options = props.options
// set schema from server
return {
filename: options.filename,
apiEndpoint: options.apiEndpoint,
setPbaFilename: options.setPbaFilename
};
}
componentDidUpdate(prevProps, _prevState, _snapshot) {
if(prevProps.options.filename !== this.props.options.filename && this.props.options.filename === ''){
this.setState({filename: this.props.options.filename})
}
}
getPBAfile() {
if (this.state.filename) {
return PbaInput.getFullPBAfile(this.state.filename)
}
}
static getFullPBAfile(filename) {
return [{
source: filename,
options: {
type: 'limbo'
}
}];
}
getServerParams(path) {
return {
url: path,
process: this.getRequestParams(),
revert: this.getRequestParams(),
restore: this.getRequestParams(),
load: this.getRequestParams(),
fetch: this.getRequestParams()
}
}
getRequestParams() {
return {headers: {'Authorization': this.jwtHeader}}
}
render() {
return (<FilePond
key={this.state.apiEndpoint}
server={this.getServerParams(this.state.apiEndpoint)}
files={this.getPBAfile()}
onupdatefiles={fileItems => {
if (fileItems.length > 0) {
this.state.setPbaFilename(fileItems[0].file.name)
} else {
this.state.setPbaFilename('')
}
}}
/>)
}
}
export default PbaInput;

View File

@ -0,0 +1,92 @@
import AdvancedMultiSelect from '../ui-components/AdvancedMultiSelect';
import PbaInput from './PbaInput';
import {API_PBA_LINUX, API_PBA_WINDOWS} from '../pages/ConfigurePage';
import FieldWithInfo from './FieldWithInfo';
export default function UiSchema(props) {
const UiSchema = {
basic: {
'ui:order': ['exploiters', 'credentials'],
exploiters: {
exploiter_classes: {
classNames: 'config-template-no-header',
'ui:widget': AdvancedMultiSelect
}
}
},
basic_network: {
'ui:order': ['scope', 'network_analysis'],
scope: {
blocked_ips: {
'ui:field': FieldWithInfo
},
subnet_scan_list: {
format: 'ip-list'
}
}
},
monkey: {
post_breach: {
post_breach_actions: {
classNames: 'config-template-no-header',
'ui:widget': AdvancedMultiSelect
},
custom_PBA_linux_cmd: {
'ui:widget': 'textarea',
'ui:emptyValue': ''
},
PBA_linux_file: {
'ui:widget': PbaInput,
'ui:options': {
filename: props.PBA_linux_filename,
apiEndpoint: API_PBA_LINUX,
setPbaFilename: props.setPbaFilenameLinux
}
},
custom_PBA_windows_cmd: {
'ui:widget': 'textarea',
'ui:emptyValue': ''
},
PBA_windows_file: {
'ui:widget': PbaInput,
'ui:options': {
filename: props.PBA_windows_filename,
apiEndpoint: API_PBA_WINDOWS,
setPbaFilename: props.setPbaFilenameWindows
}
},
PBA_linux_filename: {
classNames: 'linux-pba-file-info',
'ui:emptyValue': ''
},
PBA_windows_filename: {
classNames: 'windows-pba-file-info',
'ui:emptyValue': ''
}
},
system_info: {
system_info_collector_classes: {
classNames: 'config-template-no-header',
'ui:widget': AdvancedMultiSelect
}
}
},
internal: {
general: {
started_on_island: {'ui:widget': 'hidden'}
},
classes: {
finger_classes: {
classNames: 'config-template-no-header',
'ui:widget': AdvancedMultiSelect
}
},
monkey: {
alive: {
classNames: 'config-field-hidden'
}
}
}
};
return UiSchema[props.selectedSection]
}

View File

@ -0,0 +1,14 @@
import {IP, IP_RANGE} from './ValidationFormats';
export default function transformErrors(errors) {
return errors.map(error => {
if (error.name === 'type') {
error.message = 'Field can\'t be empty.'
} else if (error.name === 'format' && error.params.format === IP_RANGE) {
error.message = 'Invalid IP range, refer to description for valid examples.'
} else if (error.name === 'format' && error.params.format === IP) {
error.message = 'Invalid IP.'
}
return error;
});
}

View File

@ -0,0 +1,24 @@
const ipRegex = '((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'
const cidrNotationRegex = '([0-9]|1[0-9]|2[0-9]|3[0-2])'
const hostnameRegex = '^([A-Za-z0-9]*[A-Za-z]+[A-Za-z0-9]*.?)*([A-Za-z0-9]*[A-Za-z]+[A-Za-z0-9]*)$'
export const IP_RANGE = 'ip-range';
export const IP = 'ip';
export const formValidationFormats = {
[IP_RANGE]: buildIpRangeRegex(),
[IP]: buildIpRegex()
};
function buildIpRangeRegex(){
return new RegExp([
'^'+ipRegex+'$|', // Single: IP
'^'+ipRegex+'-'+ipRegex+'$|', // IP range: IP-IP
'^'+ipRegex+'/'+cidrNotationRegex+'$|', // IP range with cidr notation: IP/cidr
hostnameRegex // Hostname: target.tg
].join(''))
}
function buildIpRegex(){
return new RegExp('^'+ipRegex+'$')
}

View File

@ -101,9 +101,9 @@ class PreviewPaneComponent extends AuthComponent {
.replace(/\\t/g, '\t')
.replace(/\\b/g, '\b')
.replace(/\\f/g, '\f')
.replace(/\\"/g, '\"')
.replace(/\\"/g, '"')
.replace(/\\'/g, '\'')
.replace(/\\&/g, '\&');
.replace(/\\&/g, '&');
}
downloadLog(asset) {

View File

@ -3,30 +3,30 @@ import Form from 'react-jsonschema-form-bs4';
import {Col, Modal, Nav, Button} from 'react-bootstrap';
import FileSaver from 'file-saver';
import AuthComponent from '../AuthComponent';
import {FilePond} from 'react-filepond';
import 'filepond/dist/filepond.min.css';
import ConfigMatrixComponent from '../attack/ConfigMatrixComponent';
import UiSchema from '../configuration-components/UiSchema';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle';
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
import {faExclamationCircle} from '@fortawesome/free-solid-svg-icons/faExclamationCircle';
import {formValidationFormats} from '../configuration-components/ValidationFormats';
import transformErrors from '../configuration-components/ValidationErrorMessages';
import InternalConfig from '../configuration-components/InternalConfig';
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';
class ConfigurePageComponent extends AuthComponent {
constructor(props) {
super(props);
this.PBAwindowsPond = null;
this.PBAlinuxPond = null;
this.currentSection = 'attack';
this.currentFormData = {};
this.initialConfig = {};
this.initialAttackConfig = {};
this.sectionsOrder = ['attack', 'basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal'];
this.uiSchemas = this.getUiSchemas();
// set schema from server
this.sectionsOrder = ['attack', 'basic', 'basic_network', 'monkey', 'internal'];
this.state = {
schema: {},
configuration: {},
@ -34,53 +34,10 @@ class ConfigurePageComponent extends AuthComponent {
lastAction: 'none',
sections: [],
selectedSection: 'attack',
PBAwinFile: [],
PBAlinuxFile: [],
showAttackAlert: false
};
}
getUiSchemas() {
return ({
basic: {'ui:order': ['general', 'credentials']},
basic_network: {},
monkey: {
behaviour: {
custom_PBA_linux_cmd: {
'ui:widget': 'textarea',
'ui:emptyValue': ''
},
PBA_linux_file: {
'ui:widget': this.PBAlinux
},
custom_PBA_windows_cmd: {
'ui:widget': 'textarea',
'ui:emptyValue': ''
},
PBA_windows_file: {
'ui:widget': this.PBAwindows
},
PBA_linux_filename: {
classNames: 'linux-pba-file-info',
'ui:emptyValue': ''
},
PBA_windows_filename: {
classNames: 'windows-pba-file-info',
'ui:emptyValue': ''
}
}
},
cnc: {},
network: {},
exploits: {},
internal: {
general: {
started_on_island: {'ui:widget': 'hidden'}
}
}
})
}
setInitialConfig(config) {
// Sets a reference to know if config was changed
this.initialConfig = JSON.parse(JSON.stringify(config));
@ -122,7 +79,7 @@ class ConfigurePageComponent extends AuthComponent {
.then(res => res.json())
.then(data => {
this.setInitialConfig(data.configuration);
this.setState({configuration: data.configuration})
this.setState({configuration: data.configuration});
})
};
@ -271,7 +228,6 @@ class ConfigurePageComponent extends AuthComponent {
};
resetConfig = () => {
this.removePBAfiles();
this.authFetch(CONFIG_URL,
{
method: 'POST',
@ -287,6 +243,10 @@ class ConfigurePageComponent extends AuthComponent {
});
this.setInitialConfig(res.configuration);
this.props.onStatusChange();
}
).then(() => {
this.removePBAfile(API_PBA_WINDOWS, this.setPbaFilenameWindows)
this.removePBAfile(API_PBA_LINUX, this.setPbaFilenameLinux)
});
this.authFetch(ATTACK_URL, {
method: 'POST',
@ -300,21 +260,17 @@ class ConfigurePageComponent extends AuthComponent {
})
};
removePBAfiles() {
// We need to clean files from widget, local state and configuration (to sync with bac end)
if (this.PBAwindowsPond !== null) {
this.PBAwindowsPond.removeFile();
}
if (this.PBAlinuxPond !== null) {
this.PBAlinuxPond.removeFile();
removePBAfile(apiEndpoint, setFilenameFnc) {
this.sendPbaRemoveRequest(apiEndpoint)
setFilenameFnc('')
}
sendPbaRemoveRequest(apiEndpoint) {
let request_options = {
method: 'DELETE',
headers: {'Content-Type': 'text/plain'}
};
this.authFetch('/api/fileUpload/PBAlinux', request_options);
this.authFetch('/api/fileUpload/PBAwindows', request_options);
this.setState({PBAlinuxFile: [], PBAwinFile: []});
this.authFetch(apiEndpoint, request_options);
}
setConfigOnImport = (event) => {
@ -366,82 +322,6 @@ class ConfigurePageComponent extends AuthComponent {
event.target.value = null;
};
PBAwindows = () => {
return (<FilePond
server={{
url: '/api/fileUpload/PBAwindows',
process: {headers: {'Authorization': this.jwtHeader}},
revert: {headers: {'Authorization': this.jwtHeader}},
restore: {headers: {'Authorization': this.jwtHeader}},
load: {headers: {'Authorization': this.jwtHeader}},
fetch: {headers: {'Authorization': this.jwtHeader}}
}}
files={this.getWinPBAfile()}
onupdatefiles={fileItems => {
this.setState({
PBAwinFile: fileItems.map(fileItem => fileItem.file)
})
}}
ref={ref => this.PBAwindowsPond = ref}
/>)
};
PBAlinux = () => {
return (<FilePond
server={{
url: '/api/fileUpload/PBAlinux',
process: {headers: {'Authorization': this.jwtHeader}},
revert: {headers: {'Authorization': this.jwtHeader}},
restore: {headers: {'Authorization': this.jwtHeader}},
load: {headers: {'Authorization': this.jwtHeader}},
fetch: {headers: {'Authorization': this.jwtHeader}}
}}
files={this.getLinuxPBAfile()}
onupdatefiles={fileItems => {
this.setState({
PBAlinuxFile: fileItems.map(fileItem => fileItem.file)
})
}}
ref={ref => this.PBAlinuxPond = ref}
/>)
};
getWinPBAfile() {
if (this.state.PBAwinFile.length !== 0) {
return ConfigurePageComponent.getMockPBAfile(this.state.PBAwinFile[0])
} else if (this.state.configuration.monkey.behaviour.PBA_windows_filename) {
return ConfigurePageComponent.getFullPBAfile(this.state.configuration.monkey.behaviour.PBA_windows_filename)
}
}
getLinuxPBAfile() {
if (this.state.PBAlinuxFile.length !== 0) {
return ConfigurePageComponent.getMockPBAfile(this.state.PBAlinuxFile[0])
} else if (this.state.configuration.monkey.behaviour.PBA_linux_filename) {
return ConfigurePageComponent.getFullPBAfile(this.state.configuration.monkey.behaviour.PBA_linux_filename)
}
}
static getFullPBAfile(filename) {
return [{
source: filename,
options: {
type: 'limbo'
}
}];
}
static getMockPBAfile(mockFile) {
let pbaFile = [{
source: mockFile.name,
options: {
type: 'limbo'
}
}];
pbaFile[0].options.file = mockFile;
return pbaFile
}
renderMatrix = () => {
return (<ConfigMatrixComponent configuration={this.state.attackConfig}
submit={this.componentDidMount}
@ -449,43 +329,65 @@ class ConfigurePageComponent extends AuthComponent {
change={this.attackTechniqueChange}/>)
};
renderConfigContent = (displayedSchema) => {
return (<div>
{this.renderBasicNetworkWarning()}
<Form schema={displayedSchema}
uiSchema={this.uiSchemas[this.state.selectedSection]}
formData={this.state.configuration[this.state.selectedSection]}
onChange={this.onChange}
noValidate={true}
className={'config-form'}>
let formProperties = {};
formProperties['schema'] = displayedSchema
formProperties['uiSchema'] = UiSchema({
PBA_linux_filename: this.state.configuration.monkey.post_breach.PBA_linux_filename,
PBA_windows_filename: this.state.configuration.monkey.post_breach.PBA_windows_filename,
setPbaFilenameWindows: this.setPbaFilenameWindows,
setPbaFilenameLinux: this.setPbaFilenameLinux,
selectedSection: this.state.selectedSection
})
formProperties['formData'] = this.state.configuration[this.state.selectedSection];
formProperties['onChange'] = this.onChange;
formProperties['customFormats'] = formValidationFormats;
formProperties['transformErrors'] = transformErrors;
formProperties['className'] = 'config-form';
formProperties['liveValidate'] = true;
if (this.state.selectedSection === 'internal') {
return (<InternalConfig {...formProperties}/>)
} else {
return (
<div>
<Form {...formProperties}>
<button type='submit' className={'hidden'}>Submit</button>
</Form>
</div>)
};
renderBasicNetworkWarning = () => {
if (this.state.selectedSection === 'basic_network') {
return (<div className='alert alert-info'>
<FontAwesomeIcon icon={faInfoCircle} 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/>)
</div>
)
}
};
setPbaFilenameWindows = (filename) => {
let config = this.state.configuration
config.monkey.post_breach.PBA_windows_filename = filename
this.setState({
configuration: config
})
}
setPbaFilenameLinux = (filename) => {
let config = this.state.configuration
config.monkey.post_breach.PBA_linux_filename = filename
this.setState({
configuration: config
})
}
renderNav = () => {
return (<Nav variant='tabs'
fill
activeKey={this.state.selectedSection} onSelect={this.setSelectedSection}
style={{'marginBottom': '2em'}}
className={'config-nav'}>
{this.state.sections.map(section =>
<Nav.Item>
<Nav.Link eventKey={section.key}>{section.title}</Nav.Link>
</Nav.Item>)}
{this.state.sections.map(section => {
let classProp = section.key.startsWith('basic') ? 'tab-primary' : '';
return (
<Nav.Item key={section.key}>
<Nav.Link className={classProp} eventKey={section.key}>{section.title}</Nav.Link>
</Nav.Item>);
})}
</Nav>)
};
@ -495,6 +397,7 @@ class ConfigurePageComponent extends AuthComponent {
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()

View File

@ -1,8 +1,8 @@
import React from 'react';
import {Col} from 'react-bootstrap';
import rainge from 'rainge';
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faCopyright} from "@fortawesome/free-regular-svg-icons";
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCopyright} from '@fortawesome/free-regular-svg-icons';
class LicensePageComponent extends React.Component {
constructor(props) {

View File

@ -2,9 +2,9 @@ import React from 'react';
import {Button, Col, Container, Form, Row} from 'react-bootstrap';
import AuthService from '../../services/AuthService';
import Particles from "react-particles-js";
import {particleParams} from "../../styles/particle-component/AuthPageParams";
import monkeyGeneral from "../../images/militant-monkey.svg";
import Particles from 'react-particles-js';
import {particleParams} from '../../styles/components/particle-component/AuthPageParams';
import monkeyGeneral from '../../images/militant-monkey.svg';
class LoginPageComponent extends React.Component {
login = (event) => {

View File

@ -8,8 +8,8 @@ import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
import {getOptions, edgeGroupToColor} from 'components/map/MapOptions';
import AuthComponent from '../AuthComponent';
import '../../styles/Map.scss';
import {faInfoCircle} from "@fortawesome/free-solid-svg-icons/faInfoCircle";
import '../../styles/components/Map.scss';
import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle';
class MapPageComponent extends AuthComponent {
constructor(props) {

View File

@ -1,6 +1,6 @@
import React from 'react';
import '../../styles/NotFoundPage.scss';
import '../../styles/pages/NotFoundPage.scss';
import monkeyDetective from '../../images/detective-monkey.svg';

View File

@ -3,7 +3,7 @@ import {Row, Col, Container, Form, Button} from 'react-bootstrap';
import Particles from 'react-particles-js';
import AuthService from '../../services/AuthService';
import {particleParams} from '../../styles/particle-component/AuthPageParams';
import {particleParams} from '../../styles/components/particle-component/AuthPageParams';
import monkeyDetective from '../../images/detective-monkey.svg';
class RegisterPageComponent extends React.Component {

View File

@ -8,8 +8,8 @@ import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faClipboard} from '@fortawesome/free-solid-svg-icons/faClipboard';
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
import {faSync} from '@fortawesome/free-solid-svg-icons/faSync';
import {faInfoCircle} from "@fortawesome/free-solid-svg-icons/faInfoCircle";
import {faExclamationTriangle} from "@fortawesome/free-solid-svg-icons/faExclamationTriangle";
import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle';
import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons/faExclamationTriangle';
import {Link} from 'react-router-dom';
import AuthComponent from '../AuthComponent';

View File

@ -3,10 +3,10 @@ import {Col, Button} from 'react-bootstrap';
import {Link} from 'react-router-dom';
import AuthComponent from '../AuthComponent';
import StartOverModal from '../ui-components/StartOverModal';
import '../../styles/StartOverPage.scss';
import '../../styles/pages/StartOverPage.scss';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faInfoCircle} from "@fortawesome/free-solid-svg-icons/faInfoCircle";
import {faCheck} from "@fortawesome/free-solid-svg-icons/faCheck";
import {faInfoCircle} from '@fortawesome/free-solid-svg-icons/faInfoCircle';
import {faCheck} from '@fortawesome/free-solid-svg-icons/faCheck';
class StartOverPageComponent extends AuthComponent {
constructor(props) {

View File

@ -6,8 +6,8 @@ import AuthComponent from '../AuthComponent';
import download from 'downloadjs';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import '../../styles/TelemetryPage.scss';
import {faDownload} from "@fortawesome/free-solid-svg-icons/faDownload";
import '../../styles/pages/TelemetryPage.scss';
import {faDownload} from '@fortawesome/free-solid-svg-icons/faDownload';
const renderJson = (val) => <JSONTree data={val} level={1} theme="eighties" invertTheme={true}/>;
const renderTime = (val) => val.split('.')[0];

View File

@ -1,11 +1,13 @@
import React from 'react';
import {Col, Button} from 'react-bootstrap';
import '../../styles/Collapse.scss';
import '../../styles/components/Collapse.scss';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {faCircle} from '@fortawesome/free-solid-svg-icons/faCircle';
import {faRadiation} from '@fortawesome/free-solid-svg-icons/faRadiation';
import {faEye} from '@fortawesome/free-solid-svg-icons/faEye';
import {faEyeSlash} from '@fortawesome/free-solid-svg-icons/faEyeSlash';
import {faToggleOff} from '@fortawesome/free-solid-svg-icons/faToggleOff';
import marked from 'marked';
import ReportHeader, {ReportTypes} from './common/ReportHeader';
import {ScanStatus} from '../attack/techniques/Helpers';
@ -37,18 +39,18 @@ class AttackReport extends React.Component {
};
if (typeof this.props.report.schema !== 'undefined' && typeof this.props.report.techniques !== 'undefined'){
this.state['schema'] = this.props.report['schema'];
this.state['techniques'] = AttackReport.addLinksToTechniques(this.props.report['schema'], this.props.report['techniques']);
this.state['techniques'] = AttackReport.modifyTechniqueData(this.props.report['schema'], this.props.report['techniques']);
}
}
componentDidUpdate(prevProps) {
if (this.props.report !== prevProps.report) {
this.setState({schema: this.props.report['schema'],
techniques: AttackReport.addLinksToTechniques(this.props.report['schema'], this.props.report['techniques'])})
techniques: AttackReport.modifyTechniqueData(this.props.report['schema'], this.props.report['techniques'])})
}
}
onTechniqueSelect = (technique, value) => {
onTechniqueSelect = (technique, _) => {
let selectedTechnique = this.getTechniqueByTitle(technique);
if (selectedTechnique === false){
return;
@ -62,6 +64,8 @@ class AttackReport extends React.Component {
return 'collapse-warning';
case ScanStatus.USED:
return 'collapse-danger';
case ScanStatus.DISABLED:
return 'collapse-disabled';
default:
return 'collapse-default';
}
@ -73,6 +77,8 @@ class AttackReport extends React.Component {
return <FontAwesomeIcon icon={faEye} className={'technique-status-icon'}/>;
case ScanStatus.USED:
return <FontAwesomeIcon icon={faRadiation} className={'technique-status-icon'}/>;
case ScanStatus.DISABLED:
return <FontAwesomeIcon icon={faToggleOff} className={'technique-status-icon'}/>;
default:
return <FontAwesomeIcon icon={faEyeSlash} className={'technique-status-icon'}/>;
}
@ -80,15 +86,19 @@ class AttackReport extends React.Component {
renderLegend() {
return (<div id='header' className='row justify-content-between attack-legend'>
<Col xs={4}>
<Col xs={3}>
<FontAwesomeIcon icon={faCircle} className='technique-disabled'/>
<span> - Disabled</span>
</Col>
<Col xs={3}>
<FontAwesomeIcon icon={faCircle} className='technique-not-attempted'/>
<span> - Not attempted</span>
</Col>
<Col xs={4}>
<Col xs={3}>
<FontAwesomeIcon icon={faCircle} className='technique-attempted'/>
<span> - Tried (but failed)</span>
</Col>
<Col xs={4}>
<Col xs={3}>
<FontAwesomeIcon icon={faCircle} className='technique-used'/>
<span> - Successfully used</span>
</Col>
@ -134,7 +144,8 @@ class AttackReport extends React.Component {
return false;
}
static addLinksToTechniques(schema, techniques){
static modifyTechniqueData(schema, techniques){
// add links to techniques
schema = schema.properties;
for(const type in schema){
if (! schema.hasOwnProperty(type)) {return false;}
@ -146,6 +157,11 @@ class AttackReport extends React.Component {
}
}
}
// modify techniques' messages
for (const tech_id in techniques){
techniques[tech_id]['message'] = <div dangerouslySetInnerHTML={{__html: marked(techniques[tech_id]['message'])}} />;
}
return techniques
}

View File

@ -473,7 +473,7 @@ class ReportPageComponent extends AuthComponent {
}
generateShellshockPathListBadges(paths) {
return paths.map(path => <span className="badge badge-warning" style={{margin: '2px'}}>{path}</span>);
return paths.map(path => <span className="badge badge-warning" style={{margin: '2px'}} key={path}>{path}</span>);
}
generateSmbPasswordIssue(issue) {

View File

@ -3,7 +3,7 @@ import React from 'react';
import Checkbox from '../../ui-components/Checkbox';
import ReactTable from 'react-table';
import 'filepond/dist/filepond.min.css';
import '../../../styles/report/ReportAttackMatrix.scss';
import '../../../styles/pages/report/ReportAttackMatrix.scss';
class ReportMatrixComponent extends React.Component {
constructor(props) {

View File

@ -1,7 +1,7 @@
import React, {Component} from 'react';
import * as PropTypes from 'prop-types';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faExclamationTriangle} from "@fortawesome/free-solid-svg-icons/faExclamationTriangle";
import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons/faExclamationTriangle';
export default class MonkeysStillAliveWarning extends Component {
render() {

View File

@ -1,7 +1,7 @@
import React, {Component} from 'react';
import {NavLink} from 'react-router-dom';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faExclamationTriangle} from "@fortawesome/free-solid-svg-icons/faExclamationTriangle";
import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons/faExclamationTriangle';
export default class MustRunMonkeyWarning extends Component {
render() {

View File

@ -7,7 +7,7 @@ import { faPrint } from '@fortawesome/free-solid-svg-icons/faPrint';
export default class PrintReportButton extends Component {
render() {
return <div className="text-center no-print">
<Button size="md" variant={"outline-standard"} onClick={this.props.onClick}>
<Button size="md" variant={'outline-standard'} onClick={this.props.onClick}>
<FontAwesomeIcon icon={faPrint}/> Print
Report</Button>
</div>

View File

@ -1,4 +1,4 @@
import React from "react";
import React from 'react';
export let renderArray = function (val) {
return <>{val.map(x => <div key={x}>{x}</div>)}</>;

View File

@ -1,7 +1,7 @@
import React from 'react';
import ReactTable from 'react-table';
import Pluralize from 'pluralize';
import {renderArray, renderIpAddresses} from "../common/RenderArrays";
import {renderArray, renderIpAddresses} from '../common/RenderArrays';
const columns = [

View File

@ -1,8 +1,8 @@
import React from 'react';
import ReactTable from 'react-table';
import Pluralize from 'pluralize';
import {renderIpAddresses} from "../common/RenderArrays";
import parsePbaResults from "./PostBreachParser";
import {renderIpAddresses} from '../common/RenderArrays';
import parsePbaResults from './PostBreachParser';
let renderMachine = function (data) {
return <div>{data.label} ( {renderIpAddresses(data)} )</div>
@ -58,7 +58,7 @@ class PostBreachComponent extends React.Component {
pbaMachines = pbaMachines.map(pbaData => parsePbaResults(pbaData));
let defaultPageSize = pbaMachines.length > pageSize ? pageSize : pbaMachines.length;
let showPagination = pbaMachines > pageSize;
const pbaCount = pbaMachines.reduce((accumulated, pbaMachine) => accumulated+pbaMachine["pba_results"].length, 0);
const pbaCount = pbaMachines.reduce((accumulated, pbaMachine) => accumulated+pbaMachine['pba_results'].length, 0);
return (
<>
<p>

View File

@ -1,7 +1,7 @@
import React from 'react';
import ReactTable from 'react-table';
import Pluralize from 'pluralize';
import {renderArray, renderIpAddresses} from "../common/RenderArrays";
import {renderArray, renderIpAddresses} from '../common/RenderArrays';
const columns = [
@ -32,7 +32,7 @@ class ScannedServersComponent extends React.Component {
let showPagination = this.props.data.length > pageSize;
const scannedMachinesCount = this.props.data.length;
const reducerFromScannedServerToServicesAmount = (accumulated, scannedServer) => accumulated + scannedServer["services"].length;
const reducerFromScannedServerToServicesAmount = (accumulated, scannedServer) => accumulated + scannedServer['services'].length;
const scannedServicesAmount = this.props.data.reduce(reducerFromScannedServerToServicesAmount, 0);
return (

View File

@ -1,6 +1,6 @@
import React from 'react';
import ReactTable from 'react-table'
import {renderArray} from "../common/RenderArrays";
import {renderArray} from '../common/RenderArrays';
const columns = [

View File

@ -14,7 +14,7 @@ class ZeroTrustReportLegend extends Component {
super(props, context);
this.state = {
open: false,
open: false
};
}

View File

@ -7,7 +7,7 @@ import {Card, Collapse} from 'react-bootstrap';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faChevronDown} from '@fortawesome/free-solid-svg-icons';
import '../../../styles/report/ZeroTrustReport.scss';
import '../../../styles/pages/report/ZeroTrustReport.scss';
export default class SinglePillarPrinciplesStatus extends AuthComponent {
@ -15,7 +15,7 @@ export default class SinglePillarPrinciplesStatus extends AuthComponent {
super(props, context);
this.state = {
open: false,
open: false
};
}

View File

@ -149,7 +149,7 @@ class VennDiagram extends React.Component {
} else {
// Return z indices to default
Object.keys(this.layout).forEach(function (d_, i_) {
Object.keys(this.layout).forEach(function (_d, i_) {
document.querySelector('#' + self.prefix).appendChild(document.querySelector('#' + self.prefix + 'Node_' + i_).parentNode);
})
}

View File

@ -67,7 +67,7 @@ class AwsRunTableComponent extends React.Component {
this.setState({selectAll, selection});
};
getTrProps = (s, r) => {
getTrProps = (_, r) => {
let color = 'inherit';
if (r) {
let instId = r.original.instance_id;

View File

@ -0,0 +1,121 @@
import React, {useState} from 'react';
import {Card, Button, Form} from 'react-bootstrap';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCheckSquare} from '@fortawesome/free-solid-svg-icons';
import {faSquare} from '@fortawesome/free-regular-svg-icons';
import {cloneDeep} from 'lodash';
import {getComponentHeight} from './utils/HeightCalculator';
import {resolveObjectPath} from './utils/ObjectPathResolver';
import InfoPane from './InfoPane';
function getSelectValuesAfterClick(valueArray, clickedValue) {
if (valueArray.includes(clickedValue)) {
return valueArray.filter((e) => {
return e !== clickedValue;
});
} else {
valueArray.push(clickedValue);
return valueArray;
}
}
function onMasterCheckboxClick(checkboxValue, defaultArray, onChangeFnc) {
if (checkboxValue) {
onChangeFnc([]);
} else {
onChangeFnc(defaultArray);
}
}
// Definitions passed to components only contains value and label,
// custom fields like "info" or "links" must be pulled from registry object using this function
function getFullDefinitionsFromRegistry(refString, registry) {
return getObjectFromRegistryByRef(refString, registry).anyOf;
}
function getObjectFromRegistryByRef(refString, registry) {
let refArray = refString.replace('#', '').split('/');
return resolveObjectPath(refArray, registry);
}
function getFullDefinitionByKey(refString, registry, itemKey) {
let fullArray = getFullDefinitionsFromRegistry(refString, registry);
return fullArray.filter(e => (e.enum[0] === itemKey))[0];
}
function setPaneInfo(refString, registry, itemKey, setPaneInfoFnc) {
let definitionObj = getFullDefinitionByKey(refString, registry, itemKey);
setPaneInfoFnc({title: definitionObj.title, content: definitionObj.info, link: definitionObj.link});
}
function getDefaultPaneParams(refString, registry) {
let configSection = getObjectFromRegistryByRef(refString, registry);
return ({title: configSection.title, content: configSection.description});
}
function AdvancedMultiSelect(props) {
const [masterCheckbox, setMasterCheckbox] = useState(true);
const {
schema,
id,
options,
value,
required,
disabled,
readonly,
multiple,
autofocus,
onChange,
registry
} = props;
const {enumOptions} = options;
const [infoPaneParams, setInfoPaneParams] = useState(getDefaultPaneParams(schema.items.$ref, registry));
getDefaultPaneParams(schema.items.$ref, registry);
const selectValue = cloneDeep(value);
return (
<div className={'advanced-multi-select'}>
<Card.Header>
<Button key={`${props.schema.title}-button`} value={value}
variant={'link'} disabled={disabled}
onClick={() => {
onMasterCheckboxClick(masterCheckbox, schema.default, onChange);
setMasterCheckbox(!masterCheckbox);
}}
>
<FontAwesomeIcon icon={masterCheckbox ? faCheckSquare : faSquare}/>
</Button>
<span className={'header-title'}>{props.schema.title}</span>
</Card.Header>
<Form.Group
style={{height: `${getComponentHeight(enumOptions.length)}px`}}
id={id}
multiple={multiple}
className='choice-block form-control'
required={required}
disabled={disabled || readonly}
autoFocus={autofocus}>
{enumOptions.map(({value, label}, i) => {
return (
<Form.Group
key={i}
onClick={() => setPaneInfo(schema.items.$ref, registry, value, setInfoPaneParams)}>
<Button value={value} variant={'link'} disabled={disabled}
onClick={() => onChange(getSelectValuesAfterClick(selectValue, value))}>
<FontAwesomeIcon icon={selectValue.includes(value) ? faCheckSquare : faSquare}/>
</Button>
<span className={'option-text'}>
{label}
</span>
</Form.Group>
);
})}
</Form.Group>
<InfoPane title={infoPaneParams.title} body={infoPaneParams.content} link={infoPaneParams.link}/>
</div>
);
}
export default AdvancedMultiSelect;

View File

@ -1,4 +1,4 @@
import '../../styles/Checkbox.scss'
import '../../styles/components/Checkbox.scss'
import React from 'react';
class CheckboxComponent extends React.PureComponent {

View File

@ -0,0 +1,52 @@
import {Card, Button} from 'react-bootstrap';
import React from 'react';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons';
function InfoPane(props) {
return (
<Card className={'info-pane'}>
{getTitle(props)}
{getSubtitle(props)}
{getBody(props)}
</Card>);
}
function getTitle(props) {
if (typeof (props.title) == 'string') {
return (
<Card.Title className={'pane-title'}>
{props.title}
{getLinkButton(props)}
</Card.Title>)
}
}
function getLinkButton(props) {
if (typeof (props.link) == 'string') {
return (
<Button variant={'link'} className={'pane-link'} href={props.link} target={'_blank'}>
<FontAwesomeIcon icon={faQuestionCircle}/>
</Button>)
}
}
function getSubtitle(props) {
if (typeof (props.subtitle) == 'string') {
return (
<Card.Subtitle className={'pane-subtitle'}>
{props.subtitle}
</Card.Subtitle>)
}
}
function getBody(props) {
return (
<Card.Body className={'pane-body'}>
{props.body}
</Card.Body>
)
}
export default InfoPane

View File

@ -1,6 +1,5 @@
import {Modal} from 'react-bootstrap';
import React from 'react';
import {GridLoader} from 'react-spinners';
class MissingBinariesModal extends React.PureComponent {

View File

@ -0,0 +1,14 @@
const defaultMinHeight = 25
const defaultMaxHeight = 250
const defaultSubcomponentHeight = 25
export function getComponentHeight(subcomponentCount,
subcomponentHeight = defaultSubcomponentHeight,
minHeight = defaultMinHeight,
maxHeight = defaultMaxHeight) {
let height = minHeight + (subcomponentHeight*subcomponentCount);
if (height > maxHeight)
height = maxHeight
return height
}

Some files were not shown because too many files have changed in this diff Show More