forked from p34709852/monkey
Merge pull request #707 from guardicore/feature/configuration_improvement
Configuration UI improvements
This commit is contained in:
commit
c1c412f176
|
@ -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=25
|
||||
- eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT # Test for max warnings
|
||||
|
||||
# Build documentation
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -2,3 +2,5 @@ AWS_COLLECTOR = "AwsCollector"
|
|||
HOSTNAME_COLLECTOR = "HostnameCollector"
|
||||
ENVIRONMENT_COLLECTOR = "EnvironmentCollector"
|
||||
PROCESS_LIST_COLLECTOR = "ProcessListCollector"
|
||||
MIMIKATZ_COLLECTOR = "MimikatzCollector"
|
||||
AZURE_CRED_COLLECTOR = "AzureCollector"
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# Defined in UI on ValidationFormats.js
|
||||
IP_RANGE = "ip-range"
|
||||
IP = "ip"
|
|
@ -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
|
||||
###########################
|
||||
|
|
|
@ -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 = ""
|
||||
|
|
|
@ -190,23 +190,23 @@ 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]:
|
||||
if self.try_exploiting(machine, exploiter):
|
||||
host_exploited = True
|
||||
VictimHostTelem('T1210', ScanStatus.USED, machine=machine).send()
|
||||
break
|
||||
if not host_exploited:
|
||||
self._fail_exploitation_machines.add(machine)
|
||||
VictimHostTelem('T1210', ScanStatus.SCANNED, machine=machine).send()
|
||||
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]:
|
||||
if self.try_exploiting(machine, exploiter):
|
||||
host_exploited = True
|
||||
VictimHostTelem('T1210', ScanStatus.USED, machine=machine).send()
|
||||
break
|
||||
if not host_exploited:
|
||||
self._fail_exploitation_machines.add(machine)
|
||||
VictimHostTelem('T1210', ScanStatus.SCANNED, machine=machine).send()
|
||||
if not self._keep_running:
|
||||
break
|
||||
|
||||
|
@ -242,11 +242,10 @@ 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()
|
||||
SystemInfoTelem(system_info).send()
|
||||
LOG.debug("Calling system info collection")
|
||||
system_info_collector = SystemInfoCollector()
|
||||
system_info = system_info_collector.get_info()
|
||||
SystemInfoTelem(system_info).send()
|
||||
|
||||
def shutdown_by_not_alive_config(self):
|
||||
if not WormConfiguration.alive:
|
||||
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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\""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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/"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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!"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
WARNING_SIGN = " \u26A0"
|
|
@ -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,
|
||||
{
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -74,6 +74,7 @@
|
|||
"file-saver": "^2.0.2",
|
||||
"filepond": "^4.18.0",
|
||||
"jwt-decode": "^2.2.0",
|
||||
"lodash": "^4.17.15",
|
||||
"marked": "^0.8.2",
|
||||
"normalize.css": "^8.0.0",
|
||||
"npm": "^6.14.6",
|
||||
|
@ -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",
|
||||
|
|
|
@ -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}}/>
|
||||
}
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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]
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
}
|
|
@ -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+'$')
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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',
|
||||
|
@ -280,14 +236,18 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
this.setState({
|
||||
lastAction: 'reset',
|
||||
schema: res.schema,
|
||||
configuration: res.configuration
|
||||
});
|
||||
this.setInitialConfig(res.configuration);
|
||||
this.props.onStatusChange();
|
||||
});
|
||||
this.setState({
|
||||
lastAction: 'reset',
|
||||
schema: res.schema,
|
||||
configuration: res.configuration
|
||||
});
|
||||
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',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
|
@ -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'}>
|
||||
<button type='submit' className={'hidden'}>Submit</button>
|
||||
</Form>
|
||||
</div>)
|
||||
};
|
||||
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;
|
||||
|
||||
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>)
|
||||
if (this.state.selectedSection === 'internal') {
|
||||
return (<InternalConfig {...formProperties}/>)
|
||||
} else {
|
||||
return (<div/>)
|
||||
return (
|
||||
<div>
|
||||
<Form {...formProperties}>
|
||||
<button type='submit' className={'hidden'}>Submit</button>
|
||||
</Form>
|
||||
</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()
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
import '../../styles/NotFoundPage.scss';
|
||||
import '../../styles/pages/NotFoundPage.scss';
|
||||
|
||||
import monkeyDetective from '../../images/detective-monkey.svg';
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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';
|
||||
|
@ -48,7 +48,7 @@ class AttackReport extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
onTechniqueSelect = (technique, value) => {
|
||||
onTechniqueSelect = (technique, _) => {
|
||||
let selectedTechnique = this.getTechniqueByTitle(technique);
|
||||
if (selectedTechnique === false){
|
||||
return;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>)}</>;
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import Pluralize from 'pluralize';
|
||||
import {renderIpAddresses} from "../common/RenderArrays";
|
||||
import {renderIpAddresses} from '../common/RenderArrays';
|
||||
|
||||
let renderMachine = function (data) {
|
||||
return <div>{data.label} ( {renderIpAddresses(data)} )</div>
|
||||
|
@ -56,7 +56,7 @@ class PostBreachComponent extends React.Component {
|
|||
});
|
||||
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>
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -18,11 +18,13 @@ const columns = [
|
|||
{
|
||||
Header: 'Events', id: 'events',
|
||||
accessor: x => {
|
||||
return <EventsButton finding_id={x.finding_id}
|
||||
const comp = <EventsButton finding_id={x.finding_id}
|
||||
latest_events={x.latest_events}
|
||||
oldest_events={x.oldest_events}
|
||||
event_count={x.event_count}
|
||||
exportFilename={'Events_' + x.test_key} />;
|
||||
comp.displayName = 'EventsButton_' + x.finding_id;
|
||||
return comp;
|
||||
},
|
||||
maxWidth: EVENTS_COLUMN_MAX_WIDTH
|
||||
},
|
||||
|
@ -34,7 +36,9 @@ const columns = [
|
|||
const pillarLabels = pillars.map((pillar) =>
|
||||
<PillarLabel key={pillar.name} pillar={pillar.name} status={pillar.status}/>
|
||||
);
|
||||
return <div style={{textAlign: 'center'}}>{pillarLabels}</div>;
|
||||
const comp = <div style={{textAlign: 'center'}}>{pillarLabels}</div>;
|
||||
comp.displayName = 'PillarsLabels';
|
||||
return comp;
|
||||
},
|
||||
maxWidth: PILLARS_COLUMN_MAX_WIDTH,
|
||||
style: {'whiteSpace': 'unset'}
|
||||
|
|
|
@ -13,7 +13,9 @@ const columns = [
|
|||
{
|
||||
Header: 'Status', id: 'status',
|
||||
accessor: x => {
|
||||
return <StatusLabel status={x.status} size="3x" showText={false}/>;
|
||||
const comp = <StatusLabel status={x.status} size="3x" showText={false}/>;
|
||||
comp.displayName = 'StatusLabel';
|
||||
return comp;
|
||||
},
|
||||
maxWidth: MAX_WIDTH_STATUS_COLUMN
|
||||
},
|
||||
|
@ -25,7 +27,9 @@ const columns = [
|
|||
Header: 'Monkey Tests', id: 'tests',
|
||||
style: {'whiteSpace': 'unset'}, // This enables word wrap
|
||||
accessor: x => {
|
||||
return <TestsStatus tests={x.tests}/>;
|
||||
const comp = <TestsStatus tests={x.tests}/>;
|
||||
comp.displayName = 'TestsStatus';
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -14,7 +14,7 @@ class ZeroTrustReportLegend extends Component {
|
|||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
open: false,
|
||||
open: false
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
|
@ -1,4 +1,4 @@
|
|||
import '../../styles/Checkbox.scss'
|
||||
import '../../styles/components/Checkbox.scss'
|
||||
import React from 'react';
|
||||
|
||||
class CheckboxComponent extends React.PureComponent {
|
||||
|
|
|
@ -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
|
|
@ -1,6 +1,5 @@
|
|||
import {Modal} from 'react-bootstrap';
|
||||
import React from 'react';
|
||||
import {GridLoader} from 'react-spinners';
|
||||
|
||||
|
||||
class MissingBinariesModal extends React.PureComponent {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
// Resolves object's path if it's specified in a dot notation.
|
||||
// (e.g. params: "firstLevel.secondLevel.property", myObject)
|
||||
export function resolveObjectPath(pathArray, obj) {
|
||||
return pathArray.reduce(function(prev, curr) {
|
||||
if(curr === '')
|
||||
return prev;
|
||||
else
|
||||
return prev ? prev[curr] : null;
|
||||
}, obj || self)
|
||||
}
|
|
@ -3,12 +3,14 @@
|
|||
@import '../../node_modules/bootstrap/scss/bootstrap';
|
||||
|
||||
// Imports that require variables
|
||||
@import './report/ReportPage.scss';
|
||||
@import './report/AttackReport.scss';
|
||||
@import './PreviewPane.scss';
|
||||
@import './ConfigurationPage.scss';
|
||||
@import './AuthPage.scss';
|
||||
@import './MonkeyRunPage.scss';
|
||||
@import 'pages/report/ReportPage.scss';
|
||||
@import 'pages/report/AttackReport.scss';
|
||||
@import 'pages/ConfigurationPage';
|
||||
@import 'pages/AuthPage';
|
||||
@import 'pages/MonkeyRunPage';
|
||||
@import 'components/InfoPane';
|
||||
@import 'components/PreviewPane';
|
||||
@import 'components/AdvancedMultiSelect';
|
||||
|
||||
|
||||
// Define custom elements after bootstrap import
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
.advanced-multi-select .choice-block.form-control {
|
||||
overflow: scroll;
|
||||
overflow-x: hidden;
|
||||
padding: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.advanced-multi-select svg {
|
||||
font-size: 1.15em;
|
||||
}
|
||||
|
||||
.advanced-multi-select .card-header {
|
||||
border: 1px solid $gray-300;
|
||||
border-bottom: none;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
padding-left: 0;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.advanced-multi-select .card-header button {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.advanced-multi-select .card-header .header-title {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.advanced-multi-select .choice-block .form-group {
|
||||
margin: 0;
|
||||
padding: 3px 0 0 0;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.advanced-multi-select .choice-block .form-group:hover {
|
||||
background-color: $gray-300;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.advanced-multi-select .choice-block .form-group button {
|
||||
margin: 0 3px 3px 12px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.advanced-multi-select .option-text {
|
||||
margin-left: 10px;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
.info-pane, .card {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.info-pane svg {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.info-pane .pane-title {
|
||||
margin: 10px 15px 3px 15px;
|
||||
color: $monkey-alt;
|
||||
}
|
||||
|
||||
.info-pane .pane-link {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.info-pane .pane-subtitle {
|
||||
margin: 0 15px;
|
||||
color: $gray-600;
|
||||
}
|
||||
|
||||
|
||||
.info-pane .pane-body {
|
||||
margin: 10px 15px;
|
||||
padding: 0;
|
||||
}
|
|
@ -2,6 +2,11 @@
|
|||
height: 50px !important;
|
||||
}
|
||||
|
||||
.config-nav .nav-link.tab-primary{
|
||||
color: $monkey-alt;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.config-nav .nav-item > a{
|
||||
color: $black;
|
||||
padding: 15px 10px 15px 10px;
|
||||
|
@ -25,3 +30,30 @@
|
|||
.config-form .form-group {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
.config-form div.card.errors {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.config-template-no-header > p {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.config-template-no-header > label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.config-form .form-group.field > label {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.config-field-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.field-description {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
Loading…
Reference in New Issue