forked from p15670423/monkey
Merge pull request #496 from VakarisZ/mitre_updates
ATT&CK UI/UX improvements
This commit is contained in:
commit
34c2ff6bb6
|
@ -48,7 +48,7 @@ script:
|
|||
- cd -
|
||||
- cd monkey_island/cc/ui
|
||||
- eslint ./src --quiet
|
||||
- JS_WARNINGS_AMOUNT_UPPER_LIMIT=29
|
||||
- JS_WARNINGS_AMOUNT_UPPER_LIMIT=37
|
||||
- eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT
|
||||
|
||||
notifications:
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import flask_restful
|
||||
import json
|
||||
from flask import jsonify, request
|
||||
from flask import jsonify, request, json, current_app
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.services.attack.attack_config import AttackConfig
|
||||
|
@ -11,7 +10,11 @@ __author__ = "VakarisZ"
|
|||
class AttackConfiguration(flask_restful.Resource):
|
||||
@jwt_required()
|
||||
def get(self):
|
||||
return jsonify(configuration=AttackConfig.get_config()['properties'])
|
||||
return current_app.response_class(json.dumps({"configuration": AttackConfig.get_config()},
|
||||
indent=None,
|
||||
separators=(",", ":"),
|
||||
sort_keys=False) + "\n",
|
||||
mimetype=current_app.config['JSONIFY_MIMETYPE'])
|
||||
|
||||
@jwt_required()
|
||||
def post(self):
|
||||
|
@ -22,7 +25,7 @@ class AttackConfiguration(flask_restful.Resource):
|
|||
config_json = json.loads(request.data)
|
||||
if 'reset_attack_matrix' in config_json:
|
||||
AttackConfig.reset_config()
|
||||
return jsonify(configuration=AttackConfig.get_config()['properties'])
|
||||
return jsonify(configuration=AttackConfig.get_config())
|
||||
else:
|
||||
AttackConfig.update_config({'properties': json.loads(request.data)})
|
||||
AttackConfig.apply_to_monkey_config()
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import flask_restful
|
||||
from flask import jsonify
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.services.attack.attack_report import AttackReportService
|
||||
from monkey_island.cc.services.attack.attack_schema import SCHEMA
|
||||
from flask import json, current_app
|
||||
|
||||
__author__ = "VakarisZ"
|
||||
|
||||
|
@ -10,4 +11,9 @@ class AttackReport(flask_restful.Resource):
|
|||
|
||||
@jwt_required()
|
||||
def get(self):
|
||||
return jsonify(AttackReportService.get_latest_report()['techniques'])
|
||||
response_content = {'techniques': AttackReportService.get_latest_report()['techniques'], 'schema': SCHEMA}
|
||||
return current_app.response_class(json.dumps(response_content,
|
||||
indent=None,
|
||||
separators=(",", ":"),
|
||||
sort_keys=False) + "\n",
|
||||
mimetype=current_app.config['JSONIFY_MIMETYPE'])
|
||||
|
|
|
@ -15,7 +15,7 @@ class AttackConfig(object):
|
|||
|
||||
@staticmethod
|
||||
def get_config():
|
||||
config = mongo.db.attack.find_one({'name': 'newconfig'})
|
||||
config = mongo.db.attack.find_one({'name': 'newconfig'})['properties']
|
||||
return config
|
||||
|
||||
@staticmethod
|
||||
|
@ -26,7 +26,7 @@ class AttackConfig(object):
|
|||
:return: Technique object or None if technique is not found
|
||||
"""
|
||||
attack_config = AttackConfig.get_config()
|
||||
for config_key, attack_type in list(attack_config['properties'].items()):
|
||||
for config_key, attack_type in list(attack_config.items()):
|
||||
for type_key, technique in list(attack_type['properties'].items()):
|
||||
if type_key == technique_id:
|
||||
return technique
|
||||
|
@ -169,7 +169,19 @@ class AttackConfig(object):
|
|||
"""
|
||||
attack_config = AttackConfig.get_config()
|
||||
techniques = {}
|
||||
for type_name, attack_type in list(attack_config['properties'].items()):
|
||||
for type_name, attack_type in list(attack_config.items()):
|
||||
for key, technique in list(attack_type['properties'].items()):
|
||||
techniques[key] = technique['value']
|
||||
return techniques
|
||||
|
||||
@staticmethod
|
||||
def get_techniques_for_report():
|
||||
"""
|
||||
:return: Format: {"T1110": {"selected": True, "type": "Credential Access", "T1075": ...}
|
||||
"""
|
||||
attack_config = AttackConfig.get_config()
|
||||
techniques = {}
|
||||
for type_name, attack_type in list(attack_config.items()):
|
||||
for key, technique in list(attack_type['properties'].items()):
|
||||
techniques[key] = {'selected': technique['value'], 'type': SCHEMA['properties'][type_name]['title']}
|
||||
return techniques
|
||||
|
|
|
@ -58,10 +58,11 @@ class AttackReportService:
|
|||
'name': REPORT_NAME
|
||||
}
|
||||
|
||||
for tech_id, value in list(AttackConfig.get_technique_values().items()):
|
||||
if value:
|
||||
for tech_id, tech_info in list(AttackConfig.get_techniques_for_report().items()):
|
||||
try:
|
||||
report['techniques'].update({tech_id: TECHNIQUES[tech_id].get_report_data()})
|
||||
technique_report_data = TECHNIQUES[tech_id].get_report_data()
|
||||
technique_report_data.update(tech_info)
|
||||
report['techniques'].update({tech_id: technique_report_data})
|
||||
except KeyError as e:
|
||||
LOG.error("Attack technique does not have it's report component added "
|
||||
"to attack report service. %s" % e)
|
||||
|
|
|
@ -2,64 +2,125 @@ SCHEMA = {
|
|||
"title": "ATT&CK configuration",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lateral_movement": {
|
||||
"title": "Lateral movement",
|
||||
"execution": {
|
||||
"title": "Execution",
|
||||
"type": "object",
|
||||
"link": "https://attack.mitre.org/tactics/TA0002/",
|
||||
"properties": {
|
||||
"T1210": {
|
||||
"title": "T1210 Exploitation of Remote services",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": False,
|
||||
"description": "Exploitation of a software vulnerability occurs when an adversary "
|
||||
"takes advantage of a programming error in a program, service, or within the "
|
||||
"operating system software or kernel itself to execute adversary-controlled code."
|
||||
},
|
||||
"T1075": {
|
||||
"title": "T1075 Pass the hash",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": False,
|
||||
"description": "Pass the hash (PtH) is a method of authenticating as a user without "
|
||||
"having access to the user's cleartext password."
|
||||
},
|
||||
"T1105": {
|
||||
"title": "T1105 Remote file copy",
|
||||
"T1059": {
|
||||
"title": "Command line interface",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"description": "Files may be copied from one system to another to stage "
|
||||
"adversary tools or other files over the course of an operation."
|
||||
"link": "https://attack.mitre.org/techniques/T1059",
|
||||
"description": "Adversaries may use command-line interfaces to interact with systems "
|
||||
"and execute other software during the course of an operation.",
|
||||
},
|
||||
"T1021": {
|
||||
"title": "T1021 Remote services",
|
||||
"T1129": {
|
||||
"title": "Execution through module load",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": False,
|
||||
"depends_on": ["T1110"],
|
||||
"description": "An adversary may use Valid Accounts to log into a service"
|
||||
" specifically designed to accept remote connections."
|
||||
"link": "https://attack.mitre.org/techniques/T1129",
|
||||
"description": "The Windows module loader can be instructed to load DLLs from arbitrary "
|
||||
"local paths and arbitrary Universal Naming Convention (UNC) network paths.",
|
||||
"depends_on": ["T1078", "T1003"]
|
||||
},
|
||||
"T1106": {
|
||||
"title": "Execution through API",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": False,
|
||||
"link": "https://attack.mitre.org/techniques/T1106",
|
||||
"description": "Adversary tools may directly use the Windows application "
|
||||
"programming interface (API) to execute binaries.",
|
||||
"depends_on": ["T1210"]
|
||||
},
|
||||
"T1086": {
|
||||
"title": "Powershell",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"link": "https://attack.mitre.org/techniques/T1086",
|
||||
"description": "Adversaries can use PowerShell to perform a number of actions,"
|
||||
" including discovery of information and execution of code.",
|
||||
},
|
||||
"T1064": {
|
||||
"title": "Scripting",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"link": "https://attack.mitre.org/techniques/T1064",
|
||||
"description": "Adversaries may use scripts to aid in operations and "
|
||||
"perform multiple actions that would otherwise be manual.",
|
||||
},
|
||||
"T1035": {
|
||||
"title": "Service execution",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": False,
|
||||
"link": "https://attack.mitre.org/techniques/T1035",
|
||||
"description": "Adversaries may execute a binary, command, or script via a method "
|
||||
"that interacts with Windows services, such as the Service Control Manager.",
|
||||
"depends_on": ["T1210"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"defence_evasion": {
|
||||
"title": "Defence evasion",
|
||||
"type": "object",
|
||||
"link": "https://attack.mitre.org/tactics/TA0005/",
|
||||
"properties": {
|
||||
"T1197": {
|
||||
"title": "BITS jobs",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"link": "https://attack.mitre.org/techniques/T1197",
|
||||
"description": "Adversaries may abuse BITS to download, execute, "
|
||||
"and even clean up after running malicious code."
|
||||
},
|
||||
"T1107": {
|
||||
"title": "File Deletion",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"link": "https://attack.mitre.org/techniques/T1107",
|
||||
"description": "Adversaries may remove files over the course of an intrusion "
|
||||
"to keep their footprint low or remove them at the end as part "
|
||||
"of the post-intrusion cleanup process."
|
||||
},
|
||||
"T1222": {
|
||||
"title": "File permissions modification",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"link": "https://attack.mitre.org/techniques/T1222",
|
||||
"description": "Adversaries may modify file permissions/attributes to evade intended DACLs."
|
||||
}
|
||||
}
|
||||
},
|
||||
"credential_access": {
|
||||
"title": "Credential access",
|
||||
"type": "object",
|
||||
"link": "https://attack.mitre.org/tactics/TA0006/",
|
||||
"properties": {
|
||||
"T1110": {
|
||||
"title": "T1110 Brute force",
|
||||
"title": "Brute force",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": False,
|
||||
"link": "https://attack.mitre.org/techniques/T1110",
|
||||
"description": "Adversaries may use brute force techniques to attempt access to accounts "
|
||||
"when passwords are unknown or when password hashes are obtained.",
|
||||
"depends_on": ["T1210", "T1021"]
|
||||
},
|
||||
"T1003": {
|
||||
"title": "T1003 Credential dumping",
|
||||
"title": "Credential dumping",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": False,
|
||||
"link": "https://attack.mitre.org/techniques/T1003",
|
||||
"description": "Mapped with T1078 Valid Accounts because both techniques require"
|
||||
" same credential harvesting modules. "
|
||||
"Credential dumping is the process of obtaining account login and password "
|
||||
|
@ -68,10 +129,11 @@ SCHEMA = {
|
|||
"depends_on": ["T1078"]
|
||||
},
|
||||
"T1145": {
|
||||
"title": "T1145 Private keys",
|
||||
"title": "Private keys",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": False,
|
||||
"link": "https://attack.mitre.org/techniques/T1145",
|
||||
"description": "Adversaries may gather private keys from compromised systems for use in "
|
||||
"authenticating to Remote Services like SSH or for use in decrypting "
|
||||
"other collected files such as email.",
|
||||
|
@ -79,120 +141,37 @@ SCHEMA = {
|
|||
}
|
||||
}
|
||||
},
|
||||
"defence_evasion": {
|
||||
"title": "Defence evasion",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"T1197": {
|
||||
"title": "T1197 BITS jobs",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"description": "Adversaries may abuse BITS to download, execute, "
|
||||
"and even clean up after running malicious code."
|
||||
},
|
||||
"T1107": {
|
||||
"title": "T1107 File Deletion",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"description": "Adversaries may remove files over the course of an intrusion "
|
||||
"to keep their footprint low or remove them at the end as part "
|
||||
"of the post-intrusion cleanup process."
|
||||
},
|
||||
"T1222": {
|
||||
"title": "T1222 File permissions modification",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"description": "Adversaries may modify file permissions/attributes to evade intended DACLs."
|
||||
}
|
||||
}
|
||||
},
|
||||
"execution": {
|
||||
"title": "Execution",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"T1035": {
|
||||
"title": "T1035 Service execution",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": False,
|
||||
"description": "Adversaries may execute a binary, command, or script via a method "
|
||||
"that interacts with Windows services, such as the Service Control Manager.",
|
||||
"depends_on": ["T1210"]
|
||||
},
|
||||
"T1129": {
|
||||
"title": "T1129 Execution through module load",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": False,
|
||||
"description": "The Windows module loader can be instructed to load DLLs from arbitrary "
|
||||
"local paths and arbitrary Universal Naming Convention (UNC) network paths.",
|
||||
"depends_on": ["T1078", "T1003"]
|
||||
},
|
||||
"T1106": {
|
||||
"title": "T1106 Execution through API",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": False,
|
||||
"description": "Adversary tools may directly use the Windows application "
|
||||
"programming interface (API) to execute binaries.",
|
||||
"depends_on": ["T1210"]
|
||||
},
|
||||
"T1059": {
|
||||
"title": "T1059 Command line interface",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"description": "Adversaries may use command-line interfaces to interact with systems "
|
||||
"and execute other software during the course of an operation.",
|
||||
},
|
||||
"T1086": {
|
||||
"title": "T1086 Powershell",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"description": "Adversaries can use PowerShell to perform a number of actions,"
|
||||
" including discovery of information and execution of code.",
|
||||
},
|
||||
"T1064": {
|
||||
"title": "T1064 Scripting",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"description": "Adversaries may use scripts to aid in operations and "
|
||||
"perform multiple actions that would otherwise be manual.",
|
||||
}
|
||||
}
|
||||
},
|
||||
"discovery": {
|
||||
"title": "Discovery",
|
||||
"type": "object",
|
||||
"link": "https://attack.mitre.org/tactics/TA0007/",
|
||||
"properties": {
|
||||
"T1018": {
|
||||
"title": "Remote System Discovery",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"link": "https://attack.mitre.org/techniques/T1018",
|
||||
"description": "Adversaries will likely attempt to get a listing of other systems by IP address, "
|
||||
"hostname, or other logical identifier on a network for lateral movement."
|
||||
},
|
||||
"T1082": {
|
||||
"title": "T1082 System information discovery",
|
||||
"title": "System information discovery",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": False,
|
||||
"link": "https://attack.mitre.org/techniques/T1082",
|
||||
"depends_on": ["T1016", "T1005"],
|
||||
"description": "An adversary may attempt to get detailed information about the "
|
||||
"operating system and hardware, including version, patches, hotfixes, "
|
||||
"service packs, and architecture."
|
||||
},
|
||||
"T1018": {
|
||||
"title": "T1018 Remote System Discovery",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"description": "Adversaries will likely attempt to get a listing of other systems by IP address, "
|
||||
"hostname, or other logical identifier on a network for lateral movement."
|
||||
},
|
||||
"T1016": {
|
||||
"title": "T1016 System network configuration discovery",
|
||||
"title": "System network configuration discovery",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": False,
|
||||
"link": "https://attack.mitre.org/techniques/T1016",
|
||||
"depends_on": ["T1005", "T1082"],
|
||||
"description": "Adversaries will likely look for details about the network configuration "
|
||||
"and settings of systems they access or through information discovery"
|
||||
|
@ -200,15 +179,62 @@ SCHEMA = {
|
|||
}
|
||||
}
|
||||
},
|
||||
"collection": {
|
||||
"title": "Collection",
|
||||
"lateral_movement": {
|
||||
"title": "Lateral movement",
|
||||
"type": "object",
|
||||
"link": "https://attack.mitre.org/tactics/TA0008/",
|
||||
"properties": {
|
||||
"T1005": {
|
||||
"title": "T1005 Data from local system",
|
||||
"T1210": {
|
||||
"title": "Exploitation of Remote services",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": False,
|
||||
"link": "https://attack.mitre.org/techniques/T1210",
|
||||
"description": "Exploitation of a software vulnerability occurs when an adversary "
|
||||
"takes advantage of a programming error in a program, service, or within the "
|
||||
"operating system software or kernel itself to execute adversary-controlled code."
|
||||
},
|
||||
"T1075": {
|
||||
"title": "Pass the hash",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": False,
|
||||
"link": "https://attack.mitre.org/techniques/T1075",
|
||||
"description": "Pass the hash (PtH) is a method of authenticating as a user without "
|
||||
"having access to the user's cleartext password."
|
||||
},
|
||||
"T1105": {
|
||||
"title": "Remote file copy",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"link": "https://attack.mitre.org/techniques/T1105",
|
||||
"description": "Files may be copied from one system to another to stage "
|
||||
"adversary tools or other files over the course of an operation."
|
||||
},
|
||||
"T1021": {
|
||||
"title": "Remote services",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": False,
|
||||
"link": "https://attack.mitre.org/techniques/T1021",
|
||||
"depends_on": ["T1110"],
|
||||
"description": "An adversary may use Valid Accounts to log into a service"
|
||||
" specifically designed to accept remote connections."
|
||||
}
|
||||
}
|
||||
},
|
||||
"collection": {
|
||||
"title": "Collection",
|
||||
"type": "object",
|
||||
"link": "https://attack.mitre.org/tactics/TA0009/",
|
||||
"properties": {
|
||||
"T1005": {
|
||||
"title": "Data from local system",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": False,
|
||||
"link": "https://attack.mitre.org/techniques/T1005",
|
||||
"depends_on": ["T1016", "T1082"],
|
||||
"description": "Sensitive data can be collected from local system sources, such as the file system "
|
||||
"or databases of information residing on the system prior to Exfiltration."
|
||||
|
@ -218,28 +244,32 @@ SCHEMA = {
|
|||
"command_and_control": {
|
||||
"title": "Command and Control",
|
||||
"type": "object",
|
||||
"link": "https://attack.mitre.org/tactics/TA0011/",
|
||||
"properties": {
|
||||
"T1065": {
|
||||
"title": "T1065 Uncommonly used port",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"description": "Adversaries may conduct C2 communications over a non-standard "
|
||||
"port to bypass proxies and firewalls that have been improperly configured."
|
||||
},
|
||||
"T1090": {
|
||||
"title": "T1090 Connection proxy",
|
||||
"title": "Connection proxy",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"link": "https://attack.mitre.org/techniques/T1090",
|
||||
"description": "A connection proxy is used to direct network traffic between systems "
|
||||
"or act as an intermediary for network communications."
|
||||
},
|
||||
"T1188": {
|
||||
"title": "T1188 Multi-hop proxy",
|
||||
"T1065": {
|
||||
"title": "Uncommonly used port",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"link": "https://attack.mitre.org/techniques/T1065",
|
||||
"description": "Adversaries may conduct C2 communications over a non-standard "
|
||||
"port to bypass proxies and firewalls that have been improperly configured."
|
||||
},
|
||||
"T1188": {
|
||||
"title": "Multi-hop proxy",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"link": "https://attack.mitre.org/techniques/T1188",
|
||||
"description": "To disguise the source of malicious traffic, "
|
||||
"adversaries may chain together multiple proxies."
|
||||
}
|
||||
|
@ -248,12 +278,14 @@ SCHEMA = {
|
|||
"exfiltration": {
|
||||
"title": "Exfiltration",
|
||||
"type": "object",
|
||||
"link": "https://attack.mitre.org/tactics/TA0010/",
|
||||
"properties": {
|
||||
"T1041": {
|
||||
"title": "T1041 Exfiltration Over Command and Control Channel",
|
||||
"title": "Exfiltration Over Command and Control Channel",
|
||||
"type": "bool",
|
||||
"value": True,
|
||||
"necessary": True,
|
||||
"link": "https://attack.mitre.org/techniques/T1041",
|
||||
"description": "Data exfiltration is performed over the Command and Control channel."
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -37,9 +37,9 @@
|
|||
"css-loader": "^1.0.1",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-loader": "^2.2.1",
|
||||
"eslint-plugin-react": "^7.15.1",
|
||||
"eslint-plugin-react": "^7.16.0",
|
||||
"file-loader": "^1.1.11",
|
||||
"glob": "^7.1.4",
|
||||
"glob": "^7.1.6",
|
||||
"html-loader": "^0.5.5",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"karma": "^3.1.4",
|
||||
|
@ -56,21 +56,25 @@
|
|||
"phantomjs-prebuilt": "^2.1.16",
|
||||
"react-addons-test-utils": "^15.6.2",
|
||||
"react-event-timeline": "^1.6.3",
|
||||
"react-hot-loader": "^4.12.14",
|
||||
"react-hot-loader": "^4.12.18",
|
||||
"rimraf": "^2.7.1",
|
||||
"style-loader": "^0.22.1",
|
||||
"url-loader": "^1.1.2",
|
||||
"webpack": "^4.41.0",
|
||||
"webpack-cli": "^3.3.9",
|
||||
"webpack-dev-server": "^3.8.1"
|
||||
"webpack": "^4.41.2",
|
||||
"webpack-cli": "^3.3.10",
|
||||
"webpack-dev-server": "^3.9.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/core": "^10.0.17",
|
||||
"@emotion/core": "^10.0.22",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.25",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.11.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.11.2",
|
||||
"@fortawesome/react-fontawesome": "^0.1.7",
|
||||
"@kunukn/react-collapse": "^1.2.7",
|
||||
"bootstrap": "3.4.1",
|
||||
"bootstrap": "^3.4.1",
|
||||
"classnames": "^2.2.6",
|
||||
"core-js": "^2.6.9",
|
||||
"d3": "^5.11.0",
|
||||
"core-js": "^2.6.10",
|
||||
"d3": "^5.14.1",
|
||||
"downloadjs": "^1.4.7",
|
||||
"fetch": "^1.1.0",
|
||||
"file-saver": "^2.0.2",
|
||||
|
@ -80,24 +84,24 @@
|
|||
"moment": "^2.24.0",
|
||||
"node-sass": "^4.13.0",
|
||||
"normalize.css": "^8.0.0",
|
||||
"npm": "^6.11.3",
|
||||
"npm": "^6.13.1",
|
||||
"pluralize": "^7.0.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"rainge": "^1.0.1",
|
||||
"rc-progress": "^2.5.2",
|
||||
"react": "^16.10.1",
|
||||
"react": "^16.12.0",
|
||||
"react-bootstrap": "^0.32.4",
|
||||
"react-copy-to-clipboard": "^5.0.1",
|
||||
"react-copy-to-clipboard": "^5.0.2",
|
||||
"react-data-components": "^1.2.0",
|
||||
"react-desktop-notification": "^1.0.9",
|
||||
"react-dimensions": "^1.3.0",
|
||||
"react-dom": "^16.10.1",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-fa": "^5.0.0",
|
||||
"react-filepond": "^7.0.1",
|
||||
"react-graph-vis": "^1.0.2",
|
||||
"react-graph-vis": "^1.0.5",
|
||||
"react-json-tree": "^0.11.2",
|
||||
"react-jsonschema-form": "^1.8.0",
|
||||
"react-redux": "^5.1.1",
|
||||
"react-redux": "^5.1.2",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react-spinners": "^0.5.13",
|
||||
"react-table": "^6.10.3",
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react';
|
||||
import {BrowserRouter as Router, NavLink, Redirect, Route} from 'react-router-dom';
|
||||
import {BrowserRouter as Router, NavLink, Redirect, Route, Switch} from 'react-router-dom';
|
||||
import {Col, Grid, Row} from 'react-bootstrap';
|
||||
import {Icon} from 'react-fa';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faCheck, faUndo } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import RunServerPage from 'components/pages/RunServerPage';
|
||||
import ConfigurePage from 'components/pages/ConfigurePage';
|
||||
|
@ -10,7 +11,6 @@ import MapPage from 'components/pages/MapPage';
|
|||
import TelemetryPage from 'components/pages/TelemetryPage';
|
||||
import StartOverPage from 'components/pages/StartOverPage';
|
||||
import ReportPage from 'components/pages/ReportPage';
|
||||
import ZeroTrustReportPage from 'components/pages/ZeroTrustReportPage';
|
||||
import LicensePage from 'components/pages/LicensePage';
|
||||
import AuthComponent from 'components/AuthComponent';
|
||||
import LoginPageComponent from 'components/pages/LoginPage';
|
||||
|
@ -82,6 +82,13 @@ class AppComponent extends AuthComponent {
|
|||
}
|
||||
};
|
||||
|
||||
redirectTo = (userPath, targetPath) => {
|
||||
let pathQuery = new RegExp(userPath+'[\/]?$', 'g');
|
||||
if(window.location.pathname.match(pathQuery)){
|
||||
return <Redirect to={{pathname: targetPath}}/>
|
||||
}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
@ -115,61 +122,57 @@ class AppComponent extends AuthComponent {
|
|||
<Router>
|
||||
<Grid fluid={true}>
|
||||
<Row>
|
||||
<Col sm={3} md={2} className="sidebar">
|
||||
<div className="header">
|
||||
<Col sm={3} md={2} className='sidebar'>
|
||||
<div className='header'>
|
||||
<img src={logoImage} style={{width: '5vw', margin: '15px'}}/>
|
||||
<img src={infectionMonkeyImage} style={{width: '15vw'}} alt="Infection Monkey"/>
|
||||
<img src={infectionMonkeyImage} style={{width: '15vw'}} alt='Infection Monkey'/>
|
||||
</div>
|
||||
|
||||
<ul className="navigation">
|
||||
<ul className='navigation'>
|
||||
<li>
|
||||
<NavLink to="/" exact={true}>
|
||||
<span className="number">1.</span>
|
||||
<NavLink to='/' exact={true}>
|
||||
<span className='number'>1.</span>
|
||||
Run Monkey Island Server
|
||||
{this.state.completedSteps.run_server ?
|
||||
<Icon name="check" className="pull-right checkmark text-success"/>
|
||||
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark text-success'/>
|
||||
: ''}
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink to="/run-monkey">
|
||||
<span className="number">2.</span>
|
||||
<NavLink to='/run-monkey'>
|
||||
<span className='number'>2.</span>
|
||||
Run Monkey
|
||||
{this.state.completedSteps.run_monkey ?
|
||||
<Icon name="check" className="pull-right checkmark text-success"/>
|
||||
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark text-success'/>
|
||||
: ''}
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink to="/infection/map">
|
||||
<span className="number">3.</span>
|
||||
<NavLink to='/infection/map'>
|
||||
<span className='number'>3.</span>
|
||||
Infection Map
|
||||
{this.state.completedSteps.infection_done ?
|
||||
<Icon name="check" className="pull-right checkmark text-success"/>
|
||||
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark text-success'/>
|
||||
: ''}
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink to="/report/security">
|
||||
<span className="number">4.</span>
|
||||
Security Report
|
||||
<NavLink to='/report/security'
|
||||
isActive={(match, location) => {
|
||||
return (location.pathname === '/report/attack'
|
||||
|| location.pathname === '/report/zeroTrust'
|
||||
|| location.pathname === '/report/security')
|
||||
}}>
|
||||
<span className='number'>4.</span>
|
||||
Security Reports
|
||||
{this.state.completedSteps.report_done ?
|
||||
<Icon name="check" className="pull-right checkmark text-success"/>
|
||||
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark text-success'/>
|
||||
: ''}
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink to="/report/zero_trust">
|
||||
<span className="number">5.</span>
|
||||
Zero Trust Report
|
||||
{this.state.completedSteps.report_done ?
|
||||
<Icon name="check" className="pull-right checkmark text-success"/>
|
||||
: ''}
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink to="/start-over">
|
||||
<span className="number"><i className="fa fa-undo" style={{'marginLeft': '-1px'}}/></span>
|
||||
<NavLink to='/start-over'>
|
||||
<span className='number'><FontAwesomeIcon icon={faUndo} style={{'marginLeft': '-1px'}}/></span>
|
||||
Start Over
|
||||
</NavLink>
|
||||
</li>
|
||||
|
@ -177,23 +180,23 @@ class AppComponent extends AuthComponent {
|
|||
|
||||
<hr/>
|
||||
<ul>
|
||||
<li><NavLink to="/configure">Configuration</NavLink></li>
|
||||
<li><NavLink to="/infection/telemetry">Log</NavLink></li>
|
||||
<li><NavLink to='/configure'>Configuration</NavLink></li>
|
||||
<li><NavLink to='/infection/telemetry'>Log</NavLink></li>
|
||||
</ul>
|
||||
|
||||
<hr/>
|
||||
<div className="guardicore-link text-center" style={{'marginBottom': '0.5em'}}>
|
||||
<div className='guardicore-link text-center' style={{'marginBottom': '0.5em'}}>
|
||||
<span>Powered by</span>
|
||||
<a href="http://www.guardicore.com" target="_blank">
|
||||
<img src={guardicoreLogoImage} alt="GuardiCore"/>
|
||||
<a href='http://www.guardicore.com' target='_blank'>
|
||||
<img src={guardicoreLogoImage} alt='GuardiCore'/>
|
||||
</a>
|
||||
</div>
|
||||
<div className="license-link text-center">
|
||||
<NavLink to="/license">License</NavLink>
|
||||
<div className='license-link text-center'>
|
||||
<NavLink to='/license'>License</NavLink>
|
||||
</div>
|
||||
<VersionComponent/>
|
||||
</Col>
|
||||
<Col sm={9} md={10} smOffset={3} mdOffset={2} className="main">
|
||||
<Col sm={9} md={10} smOffset={3} mdOffset={2} className='main'>
|
||||
<Route path='/login' render={() => (<LoginPageComponent onStatusChange={this.updateStatus}/>)}/>
|
||||
{this.renderRoute('/', <RunServerPage onStatusChange={this.updateStatus}/>, true)}
|
||||
{this.renderRoute('/configure', <ConfigurePage onStatusChange={this.updateStatus}/>)}
|
||||
|
@ -201,8 +204,12 @@ class AppComponent extends AuthComponent {
|
|||
{this.renderRoute('/infection/map', <MapPage onStatusChange={this.updateStatus}/>)}
|
||||
{this.renderRoute('/infection/telemetry', <TelemetryPage onStatusChange={this.updateStatus}/>)}
|
||||
{this.renderRoute('/start-over', <StartOverPage onStatusChange={this.updateStatus}/>)}
|
||||
{this.renderRoute('/report/security', <ReportPage onStatusChange={this.updateStatus}/>)}
|
||||
{this.renderRoute(reportZeroTrustRoute, <ZeroTrustReportPage onStatusChange={this.updateStatus}/>)}
|
||||
{this.redirectTo('/report', '/report/security')}
|
||||
<Switch>
|
||||
{this.renderRoute('/report/security', <ReportPage/>)}
|
||||
{this.renderRoute('/report/attack', <ReportPage/>)}
|
||||
{this.renderRoute('/report/zeroTrust', <ReportPage/>)}
|
||||
</Switch>
|
||||
{this.renderRoute('/license', <LicensePage onStatusChange={this.updateStatus}/>)}
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -7,7 +7,11 @@ import 'filepond/dist/filepond.min.css';
|
|||
import '../../styles/Tooltip.scss';
|
||||
import {Col} from 'react-bootstrap';
|
||||
|
||||
class MatrixComponent extends AuthComponent {
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faCircle as faCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faCircle as faCircleThin } from '@fortawesome/free-regular-svg-icons';
|
||||
|
||||
class ConfigMatrixComponent extends AuthComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {lastAction: 'none'}
|
||||
|
@ -37,12 +41,12 @@ class MatrixComponent extends AuthComponent {
|
|||
if (i <= Object.keys(techType.properties).length) {
|
||||
rowColumn.technique = Object.values(techType.properties)[i];
|
||||
if (rowColumn.technique) {
|
||||
rowColumn.technique.name = Object.keys(techType.properties)[i]
|
||||
rowColumn.technique.name = Object.keys(techType.properties)[i];
|
||||
}
|
||||
} else {
|
||||
rowColumn.technique = null
|
||||
rowColumn.technique = null;
|
||||
}
|
||||
row[rowColumn.techName] = rowColumn
|
||||
row[rowColumn.techName] = rowColumn;
|
||||
});
|
||||
techniques.push(row)
|
||||
}
|
||||
|
@ -77,8 +81,8 @@ class MatrixComponent extends AuthComponent {
|
|||
|
||||
getTableData = (config) => {
|
||||
let configCopy = JSON.parse(JSON.stringify(config));
|
||||
let maxTechniques = MatrixComponent.findMaxTechniques(Object.values(configCopy));
|
||||
let matrixTableData = MatrixComponent.parseTechniques(Object.values(configCopy), maxTechniques);
|
||||
let maxTechniques = ConfigMatrixComponent.findMaxTechniques(Object.values(configCopy));
|
||||
let matrixTableData = ConfigMatrixComponent.parseTechniques(Object.values(configCopy), maxTechniques);
|
||||
let columns = this.getColumns(matrixTableData);
|
||||
return {'columns': columns, 'matrixTableData': matrixTableData, 'maxTechniques': maxTechniques}
|
||||
};
|
||||
|
@ -87,15 +91,15 @@ class MatrixComponent extends AuthComponent {
|
|||
return (
|
||||
<div id="header" className="row justify-content-between attack-legend">
|
||||
<Col xs={4}>
|
||||
<i className="fa fa-circle-thin icon-unchecked"></i>
|
||||
<span> - Dissabled</span>
|
||||
<FontAwesomeIcon icon={faCircleThin} className="icon-unchecked"/>
|
||||
<span> - Disabled</span>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<i className="fa fa-circle icon-checked"></i>
|
||||
<FontAwesomeIcon icon={faCircle} className="icon-checked"/>
|
||||
<span> - Enabled</span>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<i className="fa fa-circle icon-mandatory"></i>
|
||||
<FontAwesomeIcon icon={faCircle} className="icon-mandatory"/>
|
||||
<span> - Mandatory</span>
|
||||
</Col>
|
||||
</div>)
|
||||
|
@ -116,4 +120,4 @@ class MatrixComponent extends AuthComponent {
|
|||
}
|
||||
}
|
||||
|
||||
export default MatrixComponent;
|
||||
export default ConfigMatrixComponent;
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import '../../report-components/security/StolenPasswords'
|
||||
import StolenPasswordsComponent from '../../report-components/security/StolenPasswords';
|
||||
import {ScanStatus} from './Helpers'
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachineFromSystemData, ScanStatus} from './Helpers';
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachineFromSystemData, renderUsageFields, ScanStatus} from './Helpers'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachineFromSystemData, renderMachine, ScanStatus} from './Helpers'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachine, ScanStatus} from './Helpers'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {getUsageColumns} from './Helpers'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {ScanStatus} from './Helpers';
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachine, ScanStatus} from './Helpers'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {getUsageColumns} from './Helpers'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
|
||||
|
||||
class T1065 extends React.Component {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachine, ScanStatus} from './Helpers'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachineFromSystemData, renderUsageFields, ScanStatus} from './Helpers'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachine, ScanStatus} from './Helpers'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachineFromSystemData, ScanStatus} from './Helpers'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {ScanStatus} from './Helpers'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {getUsageColumns} from './Helpers'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachineFromSystemData, ScanStatus} from './Helpers'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachine, ScanStatus} from './Helpers'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {getUsageColumns} from './Helpers';
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachineFromSystemData, ScanStatus} from './Helpers'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachineFromSystemData, ScanStatus} from './Helpers'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachine} from './Helpers'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachine} from './Helpers'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import '../../../styles/Collapse.scss'
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachine, ScanStatus} from './Helpers'
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import {Icon} from 'react-fa';
|
||||
import Toggle from 'react-toggle';
|
||||
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
|
||||
import download from 'downloadjs'
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import {Icon} from 'react-fa';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faHandPointLeft } from '@fortawesome/free-solid-svg-icons'
|
||||
import Toggle from 'react-toggle';
|
||||
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
|
||||
import download from 'downloadjs'
|
||||
|
@ -273,7 +274,7 @@ class PreviewPaneComponent extends AuthComponent {
|
|||
<div className="preview-pane">
|
||||
{!info ?
|
||||
<span>
|
||||
<Icon name="hand-o-left" style={{'marginRight': '0.5em'}}/>
|
||||
<FontAwesomeIcon icon={faHandPointLeft} style={{'marginRight': '0.5em'}}/>
|
||||
Select an item on the map for a detailed look
|
||||
</span>
|
||||
:
|
||||
|
|
|
@ -5,7 +5,7 @@ import FileSaver from 'file-saver';
|
|||
import AuthComponent from '../AuthComponent';
|
||||
import {FilePond} from 'react-filepond';
|
||||
import 'filepond/dist/filepond.min.css';
|
||||
import MatrixComponent from '../attack/MatrixComponent';
|
||||
import ConfigMatrixComponent from '../attack/ConfigMatrixComponent';
|
||||
|
||||
const ATTACK_URL = '/api/attack';
|
||||
const CONFIG_URL = '/api/configuration/island';
|
||||
|
@ -450,7 +450,7 @@ class ConfigurePageComponent extends AuthComponent {
|
|||
}
|
||||
|
||||
renderMatrix = () => {
|
||||
return (<MatrixComponent configuration={this.state.attackConfig}
|
||||
return (<ConfigMatrixComponent configuration={this.state.attackConfig}
|
||||
submit={this.componentDidMount}
|
||||
reset={this.resetConfig}
|
||||
change={this.attackTechniqueChange}/>)
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react';
|
||||
import {Col, Modal} from 'react-bootstrap';
|
||||
import {Link} from 'react-router-dom';
|
||||
import {Icon} from 'react-fa';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faStopCircle, faMinus } from '@fortawesome/free-solid-svg-icons'
|
||||
import InfMapPreviewPaneComponent from 'components/map/preview-pane/InfMapPreviewPane';
|
||||
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
||||
import {options, edgeGroupToColor} from 'components/map/MapOptions';
|
||||
|
@ -157,13 +158,13 @@ class MapPageComponent extends AuthComponent {
|
|||
<Col xs={8}>
|
||||
<div className="map-legend">
|
||||
<b>Legend: </b>
|
||||
<span>Exploit <i className="fa fa-lg fa-minus" style={{color: '#cc0200'}}/></span>
|
||||
<span>Exploit <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#cc0200'}}/></span>
|
||||
<b style={{color: '#aeaeae'}}> | </b>
|
||||
<span>Scan <i className="fa fa-lg fa-minus" style={{color: '#ff9900'}}/></span>
|
||||
<span>Scan <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#ff9900'}}/></span>
|
||||
<b style={{color: '#aeaeae'}}> | </b>
|
||||
<span>Tunnel <i className="fa fa-lg fa-minus" style={{color: '#0158aa'}}/></span>
|
||||
<span>Tunnel <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#0158aa'}}/></span>
|
||||
<b style={{color: '#aeaeae'}}> | </b>
|
||||
<span>Island Communication <i className="fa fa-lg fa-minus" style={{color: '#a9aaa9'}}/></span>
|
||||
<span>Island Communication <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#a9aaa9'}}/></span>
|
||||
</div>
|
||||
{this.renderTelemetryConsole()}
|
||||
<div style={{height: '80vh'}}>
|
||||
|
@ -180,7 +181,7 @@ class MapPageComponent extends AuthComponent {
|
|||
Telemetry</Link>
|
||||
<button onClick={() => this.setState({showKillDialog: true})} className="btn btn-danger pull-right"
|
||||
style={{'width': '48%'}}>
|
||||
<Icon name="stop-circle" style={{'marginRight': '0.5em'}}/>
|
||||
<FontAwesomeIcon icon={faStopCircle} style={{'marginRight': '0.5em'}}/>
|
||||
Kill All Monkeys
|
||||
</button>
|
||||
</div>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,7 +4,9 @@ import {Button, Col, Well, Nav, NavItem, Collapse} from 'react-bootstrap';
|
|||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import GridLoader from 'react-spinners/GridLoader';
|
||||
|
||||
import {Icon} from 'react-fa';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faClipboard, faCheck, faSync } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import {Link} from 'react-router-dom';
|
||||
import AuthComponent from '../AuthComponent';
|
||||
import AwsRunTable from '../run-monkey/AwsRunTable';
|
||||
|
@ -147,7 +149,7 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
<div style={{'overflow': 'auto', 'padding': '0.5em'}}>
|
||||
<CopyToClipboard text={cmdText} className="pull-right btn-sm">
|
||||
<Button style={{margin: '-0.5em'}} title="Copy to Clipboard">
|
||||
<Icon name="clipboard"/>
|
||||
<FontAwesomeIcon icon={faClipboard}/>
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
<code>{cmdText}</code>
|
||||
|
@ -170,9 +172,9 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
|
||||
static renderIconByState(state) {
|
||||
if (state === 'running') {
|
||||
return <Icon name="check" className="text-success" style={{'marginLeft': '5px'}}/>
|
||||
return (<FontAwesomeIcon icon={faCheck} className="text-success" style={{'marginLeft': '5px'}}/>)
|
||||
} else if (state === 'installing') {
|
||||
return <Icon name="refresh" className="text-success" style={{'marginLeft': '5px'}}/>
|
||||
return (<FontAwesomeIcon icon={faSync} className="text-success" style={{'marginLeft': '5px'}}/>)
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
|
@ -272,7 +274,7 @@ class RunMonkeyPageComponent extends AuthComponent {
|
|||
className={'btn btn-default btn-md center-block'}
|
||||
disabled={this.state.awsClicked}>
|
||||
Run on selected machines
|
||||
{this.state.awsClicked ? <Icon name="refresh" className="text-success" style={{'marginLeft': '5px'}}/> : null}
|
||||
{this.state.awsClicked ? <FontAwesomeIcon icon={faSync} className="text-success" style={{'marginLeft': '5px'}}/> : null}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,133 +0,0 @@
|
|||
import React, {Fragment} from 'react';
|
||||
import {Col} from 'react-bootstrap';
|
||||
import AuthComponent from '../AuthComponent';
|
||||
import ReportHeader, {ReportTypes} from '../report-components/common/ReportHeader';
|
||||
import ReportLoader from '../report-components/common/ReportLoader';
|
||||
import MustRunMonkeyWarning from '../report-components/common/MustRunMonkeyWarning';
|
||||
import PrintReportButton from '../report-components/common/PrintReportButton';
|
||||
import {extractExecutionStatusFromServerResponse} from '../report-components/common/ExecutionStatus';
|
||||
import SummarySection from '../report-components/zerotrust/SummarySection';
|
||||
import FindingsSection from '../report-components/zerotrust/FindingsSection';
|
||||
import PrinciplesSection from '../report-components/zerotrust/PrinciplesSection';
|
||||
|
||||
class ZeroTrustReportPageComponent extends AuthComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
allMonkeysAreDead: false,
|
||||
runStarted: true
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updatePageState();
|
||||
const refreshInterval = setInterval(this.updatePageState, 60000);
|
||||
this.setState(
|
||||
{refreshDataIntervalHandler: refreshInterval}
|
||||
)
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.state.refreshDataIntervalHandler);
|
||||
}
|
||||
|
||||
updateMonkeysRunning = () => {
|
||||
return this.authFetch('/api')
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
this.setState(extractExecutionStatusFromServerResponse(res));
|
||||
return res;
|
||||
});
|
||||
};
|
||||
|
||||
updatePageState = () => {
|
||||
this.updateMonkeysRunning().then(res => this.getZeroTrustReportFromServer(res));
|
||||
};
|
||||
|
||||
render() {
|
||||
let content;
|
||||
if (this.state.runStarted) {
|
||||
content = this.generateReportContent();
|
||||
} else {
|
||||
content = <MustRunMonkeyWarning/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Col xs={12} lg={10}>
|
||||
<h1 className="page-title no-print">5. Zero Trust Report</h1>
|
||||
<div style={{'fontSize': '1.2em'}}>
|
||||
{content}
|
||||
</div>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
generateReportContent() {
|
||||
let content;
|
||||
|
||||
if (this.stillLoadingDataFromServer()) {
|
||||
content = <ReportLoader loading={true}/>;
|
||||
} else {
|
||||
content = <div id="MainContentSection">
|
||||
<SummarySection allMonkeysAreDead={this.state.allMonkeysAreDead} pillars={this.state.pillars}/>
|
||||
<PrinciplesSection principles={this.state.principles}
|
||||
pillarsToStatuses={this.state.pillars.pillarsToStatuses}/>
|
||||
<FindingsSection pillarsToStatuses={this.state.pillars.pillarsToStatuses} findings={this.state.findings}/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
<PrintReportButton onClick={() => {
|
||||
print();
|
||||
}}/>
|
||||
</div>
|
||||
<div className="report-page">
|
||||
<ReportHeader report_type={ReportTypes.zeroTrust}/>
|
||||
<hr/>
|
||||
{content}
|
||||
</div>
|
||||
<div style={{marginTop: '20px'}}>
|
||||
<PrintReportButton onClick={() => {
|
||||
print();
|
||||
}}/>
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
stillLoadingDataFromServer() {
|
||||
return typeof this.state.findings === 'undefined'
|
||||
|| typeof this.state.pillars === 'undefined'
|
||||
|| typeof this.state.principles === 'undefined';
|
||||
}
|
||||
|
||||
getZeroTrustReportFromServer() {
|
||||
this.authFetch('/api/report/zero_trust/findings')
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
this.setState({
|
||||
findings: res
|
||||
});
|
||||
});
|
||||
this.authFetch('/api/report/zero_trust/principles')
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
this.setState({
|
||||
principles: res
|
||||
});
|
||||
});
|
||||
this.authFetch('/api/report/zero_trust/pillars')
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
this.setState({
|
||||
pillars: res
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default ZeroTrustReportPageComponent;
|
|
@ -0,0 +1,166 @@
|
|||
import React from 'react';
|
||||
import {Col, Button} from 'react-bootstrap';
|
||||
import '../../styles/Collapse.scss';
|
||||
import '../../styles/report/AttackReport.scss';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import {faCircle, faEye, faEyeSlash, faRadiation} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import ReportHeader, {ReportTypes} from './common/ReportHeader';
|
||||
import {ScanStatus} from '../attack/techniques/Helpers';
|
||||
import Matrix from './attack/ReportMatrixComponent';
|
||||
import SelectedTechnique from './attack/SelectedTechnique';
|
||||
import TechniqueDropdowns from './attack/TechniqueDropdowns';
|
||||
import ReportLoader from './common/ReportLoader';
|
||||
|
||||
const techComponents = getAllAttackModules();
|
||||
|
||||
function getAllAttackModules() {
|
||||
let context = require.context('../attack/techniques/', false, /\.js$/);
|
||||
let obj = {};
|
||||
context.keys().forEach(function (key) {
|
||||
let techName = key.replace(/\.js/, '');
|
||||
techName = String(techName.replace(/\.\//, ''));
|
||||
obj[techName] = context(key).default;
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
|
||||
class AttackReport extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedTechnique: false,
|
||||
collapseOpen: ''
|
||||
};
|
||||
if (typeof this.props.report.schema !== 'undefined' && typeof this.props.report.techniques !== 'undefined'){
|
||||
this.state['schema'] = this.props.report['schema'];
|
||||
this.state['techniques'] = AttackReport.addLinksToTechniques(this.props.report['schema'], this.props.report['techniques']);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.report !== prevProps.report) {
|
||||
this.setState({schema: this.props.report['schema'],
|
||||
techniques: AttackReport.addLinksToTechniques(this.props.report['schema'], this.props.report['techniques'])})
|
||||
}
|
||||
}
|
||||
|
||||
onTechniqueSelect = (technique, value) => {
|
||||
let selectedTechnique = this.getTechniqueByTitle(technique);
|
||||
if (selectedTechnique === false){
|
||||
return;
|
||||
}
|
||||
this.setState({selectedTechnique: selectedTechnique.tech_id})
|
||||
};
|
||||
|
||||
static getComponentClass(tech_id, techniques) {
|
||||
switch (techniques[tech_id].status) {
|
||||
case ScanStatus.SCANNED:
|
||||
return 'collapse-info';
|
||||
case ScanStatus.USED:
|
||||
return 'collapse-danger';
|
||||
default:
|
||||
return 'collapse-default';
|
||||
}
|
||||
}
|
||||
|
||||
static getStatusIcon(tech_id, techniques){
|
||||
switch (techniques[tech_id].status){
|
||||
case ScanStatus.SCANNED:
|
||||
return <FontAwesomeIcon icon={faEye} className={'technique-status-icon'}/>;
|
||||
case ScanStatus.USED:
|
||||
return <FontAwesomeIcon icon={faRadiation} className={'technique-status-icon'}/>;
|
||||
default:
|
||||
return <FontAwesomeIcon icon={faEyeSlash} className={'technique-status-icon'}/>;
|
||||
}
|
||||
}
|
||||
|
||||
renderLegend() {
|
||||
return (<div id='header' className='row justify-content-between attack-legend'>
|
||||
<Col xs={4}>
|
||||
<FontAwesomeIcon icon={faCircle} className='icon-default'/>
|
||||
<span> - Not scanned</span>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<FontAwesomeIcon icon={faCircle} className='icon-info'/>
|
||||
<span> - Scanned</span>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<FontAwesomeIcon icon={faCircle} className='icon-danger'/>
|
||||
<span> - Used</span>
|
||||
</Col>
|
||||
</div>)
|
||||
}
|
||||
|
||||
generateReportContent() {
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
This report shows information about
|
||||
<Button bsStyle={'link'}
|
||||
href={'https://attack.mitre.org/'}
|
||||
bsSize={'lg'}
|
||||
className={'attack-link'}
|
||||
target={'_blank'}>
|
||||
Mitre ATT&CK™
|
||||
</Button>
|
||||
techniques used by Infection Monkey.
|
||||
</p>
|
||||
{this.renderLegend()}
|
||||
<Matrix techniques={this.state.techniques} schema={this.state.schema} onClick={this.onTechniqueSelect}/>
|
||||
<SelectedTechnique techComponents={techComponents}
|
||||
techniques={this.state.techniques}
|
||||
selected={this.state.selectedTechnique}/>
|
||||
<TechniqueDropdowns techniques={this.state.techniques}
|
||||
techComponents={techComponents}
|
||||
schema={this.state.schema}/>
|
||||
<br/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
getTechniqueByTitle(title){
|
||||
for (const tech_id in this.state.techniques){
|
||||
if (! this.state.techniques.hasOwnProperty(tech_id)) {return false;}
|
||||
let technique = this.state.techniques[tech_id];
|
||||
if (technique.title === title){
|
||||
technique['tech_id'] = tech_id;
|
||||
return technique
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static addLinksToTechniques(schema, techniques){
|
||||
schema = schema.properties;
|
||||
for(const type in schema){
|
||||
if (! schema.hasOwnProperty(type)) {return false;}
|
||||
let typeTechniques = schema[type].properties;
|
||||
for(const tech_id in typeTechniques){
|
||||
if (! typeTechniques.hasOwnProperty(tech_id)) {return false;}
|
||||
if (typeTechniques[tech_id] !== undefined){
|
||||
techniques[tech_id]['link'] = typeTechniques[tech_id].link
|
||||
}
|
||||
}
|
||||
}
|
||||
return techniques
|
||||
}
|
||||
|
||||
render() {
|
||||
let content = {};
|
||||
if (typeof this.state.schema === 'undefined' || typeof this.state.techniques === 'undefined') {
|
||||
content = <ReportLoader/>;
|
||||
} else {
|
||||
content = <div> {this.generateReportContent()}</div>;
|
||||
}
|
||||
return (
|
||||
<div id='attack' className='attack-report report-page'>
|
||||
<ReportHeader report_type={ReportTypes.attack}/>
|
||||
<hr/>
|
||||
{content}
|
||||
</div>)
|
||||
}
|
||||
}
|
||||
|
||||
export default AttackReport;
|
|
@ -0,0 +1,935 @@
|
|||
import React, {Fragment} from 'react';
|
||||
import BreachedServers from 'components/report-components/security/BreachedServers';
|
||||
import ScannedServers from 'components/report-components/security/ScannedServers';
|
||||
import PostBreach from 'components/report-components/security/PostBreach';
|
||||
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
||||
import {edgeGroupToColor, options} from 'components/map/MapOptions';
|
||||
import StolenPasswords from 'components/report-components/security/StolenPasswords';
|
||||
import CollapsibleWellComponent from 'components/report-components/security/CollapsibleWell';
|
||||
import {Line} from 'rc-progress';
|
||||
import AuthComponent from '../AuthComponent';
|
||||
import PassTheHashMapPageComponent from '../pages/PassTheHashMapPage';
|
||||
import StrongUsers from 'components/report-components/security/StrongUsers';
|
||||
import ReportHeader, {ReportTypes} from './common/ReportHeader';
|
||||
import ReportLoader from './common/ReportLoader';
|
||||
import SecurityIssuesGlance from './common/SecurityIssuesGlance';
|
||||
import PrintReportButton from './common/PrintReportButton';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faMinus } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
let guardicoreLogoImage = require('../../images/guardicore-logo.png');
|
||||
|
||||
|
||||
class ReportPageComponent extends AuthComponent {
|
||||
|
||||
Issue =
|
||||
{
|
||||
WEAK_PASSWORD: 0,
|
||||
STOLEN_CREDS: 1,
|
||||
ELASTIC: 2,
|
||||
SAMBACRY: 3,
|
||||
SHELLSHOCK: 4,
|
||||
CONFICKER: 5,
|
||||
AZURE: 6,
|
||||
STOLEN_SSH_KEYS: 7,
|
||||
STRUTS2: 8,
|
||||
WEBLOGIC: 9,
|
||||
HADOOP: 10,
|
||||
PTH_CRIT_SERVICES_ACCESS: 11,
|
||||
MSSQL: 12,
|
||||
VSFTPD: 13
|
||||
};
|
||||
|
||||
Warning =
|
||||
{
|
||||
CROSS_SEGMENT: 0,
|
||||
TUNNEL: 1,
|
||||
SHARED_LOCAL_ADMIN: 2,
|
||||
SHARED_PASSWORDS: 3,
|
||||
SHARED_PASSWORDS_DOMAIN: 4
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
report: props.report,
|
||||
graph: {nodes: [], edges: []}
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateMapFromServer();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.report !== prevProps.report) {
|
||||
this.setState({ report: this.props.report })
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let content;
|
||||
|
||||
if (this.stillLoadingDataFromServer()) {
|
||||
content = <ReportLoader loading={true}/>;
|
||||
} else {
|
||||
content =
|
||||
<div>
|
||||
{this.generateReportOverviewSection()}
|
||||
{this.generateReportFindingsSection()}
|
||||
{this.generateReportRecommendationsSection()}
|
||||
{this.generateReportGlanceSection()}
|
||||
{this.generateReportFooter()}
|
||||
</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
<PrintReportButton onClick={() => {
|
||||
print();
|
||||
}}/>
|
||||
</div>
|
||||
<div className="report-page">
|
||||
<ReportHeader report_type={ReportTypes.security}/>
|
||||
<hr/>
|
||||
{content}
|
||||
</div>
|
||||
<div style={{marginTop: '20px'}}>
|
||||
<PrintReportButton onClick={() => {
|
||||
print();
|
||||
}}/>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
stillLoadingDataFromServer() {
|
||||
return Object.keys(this.state.report).length === 0;
|
||||
}
|
||||
|
||||
updateMapFromServer = () => {
|
||||
this.authFetch('/api/netmap')
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
res.edges.forEach(edge => {
|
||||
edge.color = {'color': edgeGroupToColor(edge.group)};
|
||||
});
|
||||
this.setState({graph: res});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
generateReportOverviewSection() {
|
||||
return (
|
||||
<div id="overview">
|
||||
<h2>
|
||||
Overview
|
||||
</h2>
|
||||
<SecurityIssuesGlance issuesFound={this.state.report.glance.exploited.length > 0}/>
|
||||
{
|
||||
this.state.report.glance.exploited.length > 0 ?
|
||||
''
|
||||
:
|
||||
<p className="alert alert-info">
|
||||
<i className="glyphicon glyphicon-info-sign" style={{'marginRight': '5px'}}/>
|
||||
To improve the monkey's detection rates, try adding users and passwords and enable the "Local
|
||||
network
|
||||
scan" config value under <b>Basic - Network</b>.
|
||||
</p>
|
||||
}
|
||||
<p>
|
||||
The first monkey run was started on <span
|
||||
className="label label-info">{this.state.report.overview.monkey_start_time}</span>. After <span
|
||||
className="label label-info">{this.state.report.overview.monkey_duration}</span>, all monkeys finished
|
||||
propagation attempts.
|
||||
</p>
|
||||
<p>
|
||||
The monkey started propagating from the following machines where it was manually installed:
|
||||
<ul>
|
||||
{this.state.report.overview.manual_monkeys.map(x => <li>{x}</li>)}
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
The monkeys were run with the following configuration:
|
||||
</p>
|
||||
{
|
||||
this.state.report.overview.config_users.length > 0 ?
|
||||
<p>
|
||||
Usernames used for brute-forcing:
|
||||
<ul>
|
||||
{this.state.report.overview.config_users.map(x => <li>{x}</li>)}
|
||||
</ul>
|
||||
Passwords used for brute-forcing:
|
||||
<ul>
|
||||
{this.state.report.overview.config_passwords.map(x => <li>{x.substr(0, 3) + '******'}</li>)}
|
||||
</ul>
|
||||
</p>
|
||||
:
|
||||
<p>
|
||||
Brute forcing uses stolen credentials only. No credentials were supplied during Monkey’s
|
||||
configuration.
|
||||
</p>
|
||||
}
|
||||
{
|
||||
this.state.report.overview.config_exploits.length > 0 ?
|
||||
(
|
||||
this.state.report.overview.config_exploits[0] === 'default' ?
|
||||
''
|
||||
:
|
||||
<p>
|
||||
The Monkey uses the following exploit methods:
|
||||
<ul>
|
||||
{this.state.report.overview.config_exploits.map(x => <li>{x}</li>)}
|
||||
</ul>
|
||||
</p>
|
||||
)
|
||||
:
|
||||
<p>
|
||||
No exploits are used by the Monkey.
|
||||
</p>
|
||||
}
|
||||
{
|
||||
this.state.report.overview.config_ips.length > 0 ?
|
||||
<p>
|
||||
The Monkey scans the following IPs:
|
||||
<ul>
|
||||
{this.state.report.overview.config_ips.map(x => <li>{x}</li>)}
|
||||
</ul>
|
||||
</p>
|
||||
:
|
||||
''
|
||||
}
|
||||
{
|
||||
this.state.report.overview.config_scan ?
|
||||
''
|
||||
:
|
||||
<p>
|
||||
Note: Monkeys were configured to avoid scanning of the local network.
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
generateReportFindingsSection() {
|
||||
return (
|
||||
<div id="findings">
|
||||
<h3>
|
||||
Security Findings
|
||||
</h3>
|
||||
<div>
|
||||
<h3>
|
||||
Immediate Threats
|
||||
</h3>
|
||||
{
|
||||
this.state.report.overview.issues.filter(function (x) {
|
||||
return x === true;
|
||||
}).length > 0 ?
|
||||
<div>
|
||||
During this simulated attack the Monkey uncovered <span
|
||||
className="label label-warning">
|
||||
{this.state.report.overview.issues.filter(function (x) {
|
||||
return x === true;
|
||||
}).length} threats</span>:
|
||||
<ul>
|
||||
{this.state.report.overview.issues[this.Issue.STOLEN_SSH_KEYS] ?
|
||||
<li>Stolen SSH keys are used to exploit other machines.</li> : null}
|
||||
{this.state.report.overview.issues[this.Issue.STOLEN_CREDS] ?
|
||||
<li>Stolen credentials are used to exploit other machines.</li> : null}
|
||||
{this.state.report.overview.issues[this.Issue.ELASTIC] ?
|
||||
<li>Elasticsearch servers are vulnerable to <a
|
||||
href="https://www.cvedetails.com/cve/cve-2015-1427">CVE-2015-1427</a>.
|
||||
</li> : null}
|
||||
{this.state.report.overview.issues[this.Issue.VSFTPD] ?
|
||||
<li>VSFTPD is vulnerable to <a
|
||||
href="https://www.rapid7.com/db/modules/exploit/unix/ftp/vsftpd_234_backdoor">CVE-2011-2523</a>.
|
||||
</li> : null}
|
||||
{this.state.report.overview.issues[this.Issue.SAMBACRY] ?
|
||||
<li>Samba servers are vulnerable to ‘SambaCry’ (<a
|
||||
href="https://www.samba.org/samba/security/CVE-2017-7494.html"
|
||||
>CVE-2017-7494</a>).</li> : null}
|
||||
{this.state.report.overview.issues[this.Issue.SHELLSHOCK] ?
|
||||
<li>Machines are vulnerable to ‘Shellshock’ (<a
|
||||
href="https://www.cvedetails.com/cve/CVE-2014-6271">CVE-2014-6271</a>).
|
||||
</li> : null}
|
||||
{this.state.report.overview.issues[this.Issue.CONFICKER] ?
|
||||
<li>Machines are vulnerable to ‘Conficker’ (<a
|
||||
href="https://docs.microsoft.com/en-us/security-updates/SecurityBulletins/2008/ms08-067"
|
||||
>MS08-067</a>).</li> : null}
|
||||
{this.state.report.overview.issues[this.Issue.WEAK_PASSWORD] ?
|
||||
<li>Machines are accessible using passwords supplied by the user during the Monkey’s
|
||||
configuration.</li> : null}
|
||||
{this.state.report.overview.issues[this.Issue.AZURE] ?
|
||||
<li>Azure machines expose plaintext passwords. (<a
|
||||
href="https://www.guardicore.com/2018/03/recovering-plaintext-passwords-azure/"
|
||||
>More info</a>)</li> : null}
|
||||
{this.state.report.overview.issues[this.Issue.STRUTS2] ?
|
||||
<li>Struts2 servers are vulnerable to remote code execution. (<a
|
||||
href="https://cwiki.apache.org/confluence/display/WW/S2-045">
|
||||
CVE-2017-5638</a>)</li> : null}
|
||||
{this.state.report.overview.issues[this.Issue.WEBLOGIC] ?
|
||||
<li>Oracle WebLogic servers are susceptible to a remote code execution vulnerability.</li> : null}
|
||||
{this.state.report.overview.issues[this.Issue.HADOOP] ?
|
||||
<li>Hadoop/Yarn servers are vulnerable to remote code execution.</li> : null}
|
||||
{this.state.report.overview.issues[this.Issue.PTH_CRIT_SERVICES_ACCESS] ?
|
||||
<li>Mimikatz found login credentials of a user who has admin access to a server defined as
|
||||
critical.</li> : null}
|
||||
{this.state.report.overview.issues[this.Issue.MSSQL] ?
|
||||
<li>MS-SQL servers are vulnerable to remote code execution via xp_cmdshell command.</li> : null}
|
||||
</ul>
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
During this simulated attack the Monkey uncovered <span
|
||||
className="label label-success">0 threats</span>.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
<h3>
|
||||
Potential Security Issues
|
||||
</h3>
|
||||
{
|
||||
this.state.report.overview.warnings.filter(function (x) {
|
||||
return x === true;
|
||||
}).length > 0 ?
|
||||
<div>
|
||||
The Monkey uncovered the following possible set of issues:
|
||||
<ul>
|
||||
{this.state.report.overview.warnings[this.Warning.CROSS_SEGMENT] ?
|
||||
<li>Weak segmentation - Machines from different segments are able to
|
||||
communicate.</li> : null}
|
||||
{this.state.report.overview.warnings[this.Warning.TUNNEL] ?
|
||||
<li>Weak segmentation - Machines were able to communicate over unused ports.</li> : null}
|
||||
{this.state.report.overview.warnings[this.Warning.SHARED_LOCAL_ADMIN] ?
|
||||
<li>Shared local administrator account - Different machines have the same account as a local
|
||||
administrator.</li> : null}
|
||||
{this.state.report.overview.warnings[this.Warning.SHARED_PASSWORDS] ?
|
||||
<li>Multiple users have the same password</li> : null}
|
||||
</ul>
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
The Monkey did not find any issues.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
{this.state.report.overview.cross_segment_issues.length > 0 ?
|
||||
<div>
|
||||
<h3>
|
||||
Segmentation Issues
|
||||
</h3>
|
||||
<div>
|
||||
The Monkey uncovered the following set of segmentation issues:
|
||||
<ul>
|
||||
{this.state.report.overview.cross_segment_issues.map(x => this.generateCrossSegmentIssue(x))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
''
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
generateReportRecommendationsSection() {
|
||||
return (
|
||||
<div id="recommendations">
|
||||
{/* Checks if there are any domain issues. If there are more then one: render the title. Otherwise,
|
||||
* don't render it (since the issues themselves will be empty. */}
|
||||
{Object.keys(this.state.report.recommendations.domain_issues).length !== 0 ?
|
||||
<h3>Domain related recommendations</h3> : null}
|
||||
<div>
|
||||
{this.generateIssues(this.state.report.recommendations.domain_issues)}
|
||||
</div>
|
||||
{/* Checks if there are any issues. If there are more then one: render the title. Otherwise,
|
||||
* don't render it (since the issues themselves will be empty. */}
|
||||
{Object.keys(this.state.report.recommendations.issues).length !== 0 ?
|
||||
<h3>Machine related recommendations</h3> : null}
|
||||
<div>
|
||||
{this.generateIssues(this.state.report.recommendations.issues)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
generateReportGlanceSection() {
|
||||
let exploitPercentage =
|
||||
(100 * this.state.report.glance.exploited.length) / this.state.report.glance.scanned.length;
|
||||
return (
|
||||
<div id="glance">
|
||||
<h3>
|
||||
The Network from the Monkey's Eyes
|
||||
</h3>
|
||||
<div>
|
||||
<p>
|
||||
The Monkey discovered <span
|
||||
className="label label-warning">{this.state.report.glance.scanned.length}</span> machines and
|
||||
successfully breached <span
|
||||
className="label label-danger">{this.state.report.glance.exploited.length}</span> of them.
|
||||
</p>
|
||||
<div className="text-center" style={{margin: '10px'}}>
|
||||
<Line style={{width: '300px', marginRight: '5px'}} percent={exploitPercentage} strokeWidth="4"
|
||||
trailWidth="4"
|
||||
strokeColor="#d9534f" trailColor="#f0ad4e"/>
|
||||
<b>{Math.round(exploitPercentage)}% of scanned machines exploited</b>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
From the attacker's point of view, the network looks like this:
|
||||
</p>
|
||||
<div className="map-legend">
|
||||
<b>Legend: </b>
|
||||
<span>Exploit <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#cc0200'}}/></span>
|
||||
<b style={{color: '#aeaeae'}}> | </b>
|
||||
<span>Scan <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#ff9900'}}/></span>
|
||||
<b style={{color: '#aeaeae'}}> | </b>
|
||||
<span>Tunnel <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#0158aa'}}/></span>
|
||||
<b style={{color: '#aeaeae'}}> | </b>
|
||||
<span>Island Communication <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#a9aaa9'}}/></span>
|
||||
</div>
|
||||
<div style={{position: 'relative', height: '80vh'}}>
|
||||
<ReactiveGraph graph={this.state.graph} options={options}/>
|
||||
</div>
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
<BreachedServers data={this.state.report.glance.exploited}/>
|
||||
</div>
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
<PostBreach data={this.state.report.glance.scanned}/>
|
||||
</div>
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
<ScannedServers data={this.state.report.glance.scanned}/>
|
||||
</div>
|
||||
<div style={{position: 'relative', height: '80vh'}}>
|
||||
{this.generateReportPthMap()}
|
||||
</div>
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
<StolenPasswords data={this.state.report.glance.stolen_creds.concat(this.state.report.glance.ssh_keys)}/>
|
||||
</div>
|
||||
<div>
|
||||
<StrongUsers data={this.state.report.glance.strong_users}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
generateReportPthMap() {
|
||||
return (
|
||||
<div id="pth">
|
||||
<h3>
|
||||
Credentials Map
|
||||
</h3>
|
||||
<p>
|
||||
This map visualizes possible attack paths through the network using credential compromise. Paths represent lateral
|
||||
movement opportunities by attackers.
|
||||
</p>
|
||||
<div className="map-legend">
|
||||
<b>Legend: </b>
|
||||
<span>Access credentials <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#0158aa'}}/></span> <b
|
||||
style={{color: '#aeaeae'}}> | </b>
|
||||
</div>
|
||||
<div>
|
||||
<PassTheHashMapPageComponent graph={this.state.report.glance.pth_map}/>
|
||||
</div>
|
||||
<br/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
generateReportFooter() {
|
||||
return (
|
||||
<div id="footer" className="text-center" style={{marginTop: '20px'}}>
|
||||
For questions, suggestions or any other feedback
|
||||
contact: <a href="mailto://labs@guardicore.com" className="no-print">labs@guardicore.com</a>
|
||||
<div className="force-print" style={{display: 'none'}}>labs@guardicore.com</div>
|
||||
<img src={guardicoreLogoImage} alt="GuardiCore" className="center-block" style={{height: '50px'}}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
generateInfoBadges(data_array) {
|
||||
return data_array.map(badge_data => <span className="label label-info" style={{margin: '2px'}}>{badge_data}</span>);
|
||||
}
|
||||
|
||||
generateCrossSegmentIssue(crossSegmentIssue) {
|
||||
return <li>
|
||||
{'Communication possible from ' + crossSegmentIssue['source_subnet'] + ' to ' + crossSegmentIssue['target_subnet']}
|
||||
<CollapsibleWellComponent>
|
||||
<ul>
|
||||
{crossSegmentIssue['issues'].map(x =>
|
||||
x['is_self'] ?
|
||||
<li>
|
||||
{'Machine ' + x['hostname'] + ' has both ips: ' + x['source'] + ' and ' + x['target']}
|
||||
</li>
|
||||
:
|
||||
<li>
|
||||
{'IP ' + x['source'] + ' (' + x['hostname'] + ') connected to IP ' + x['target']
|
||||
+ ' using the services: ' + Object.keys(x['services']).join(', ')}
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</CollapsibleWellComponent>
|
||||
</li>;
|
||||
}
|
||||
|
||||
generateShellshockPathListBadges(paths) {
|
||||
return paths.map(path => <span className="label label-warning" style={{margin: '2px'}}>{path}</span>);
|
||||
}
|
||||
|
||||
generateSmbPasswordIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
|
||||
that is not shared with other computers on the network.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">SMB</span> attack.
|
||||
<br/>
|
||||
The Monkey authenticated over the SMB protocol with user <span
|
||||
className="label label-success">{issue.username}</span> and its password.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateSmbPthIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
|
||||
that is not shared with other computers on the network.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">SMB</span> attack.
|
||||
<br/>
|
||||
The Monkey used a pass-the-hash attack over SMB protocol with user <span
|
||||
className="label label-success">{issue.username}</span>.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateWmiPasswordIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
|
||||
that is not shared with other computers on the network.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">WMI</span> attack.
|
||||
<br/>
|
||||
The Monkey authenticated over the WMI protocol with user <span
|
||||
className="label label-success">{issue.username}</span> and its password.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateWmiPthIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
|
||||
that is not shared with other computers on the network.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">WMI</span> attack.
|
||||
<br/>
|
||||
The Monkey used a pass-the-hash attack over WMI protocol with user <span
|
||||
className="label label-success">{issue.username}</span>.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateSshIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
|
||||
that is not shared with other computers on the network.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">SSH</span> attack.
|
||||
<br/>
|
||||
The Monkey authenticated over the SSH protocol with user <span
|
||||
className="label label-success">{issue.username}</span> and its password.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateSshKeysIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Protect <span className="label label-success">{issue.ssh_key}</span> private key with a pass phrase.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">SSH</span> attack.
|
||||
<br/>
|
||||
The Monkey authenticated over the SSH protocol with private key <span
|
||||
className="label label-success">{issue.ssh_key}</span>.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
generateSambaCryIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Change <span className="label label-success">{issue.username}</span>'s password to a complex one-use password
|
||||
that is not shared with other computers on the network.
|
||||
<br/>
|
||||
Update your Samba server to 4.4.14 and up, 4.5.10 and up, or 4.6.4 and up.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">SambaCry</span> attack.
|
||||
<br/>
|
||||
The Monkey authenticated over the SMB protocol with user <span
|
||||
className="label label-success">{issue.username}</span> and its password, and used the SambaCry
|
||||
vulnerability.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateVsftpdBackdoorIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Update your VSFTPD server to the latest version vsftpd-3.0.3.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) has a backdoor running at port <span
|
||||
className="label label-danger">6200</span>.
|
||||
<br/>
|
||||
The attack was made possible because the VSFTPD server was not patched against CVE-2011-2523.
|
||||
<br/><br/>In July 2011, it was discovered that vsftpd version 2.3.4 downloadable from the master site had been
|
||||
compromised.
|
||||
Users logging into a compromised vsftpd-2.3.4 server may issue a ":)" smileyface as the username and gain a command
|
||||
shell on port 6200.
|
||||
<br/><br/>
|
||||
The Monkey executed commands by first logging in with ":)" in the username and then sending commands to the backdoor
|
||||
at port 6200.
|
||||
<br/><br/>Read more about the security issue and remediation <a
|
||||
href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-2523"
|
||||
>here</a>.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateElasticIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Update your Elastic Search server to version 1.4.3 and up.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to an <span
|
||||
className="label label-danger">Elastic Groovy</span> attack.
|
||||
<br/>
|
||||
The attack was made possible because the Elastic Search server was not patched against CVE-2015-1427.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateShellshockIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Update your Bash to a ShellShock-patched version.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">ShellShock</span> attack.
|
||||
<br/>
|
||||
The attack was made possible because the HTTP server running on TCP port <span
|
||||
className="label label-info">{issue.port}</span> was vulnerable to a shell injection attack on the
|
||||
paths: {this.generateShellshockPathListBadges(issue.paths)}.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateAzureIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Delete VM Access plugin configuration files.
|
||||
<CollapsibleWellComponent>
|
||||
Credentials could be stolen from <span
|
||||
className="label label-primary">{issue.machine}</span> for the following users <span
|
||||
className="label label-primary">{issue.users}</span>. Read more about the security issue and remediation <a
|
||||
href="https://www.guardicore.com/2018/03/recovering-plaintext-passwords-azure/"
|
||||
>here</a>.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateConfickerIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Install the latest Windows updates or upgrade to a newer operating system.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">Conficker</span> attack.
|
||||
<br/>
|
||||
The attack was made possible because the target machine used an outdated and unpatched operating system
|
||||
vulnerable to Conficker.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateIslandCrossSegmentIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Segment your network and make sure there is no communication between machines from different segments.
|
||||
<CollapsibleWellComponent>
|
||||
The network can probably be segmented. A monkey instance on <span
|
||||
className="label label-primary">{issue.machine}</span> in the
|
||||
networks {this.generateInfoBadges(issue.networks)}
|
||||
could directly access the Monkey Island server in the
|
||||
networks {this.generateInfoBadges(issue.server_networks)}.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateSharedCredsDomainIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Some domain users are sharing passwords, this should be fixed by changing passwords.
|
||||
<CollapsibleWellComponent>
|
||||
These users are sharing access password:
|
||||
{this.generateInfoBadges(issue.shared_with)}.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateSharedCredsIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Some users are sharing passwords, this should be fixed by changing passwords.
|
||||
<CollapsibleWellComponent>
|
||||
These users are sharing access password:
|
||||
{this.generateInfoBadges(issue.shared_with)}.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateSharedLocalAdminsIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Make sure the right administrator accounts are managing the right machines, and that there isn’t an unintentional local
|
||||
admin sharing.
|
||||
<CollapsibleWellComponent>
|
||||
Here is a list of machines which the account <span
|
||||
className="label label-primary">{issue.username}</span> is defined as an administrator:
|
||||
{this.generateInfoBadges(issue.shared_machines)}
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateStrongUsersOnCritIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
This critical machine is open to attacks via strong users with access to it.
|
||||
<CollapsibleWellComponent>
|
||||
The services: {this.generateInfoBadges(issue.services)} have been found on the machine
|
||||
thus classifying it as a critical machine.
|
||||
These users has access to it:
|
||||
{this.generateInfoBadges(issue.threatening_users)}.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateTunnelIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Use micro-segmentation policies to disable communication other than the required.
|
||||
<CollapsibleWellComponent>
|
||||
Machines are not locked down at port level. Network tunnel was set up from <span
|
||||
className="label label-primary">{issue.machine}</span> to <span
|
||||
className="label label-primary">{issue.dest}</span>.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateStruts2Issue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Upgrade Struts2 to version 2.3.32 or 2.5.10.1 or any later versions.
|
||||
<CollapsibleWellComponent>
|
||||
Struts2 server at <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span
|
||||
className="label label-danger">remote code execution</span> attack.
|
||||
<br/>
|
||||
The attack was made possible because the server is using an old version of Jakarta based file upload
|
||||
Multipart parser. For possible work-arounds and more info read <a
|
||||
href="https://cwiki.apache.org/confluence/display/WW/S2-045"
|
||||
>here</a>.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateWebLogicIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Update Oracle WebLogic server to the latest supported version.
|
||||
<CollapsibleWellComponent>
|
||||
Oracle WebLogic server at <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to one of <span
|
||||
className="label label-danger">remote code execution</span> attacks.
|
||||
<br/>
|
||||
The attack was made possible due to one of the following vulnerabilities:
|
||||
<a href={'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-10271'}> CVE-2017-10271</a> or
|
||||
<a href={'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-2725'}> CVE-2019-2725</a>
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateHadoopIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Run Hadoop in secure mode (<a
|
||||
href="http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/SecureMode.html">
|
||||
add Kerberos authentication</a>).
|
||||
<CollapsibleWellComponent>
|
||||
The Hadoop server at <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to <span
|
||||
className="label label-danger">remote code execution</span> attack.
|
||||
<br/>
|
||||
The attack was made possible due to default Hadoop/Yarn configuration being insecure.
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateMSSQLIssue(issue) {
|
||||
return (
|
||||
<li>
|
||||
Disable the xp_cmdshell option.
|
||||
<CollapsibleWellComponent>
|
||||
The machine <span className="label label-primary">{issue.machine}</span> (<span
|
||||
className="label label-info" style={{margin: '2px'}}>{issue.ip_address}</span>) is vulnerable to a <span
|
||||
className="label label-danger">MSSQL exploit attack</span>.
|
||||
<br/>
|
||||
The attack was made possible because the target machine used an outdated MSSQL server configuration allowing
|
||||
the usage of the xp_cmdshell command. To learn more about how to disable this feature, read <a
|
||||
href="https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/xp-cmdshell-server-configuration-option?view=sql-server-2017">
|
||||
Microsoft's documentation. </a>
|
||||
</CollapsibleWellComponent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
generateIssue = (issue) => {
|
||||
let data;
|
||||
switch (issue.type) {
|
||||
case 'vsftp':
|
||||
data = this.generateVsftpdBackdoorIssue(issue);
|
||||
break;
|
||||
case 'smb_password':
|
||||
data = this.generateSmbPasswordIssue(issue);
|
||||
break;
|
||||
case 'smb_pth':
|
||||
data = this.generateSmbPthIssue(issue);
|
||||
break;
|
||||
case 'wmi_password':
|
||||
data = this.generateWmiPasswordIssue(issue);
|
||||
break;
|
||||
case 'wmi_pth':
|
||||
data = this.generateWmiPthIssue(issue);
|
||||
break;
|
||||
case 'ssh':
|
||||
data = this.generateSshIssue(issue);
|
||||
break;
|
||||
case 'ssh_key':
|
||||
data = this.generateSshKeysIssue(issue);
|
||||
break;
|
||||
case 'sambacry':
|
||||
data = this.generateSambaCryIssue(issue);
|
||||
break;
|
||||
case 'elastic':
|
||||
data = this.generateElasticIssue(issue);
|
||||
break;
|
||||
case 'shellshock':
|
||||
data = this.generateShellshockIssue(issue);
|
||||
break;
|
||||
case 'conficker':
|
||||
data = this.generateConfickerIssue(issue);
|
||||
break;
|
||||
case 'island_cross_segment':
|
||||
data = this.generateIslandCrossSegmentIssue(issue);
|
||||
break;
|
||||
case 'shared_passwords':
|
||||
data = this.generateSharedCredsIssue(issue);
|
||||
break;
|
||||
case 'shared_passwords_domain':
|
||||
data = this.generateSharedCredsDomainIssue(issue);
|
||||
break;
|
||||
case 'shared_admins_domain':
|
||||
data = this.generateSharedLocalAdminsIssue(issue);
|
||||
break;
|
||||
case 'strong_users_on_crit':
|
||||
data = this.generateStrongUsersOnCritIssue(issue);
|
||||
break;
|
||||
case 'tunnel':
|
||||
data = this.generateTunnelIssue(issue);
|
||||
break;
|
||||
case 'azure_password':
|
||||
data = this.generateAzureIssue(issue);
|
||||
break;
|
||||
case 'struts2':
|
||||
data = this.generateStruts2Issue(issue);
|
||||
break;
|
||||
case 'weblogic':
|
||||
data = this.generateWebLogicIssue(issue);
|
||||
break;
|
||||
case 'hadoop':
|
||||
data = this.generateHadoopIssue(issue);
|
||||
break;
|
||||
case 'mssql':
|
||||
data = this.generateMSSQLIssue(issue);
|
||||
break;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
generateIssues = (issues) => {
|
||||
let issuesDivArray = [];
|
||||
for (let machine of Object.keys(issues)) {
|
||||
issuesDivArray.push(
|
||||
<li>
|
||||
<h4><b>{machine}</b></h4>
|
||||
<ol>
|
||||
{issues[machine].map(this.generateIssue)}
|
||||
</ol>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
return <ul>{issuesDivArray}</ul>;
|
||||
};
|
||||
}
|
||||
|
||||
export default ReportPageComponent;
|
|
@ -0,0 +1,66 @@
|
|||
import React, {Fragment} from 'react';
|
||||
import AuthComponent from '../AuthComponent';
|
||||
import ReportHeader, {ReportTypes} from './common/ReportHeader';
|
||||
import ReportLoader from './common/ReportLoader';
|
||||
import PrintReportButton from './common/PrintReportButton';
|
||||
import SummarySection from './zerotrust/SummarySection';
|
||||
import FindingsSection from './zerotrust/FindingsSection';
|
||||
import PrinciplesSection from './zerotrust/PrinciplesSection';
|
||||
|
||||
class ZeroTrustReportPageComponent extends AuthComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = this.props.report
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.report !== prevProps.report) {
|
||||
this.setState(this.props.report)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let content;
|
||||
if (this.stillLoadingDataFromServer()) {
|
||||
content = <ReportLoader loading={true}/>;
|
||||
} else {
|
||||
content = <div id="MainContentSection">
|
||||
<SummarySection allMonkeysAreDead={this.state.allMonkeysAreDead} pillars={this.state.pillars}/>
|
||||
<PrinciplesSection principles={this.state.principles}
|
||||
pillarsToStatuses={this.state.pillars.pillarsToStatuses}/>
|
||||
<FindingsSection pillarsToStatuses={this.state.pillars.pillarsToStatuses} findings={this.state.findings}/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
<PrintReportButton onClick={() => {
|
||||
print();
|
||||
}}/>
|
||||
</div>
|
||||
<div className="report-page">
|
||||
<ReportHeader report_type={ReportTypes.zeroTrust}/>
|
||||
<hr/>
|
||||
{content}
|
||||
</div>
|
||||
<div style={{marginTop: '20px'}}>
|
||||
<PrintReportButton onClick={() => {
|
||||
print();
|
||||
}}/>
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
stillLoadingDataFromServer() {
|
||||
return typeof this.state.findings === 'undefined'
|
||||
|| typeof this.state.pillars === 'undefined'
|
||||
|| typeof this.state.principles === 'undefined';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default ZeroTrustReportPageComponent;
|
|
@ -0,0 +1,84 @@
|
|||
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';
|
||||
|
||||
class ReportMatrixComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {techniques: this.props.techniques,
|
||||
schema: this.props.schema};
|
||||
}
|
||||
|
||||
getColumns() {
|
||||
let columns = [];
|
||||
for(const type_key in this.state.schema.properties){
|
||||
if (! this.state.schema.properties.hasOwnProperty(type_key)){
|
||||
continue;
|
||||
}
|
||||
let tech_type = this.state.schema.properties[type_key];
|
||||
columns.push({
|
||||
Header: () => (<a href={tech_type.link} target="_blank">{tech_type.title}</a>),
|
||||
id: type_key,
|
||||
accessor: x => this.renderTechnique(x[tech_type.title]),
|
||||
style: {'whiteSpace': 'unset'}
|
||||
});
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
|
||||
getTableRows() {
|
||||
let rows = [];
|
||||
for (const tech_id in this.state.techniques) {
|
||||
if (this.state.techniques.hasOwnProperty(tech_id)){
|
||||
let technique_added = false;
|
||||
let technique = this.state.techniques[tech_id];
|
||||
for(const row of rows){
|
||||
if (! row.hasOwnProperty(technique.type)){
|
||||
row[technique.type] = technique;
|
||||
technique_added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! technique_added){
|
||||
let newRow = {};
|
||||
newRow[technique.type] = technique;
|
||||
rows.push(newRow)
|
||||
}
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
renderTechnique(technique) {
|
||||
if (technique == null || typeof technique === undefined) {
|
||||
return (<div/>)
|
||||
} else {
|
||||
return (
|
||||
<Checkbox checked={technique.selected}
|
||||
necessary={false}
|
||||
name={technique.title}
|
||||
changeHandler={this.props.onClick}
|
||||
status={technique.status}>
|
||||
{technique.title}
|
||||
</Checkbox>)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let tableRows = this.getTableRows();
|
||||
return (
|
||||
<div>
|
||||
<div className={'attack-matrix'}>
|
||||
<ReactTable columns={this.getColumns()}
|
||||
data={tableRows}
|
||||
showPagination={false}
|
||||
defaultPageSize={tableRows.length}/>
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
export default ReportMatrixComponent;
|
|
@ -0,0 +1,77 @@
|
|||
import React from 'react';
|
||||
import Collapse from '@kunukn/react-collapse';
|
||||
|
||||
import AttackReport from '../AttackReport';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
const classNames = require('classnames');
|
||||
|
||||
class SelectedTechnique extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
techniques: this.props.techniques,
|
||||
techComponents: this.props.techComponents,
|
||||
selectedTechnique: this.props.selected
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.selected !== prevProps.selected || this.props.techniques !== prevProps.techniques) {
|
||||
this.setState({ selectedTechnique: this.props.selected,
|
||||
techniques: this.props.techniques})
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedTechniqueComponent(tech_id) {
|
||||
const TechniqueComponent = this.state.techComponents[tech_id];
|
||||
return (
|
||||
<div key={tech_id} className={classNames('collapse-item', {'item--active': true})}>
|
||||
<button className={classNames('btn-collapse',
|
||||
'selected-technique',
|
||||
AttackReport.getComponentClass(tech_id, this.state.techniques))}>
|
||||
<span>
|
||||
{AttackReport.getStatusIcon(tech_id, this.state.techniques)}
|
||||
{this.state.techniques[tech_id].title}
|
||||
</span>
|
||||
<span>
|
||||
<a href={this.state.techniques[tech_id].link} target='_blank' className={'link-to-technique'}>
|
||||
<FontAwesomeIcon icon={faQuestionCircle}/>
|
||||
</a>
|
||||
</span>
|
||||
</button>
|
||||
<Collapse
|
||||
className='collapse-comp'
|
||||
isOpen={true}
|
||||
render={() => {
|
||||
return (<div className={`content ${tech_id}`}>
|
||||
<TechniqueComponent data={this.state.techniques[tech_id]}/>
|
||||
</div>)
|
||||
}}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(){
|
||||
let content = {};
|
||||
let selectedTechId = this.state.selectedTechnique;
|
||||
if(selectedTechId === false){
|
||||
content = 'None. Select a technique from ATT&CK matrix above.';
|
||||
} else {
|
||||
content = this.getSelectedTechniqueComponent(selectedTechId)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className='selected-technique-title'>Selected technique</h3>
|
||||
<section className='attack-report selected-technique'>
|
||||
{content}
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectedTechnique;
|
|
@ -0,0 +1,124 @@
|
|||
import React from 'react';
|
||||
import Collapse from '@kunukn/react-collapse';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faQuestionCircle, faChevronUp, faChevronDown, faToggleOn } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import {Button} from 'react-bootstrap';
|
||||
import AttackReport from '../AttackReport';
|
||||
|
||||
const classNames = require('classnames');
|
||||
|
||||
class TechniqueDropdowns extends React.Component{
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
techniques: this.props.techniques,
|
||||
techComponents: this.props.techComponents,
|
||||
schema: this.props.schema,
|
||||
collapseOpen: '',
|
||||
techniquesHidden: true
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.techniques !== prevProps.techniques) {
|
||||
this.setState({ techniques: this.props.techniques })
|
||||
}
|
||||
}
|
||||
|
||||
onToggle = technique =>
|
||||
this.setState(state => ({collapseOpen: state.collapseOpen === technique ? null : technique}));
|
||||
|
||||
getTechniqueCollapse(tech_id) {
|
||||
return (
|
||||
<div key={tech_id} className={classNames('collapse-item', {'item--active': this.state.collapseOpen === tech_id})}>
|
||||
<button className={classNames('btn-collapse', AttackReport.getComponentClass(tech_id, this.state.techniques))}
|
||||
onClick={() => this.onToggle(tech_id)}>
|
||||
<span>
|
||||
{AttackReport.getStatusIcon(tech_id, this.state.techniques)}
|
||||
{this.state.techniques[tech_id].title}
|
||||
</span>
|
||||
<span>
|
||||
<a href={this.state.techniques[tech_id].link} target='_blank' className={'link-to-technique'}>
|
||||
<FontAwesomeIcon icon={faQuestionCircle}/>
|
||||
</a>
|
||||
<FontAwesomeIcon icon={this.state.collapseOpen === tech_id ? faChevronDown : faChevronUp}/>
|
||||
</span>
|
||||
</button>
|
||||
<Collapse
|
||||
className='collapse-comp'
|
||||
isOpen={this.state.collapseOpen === tech_id}
|
||||
onChange={({collapseState}) => {
|
||||
this.setState({tech_id: collapseState});
|
||||
}}
|
||||
onInit={({collapseState}) => {
|
||||
this.setState({tech_id: collapseState});
|
||||
}}
|
||||
render={collapseState => this.createTechniqueContent(collapseState, tech_id)}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
createTechniqueContent(collapseState, technique) {
|
||||
const TechniqueComponent = this.state.techComponents[technique];
|
||||
return (
|
||||
<div className={`content ${collapseState}`}>
|
||||
<TechniqueComponent data={this.state.techniques[technique]}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
toggleTechList(){
|
||||
this.setState({techniquesHidden: (! this.state.techniquesHidden)})
|
||||
}
|
||||
|
||||
getOrderedTechniqueList(){
|
||||
let content = [];
|
||||
for(const type_key in this.state.schema.properties){
|
||||
if (! this.state.schema.properties.hasOwnProperty(type_key)){
|
||||
continue;
|
||||
}
|
||||
let tech_type = this.state.schema.properties[type_key];
|
||||
content.push(<h3>{tech_type.title}</h3>);
|
||||
for(const tech_id in this.state.techniques){
|
||||
if (! this.state.techniques.hasOwnProperty(tech_id)){
|
||||
continue;
|
||||
}
|
||||
let technique = this.state.techniques[tech_id];
|
||||
if(technique.type === tech_type.title){
|
||||
content.push(this.getTechniqueCollapse(tech_id))
|
||||
}
|
||||
}
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
render(){
|
||||
let content = [];
|
||||
let listClass = '';
|
||||
if (this.state.techniquesHidden){
|
||||
listClass = 'hidden-list'
|
||||
} else {
|
||||
content = this.getOrderedTechniqueList()
|
||||
}
|
||||
return (
|
||||
<div className='attack-technique-list-component'>
|
||||
<h3>
|
||||
List of all techniques
|
||||
<Button bsStyle='link'
|
||||
bsSize='large'
|
||||
onClick={() => this.toggleTechList()}
|
||||
className={classNames({'toggle-btn': true,
|
||||
'toggled-off' : this.state.techniquesHidden,
|
||||
'toggled-on': !this.state.techniquesHidden})}>
|
||||
<FontAwesomeIcon icon={faToggleOn} className={'switch-on'} size={'2x'}/>
|
||||
<FontAwesomeIcon icon={faToggleOn} className={'switch-off'} size={'2x'}/>
|
||||
</Button>
|
||||
</h3>
|
||||
<section className={`dropdown-list ${listClass}`}>{content}</section>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
export default TechniqueDropdowns;
|
|
@ -7,6 +7,7 @@ let monkeyLogoImage = require('../../../images/monkey-icon.svg');
|
|||
export const ReportTypes = {
|
||||
zeroTrust: 'Zero Trust',
|
||||
security: 'Security',
|
||||
attack: 'ATT&CK',
|
||||
null: ''
|
||||
};
|
||||
|
||||
|
|
|
@ -1,199 +0,0 @@
|
|||
import React from 'react';
|
||||
import {Col} from 'react-bootstrap';
|
||||
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
||||
import {edgeGroupToColor, options} from 'components/map/MapOptions';
|
||||
import '../../../styles/Collapse.scss';
|
||||
import AuthComponent from '../../AuthComponent';
|
||||
import {ScanStatus} from '../../attack/techniques/Helpers';
|
||||
import Collapse from '@kunukn/react-collapse';
|
||||
|
||||
import T1210 from '../../attack/techniques/T1210';
|
||||
import T1197 from '../../attack/techniques/T1197';
|
||||
import T1110 from '../../attack/techniques/T1110';
|
||||
import T1075 from '../../attack/techniques/T1075';
|
||||
import T1003 from '../../attack/techniques/T1003';
|
||||
import T1059 from '../../attack/techniques/T1059';
|
||||
import T1086 from '../../attack/techniques/T1086';
|
||||
import T1082 from '../../attack/techniques/T1082';
|
||||
import T1145 from '../../attack/techniques/T1145';
|
||||
import T1105 from '../../attack/techniques/T1105';
|
||||
import T1107 from '../../attack/techniques/T1107';
|
||||
import T1065 from '../../attack/techniques/T1065';
|
||||
import T1035 from '../../attack/techniques/T1035';
|
||||
import T1129 from '../../attack/techniques/T1129';
|
||||
import T1106 from '../../attack/techniques/T1106';
|
||||
import T1188 from '../../attack/techniques/T1188';
|
||||
import T1090 from '../../attack/techniques/T1090';
|
||||
import T1041 from '../../attack/techniques/T1041';
|
||||
import T1222 from '../../attack/techniques/T1222';
|
||||
import T1005 from '../../attack/techniques/T1005';
|
||||
import T1018 from '../../attack/techniques/T1018';
|
||||
import T1016 from '../../attack/techniques/T1016';
|
||||
import T1021 from '../../attack/techniques/T1021';
|
||||
import T1064 from '../../attack/techniques/T1064';
|
||||
import {extractExecutionStatusFromServerResponse} from '../common/ExecutionStatus';
|
||||
|
||||
const tech_components = {
|
||||
'T1210': T1210,
|
||||
'T1197': T1197,
|
||||
'T1110': T1110,
|
||||
'T1075': T1075,
|
||||
'T1003': T1003,
|
||||
'T1059': T1059,
|
||||
'T1086': T1086,
|
||||
'T1082': T1082,
|
||||
'T1145': T1145,
|
||||
'T1065': T1065,
|
||||
'T1105': T1105,
|
||||
'T1035': T1035,
|
||||
'T1129': T1129,
|
||||
'T1106': T1106,
|
||||
'T1107': T1107,
|
||||
'T1188': T1188,
|
||||
'T1090': T1090,
|
||||
'T1041': T1041,
|
||||
'T1222': T1222,
|
||||
'T1005': T1005,
|
||||
'T1018': T1018,
|
||||
'T1016': T1016,
|
||||
'T1021': T1021,
|
||||
'T1064': T1064
|
||||
};
|
||||
|
||||
const classNames = require('classnames');
|
||||
|
||||
class AttackReportPageComponent extends AuthComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
report: false,
|
||||
allMonkeysAreDead: false,
|
||||
runStarted: true,
|
||||
collapseOpen: ''
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateMonkeysRunning().then(res => this.getReportFromServer(res));
|
||||
}
|
||||
|
||||
updateMonkeysRunning = () => {
|
||||
return this.authFetch('/api')
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
this.setState(extractExecutionStatusFromServerResponse(res));
|
||||
return res;
|
||||
});
|
||||
};
|
||||
|
||||
getReportFromServer(res) {
|
||||
if (res['completed_steps']['run_monkey']) {
|
||||
this.authFetch('/api/attack/report')
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
this.setState({
|
||||
report: res
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onToggle = technique =>
|
||||
this.setState(state => ({collapseOpen: state.collapseOpen === technique ? null : technique}));
|
||||
|
||||
getComponentClass(tech_id) {
|
||||
switch (this.state.report[tech_id].status) {
|
||||
case ScanStatus.SCANNED:
|
||||
return 'collapse-info';
|
||||
case ScanStatus.USED:
|
||||
return 'collapse-danger';
|
||||
default:
|
||||
return 'collapse-default';
|
||||
}
|
||||
}
|
||||
|
||||
getTechniqueCollapse(tech_id) {
|
||||
return (
|
||||
<div key={tech_id} className={classNames('collapse-item', {'item--active': this.state.collapseOpen === tech_id})}>
|
||||
<button className={classNames('btn-collapse', this.getComponentClass(tech_id))} onClick={() => this.onToggle(tech_id)}>
|
||||
<span>{this.state.report[tech_id].title}</span>
|
||||
<span>
|
||||
<i className={classNames('fa', this.state.collapseOpen === tech_id ? 'fa-chevron-down' : 'fa-chevron-up')}></i>
|
||||
</span>
|
||||
</button>
|
||||
<Collapse
|
||||
className="collapse-comp"
|
||||
isOpen={this.state.collapseOpen === tech_id}
|
||||
onChange={({collapseState}) => {
|
||||
this.setState({tech_id: collapseState});
|
||||
}}
|
||||
onInit={({collapseState}) => {
|
||||
this.setState({tech_id: collapseState});
|
||||
}}
|
||||
render={collapseState => this.createTechniqueContent(collapseState, tech_id)}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
createTechniqueContent(collapseState, technique) {
|
||||
const TechniqueComponent = tech_components[technique];
|
||||
return (
|
||||
<div className={`content ${collapseState}`}>
|
||||
<TechniqueComponent data={this.state.report[technique]} reportData={this.props.reportData}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderLegend() {
|
||||
return (<div id="header" className="row justify-content-between attack-legend">
|
||||
<Col xs={4}>
|
||||
<i className="fa fa-circle icon-default"></i>
|
||||
<span> - Unscanned</span>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<i className="fa fa-circle icon-info"></i>
|
||||
<span> - Scanned</span>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<i className="fa fa-circle icon-danger"></i>
|
||||
<span> - Used</span>
|
||||
</Col>
|
||||
</div>)
|
||||
}
|
||||
|
||||
generateReportContent() {
|
||||
let content = [];
|
||||
Object.keys(this.state.report).forEach((tech_id) => {
|
||||
content.push(this.getTechniqueCollapse(tech_id))
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
{this.renderLegend()}
|
||||
<section className="attack-report">{content}</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
let content;
|
||||
if (!this.state.runStarted) {
|
||||
content =
|
||||
<p className="alert alert-warning">
|
||||
<i className="glyphicon glyphicon-warning-sign" style={{'marginRight': '5px'}}/>
|
||||
You have to run a monkey before generating a report!
|
||||
</p>;
|
||||
} else if (this.state.report === false) {
|
||||
content = (<h1>Generating Report...</h1>);
|
||||
} else if (Object.keys(this.state.report).length === 0) {
|
||||
if (this.state.runStarted) {
|
||||
content = (<h1>No techniques were scanned</h1>);
|
||||
}
|
||||
} else {
|
||||
content = this.generateReportContent();
|
||||
}
|
||||
return (<div> {content} </div>);
|
||||
}
|
||||
}
|
||||
|
||||
export default AttackReportPageComponent;
|
|
@ -3,6 +3,9 @@ import EventsModal from './EventsModal';
|
|||
import {Badge, Button} from 'react-bootstrap';
|
||||
import * as PropTypes from 'prop-types';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faList } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
export default class EventsButton extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -25,7 +28,7 @@ export default class EventsButton extends Component {
|
|||
exportFilename={this.props.exportFilename}/>
|
||||
<div className="text-center" style={{'display': 'grid'}}>
|
||||
<Button className="btn btn-primary btn-lg" onClick={this.show}>
|
||||
<i className="fa fa-list"/> Events {this.createEventsAmountBadge()}
|
||||
<FontAwesomeIcon icon={faList}/> Events {this.createEventsAmountBadge()}
|
||||
</Button>
|
||||
</div>
|
||||
</Fragment>;
|
||||
|
|
|
@ -2,12 +2,15 @@ import React, {Component} from 'react';
|
|||
import {Button} from 'react-bootstrap';
|
||||
import * as PropTypes from 'prop-types';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faDownload } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
export default class ExportEventsButton extends Component {
|
||||
render() {
|
||||
return <Button className="btn btn-primary btn-lg"
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
<i className="fa fa-download"/> Export
|
||||
<FontAwesomeIcon icon={faDownload}/> Export
|
||||
</Button>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,21 +2,24 @@ import React, {Component} from 'react';
|
|||
import {statusToLabelType} from './StatusLabel';
|
||||
import * as PropTypes from 'prop-types';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faDatabase, faUser, faWifi, faCloud, faLaptop, faEyeSlash, faCogs } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
const pillarToIcon = {
|
||||
'Data': 'fa fa-database',
|
||||
'People': 'fa fa-user',
|
||||
'Networks': 'fa fa-wifi',
|
||||
'Workloads': 'fa fa-cloud',
|
||||
'Devices': 'fa fa-laptop',
|
||||
'Visibility & Analytics': 'fa fa-eye-slash',
|
||||
'Automation & Orchestration': 'fa fa-cogs'
|
||||
'Data': faDatabase,
|
||||
'People': faUser,
|
||||
'Networks': faWifi,
|
||||
'Workloads': faCloud,
|
||||
'Devices': faLaptop,
|
||||
'Visibility & Analytics': faEyeSlash,
|
||||
'Automation & Orchestration': faCogs
|
||||
};
|
||||
|
||||
export default class PillarLabel extends Component {
|
||||
render() {
|
||||
const className = 'label ' + statusToLabelType[this.props.status];
|
||||
return <div className={className} style={{margin: '2px', display: 'inline-block'}}><i
|
||||
className={pillarToIcon[this.props.pillar]}/> {this.props.pillar}</div>
|
||||
return <div className={className} style={{margin: '2px', display: 'inline-block'}}>
|
||||
<FontAwesomeIcon icon={pillarToIcon[this.props.pillar]}/> {this.props.pillar}</div>
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ const columns = [
|
|||
{
|
||||
Header: 'Status', id: 'status',
|
||||
accessor: x => {
|
||||
return <StatusLabel status={x.status} size="fa-3x" showText={false}/>;
|
||||
return <StatusLabel status={x.status} size="3x" showText={false}/>;
|
||||
},
|
||||
maxWidth: MAX_WIDTH_STATUS_COLUMN
|
||||
},
|
||||
|
|
|
@ -4,6 +4,9 @@ import {ZeroTrustStatuses} from './ZeroTrustPillars';
|
|||
import {NavLink} from 'react-router-dom';
|
||||
import {Panel} from 'react-bootstrap';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faChevronDown } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
|
||||
class ZeroTrustReportLegend extends Component {
|
||||
render() {
|
||||
|
@ -13,7 +16,7 @@ class ZeroTrustReportLegend extends Component {
|
|||
<Panel>
|
||||
<Panel.Heading>
|
||||
<Panel.Title toggle>
|
||||
<h3><i className="fa fa-chevron-down"/> Legend</h3>
|
||||
<h3><FontAwesomeIcon icon={faChevronDown} /> Legend</h3>
|
||||
</Panel.Title>
|
||||
</Panel.Heading>
|
||||
<Panel.Collapse>
|
||||
|
|
|
@ -5,6 +5,9 @@ import React from 'react';
|
|||
import * as PropTypes from 'prop-types';
|
||||
import {Panel} from 'react-bootstrap';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faChevronDown } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
export default class SinglePillarPrinciplesStatus extends AuthComponent {
|
||||
render() {
|
||||
if (this.props.principlesStatus.length === 0) {
|
||||
|
@ -15,7 +18,7 @@ export default class SinglePillarPrinciplesStatus extends AuthComponent {
|
|||
<Panel.Heading>
|
||||
<Panel.Title toggle>
|
||||
<h3 style={{textAlign: 'center', marginTop: '1px', marginBottom: '1px'}}>
|
||||
<i className="fa fa-chevron-down"/> <PillarLabel pillar={this.props.pillar}
|
||||
<FontAwesomeIcon icon={faChevronDown}/> <PillarLabel pillar={this.props.pillar}
|
||||
status={this.props.pillarsToStatuses[this.props.pillar]}/>
|
||||
</h3>
|
||||
</Panel.Title>
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import React, {Component} from 'react';
|
||||
import * as PropTypes from 'prop-types';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faCheck, faExclamationTriangle, faBomb, faQuestion } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
const statusToIcon = {
|
||||
'Passed': 'fa-check',
|
||||
'Verify': 'fa-exclamation-triangle',
|
||||
'Failed': 'fa-bomb',
|
||||
'Unexecuted': 'fa-question'
|
||||
'Passed': faCheck,
|
||||
'Verify': faExclamationTriangle,
|
||||
'Failed': faBomb,
|
||||
'Unexecuted': faQuestion
|
||||
};
|
||||
|
||||
export const statusToLabelType = {
|
||||
|
@ -24,7 +27,7 @@ export default class StatusLabel extends Component {
|
|||
|
||||
return (
|
||||
<div className={'label ' + statusToLabelType[this.props.status]} style={{display: 'flow-root'}}>
|
||||
<i className={'fa ' + statusToIcon[this.props.status] + ' ' + this.props.size}/>{text}
|
||||
<FontAwesomeIcon icon={statusToIcon[this.props.status]} size={this.props.size}/>{text}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React, {Component} from 'react';
|
||||
import {Col, Grid, Row} from 'react-bootstrap';
|
||||
import MonkeysStillAliveWarning from '../common/MonkeysStillAliveWarning';
|
||||
import PillarsOverview from './PillarOverview';
|
||||
import ZeroTrustReportLegend from './ReportLegend';
|
||||
import * as PropTypes from 'prop-types';
|
||||
|
@ -12,7 +11,6 @@ export default class SummarySection extends Component {
|
|||
<Grid fluid={true}>
|
||||
<Row>
|
||||
<Col xs={12} sm={12} md={12} lg={12}>
|
||||
<MonkeysStillAliveWarning allMonkeysAreDead={this.props.allMonkeysAreDead}/>
|
||||
<p>
|
||||
Get a quick glance at how your network aligns with the <a
|
||||
href="https://www.forrester.com/report/The+Zero+Trust+eXtended+ZTX+Ecosystem/-/E-RES137210">
|
||||
|
@ -22,11 +20,11 @@ export default class SummarySection extends Component {
|
|||
</Col>
|
||||
</Row>
|
||||
<Row className="show-grid">
|
||||
<Col xs={8} sm={8} md={8} lg={8}>
|
||||
<Col xs={8} sm={8} md={7} lg={7}>
|
||||
<PillarsOverview pillarsToStatuses={this.props.pillars.pillarsToStatuses}
|
||||
grades={this.props.pillars.grades}/>
|
||||
</Col>
|
||||
<Col xs={4} sm={4} md={4} lg={4}>
|
||||
<Col xs={4} sm={4} md={5} lg={5}>
|
||||
<ZeroTrustReportLegend/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -36,6 +34,5 @@ export default class SummarySection extends Component {
|
|||
}
|
||||
|
||||
SummarySection.propTypes = {
|
||||
allMonkeysAreDead: PropTypes.bool,
|
||||
pillars: PropTypes.object
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import url('https://fonts.googleapis.com/css?family=Noto+Sans&display=swap');
|
||||
@import url('//netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.css');
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
|
@ -6,12 +7,10 @@ body {
|
|||
}
|
||||
|
||||
svg {
|
||||
|
||||
-webkit-touch-callout: none; /* iOS Safari */
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-khtml-user-select: none; /* Konqueror HTML */
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||
user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React from 'react';
|
||||
import {Icon} from 'react-fa';
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faDownload } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
class VersionComponent extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -32,7 +34,7 @@ class VersionComponent extends React.Component {
|
|||
<div>
|
||||
<b>Newer version available!</b>
|
||||
<br/>
|
||||
<b><a target="_blank" href={this.state.downloadLink}>Download here <Icon name="download"/></a></b>
|
||||
<b><a target="_blank" href={this.state.downloadLink}>Download here <FontAwesomeIcon icon={faDownload}/></a></b>
|
||||
</div>
|
||||
:
|
||||
undefined
|
||||
|
|
|
@ -14,10 +14,18 @@ class CheckboxComponent extends React.PureComponent {
|
|||
changeHandler(name, checked) function will be called with these parameters:
|
||||
this.props.name (the name of this component) and
|
||||
this.state.checked (boolean indicating if this component is checked or not)
|
||||
|
||||
this.props.status (int) adds a class "status-x" to this checkbox. Used for styling.
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
if (this.props.hasOwnProperty('status')){
|
||||
this.status = this.props.status;
|
||||
} else {
|
||||
this.status = false
|
||||
}
|
||||
this.state = {
|
||||
status: this.status,
|
||||
checked: this.props.checked,
|
||||
necessary: this.props.necessary,
|
||||
isAnimating: false
|
||||
|
@ -48,6 +56,9 @@ class CheckboxComponent extends React.PureComponent {
|
|||
// Creates class string for component
|
||||
composeStateClasses(core) {
|
||||
let result = core;
|
||||
if (this.state.status !== false) {
|
||||
result += ' status-'+this.state.status;
|
||||
}
|
||||
if (this.state.necessary) {
|
||||
return result + ' blocked'
|
||||
}
|
||||
|
@ -56,7 +67,6 @@ class CheckboxComponent extends React.PureComponent {
|
|||
} else {
|
||||
result += ' is-unchecked'
|
||||
}
|
||||
|
||||
if (this.state.isAnimating) {
|
||||
result += ' do-ping';
|
||||
}
|
||||
|
@ -69,11 +79,11 @@ class CheckboxComponent extends React.PureComponent {
|
|||
<div
|
||||
className={cl}
|
||||
onClick={this.state.necessary ? void (0) : this.toggleChecked}>
|
||||
<input className="ui ui-checkbox"
|
||||
type="checkbox" value={this.state.checked}
|
||||
<input className='ui ui-checkbox'
|
||||
type='checkbox' value={this.state.checked}
|
||||
name={this.props.name}/>
|
||||
<label className="text">{this.props.children}</label>
|
||||
<div className="ui-btn-ping" onTransitionEnd={this.stopAnimation}></div>
|
||||
<label className='text'>{this.props.children}</label>
|
||||
<div className='ui-btn-ping' onTransitionEnd={this.stopAnimation}></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -525,7 +525,6 @@ body {
|
|||
.label-danger {
|
||||
background-color: #d9534f !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Attack pages */
|
||||
|
@ -537,10 +536,6 @@ body {
|
|||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.attack-report .btn-collapse span:nth-of-type(2) {
|
||||
flex: 0;
|
||||
}
|
||||
|
||||
.icon-info {
|
||||
color: #ade3eb !important;
|
||||
}
|
||||
|
@ -588,8 +583,3 @@ body {
|
|||
margin-right: auto;
|
||||
}
|
||||
|
||||
.attack-report.footer-text {
|
||||
text-align: right;
|
||||
font-size: 0.8em;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ $black: #000000;
|
|||
text-align: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
|
||||
input {
|
||||
display: none;
|
||||
|
@ -39,6 +40,7 @@ $black: #000000;
|
|||
background-color: $dark-green;
|
||||
color: $light-grey;
|
||||
fill: $light-grey;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.is-checked {
|
||||
|
@ -97,12 +99,47 @@ $black: #000000;
|
|||
}
|
||||
}
|
||||
|
||||
.attack-matrix .ui-checkbox-btn label {
|
||||
padding-top: 0;
|
||||
position: relative;
|
||||
float: left;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.attack-matrix div.rt-td>div {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.attack-matrix div.rt-td {
|
||||
padding: 0;
|
||||
border-right: 3px solid rgba(0,0,0,0) !important;
|
||||
}
|
||||
|
||||
.attack-matrix .rt-tr-group div.rt-td:first-child {
|
||||
border-left: 3px solid rgba(0,0,0,0) !important;
|
||||
}
|
||||
|
||||
|
||||
.attack-matrix .rt-tr-group {
|
||||
border-top: 3px solid white !important;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.attack-matrix .rt-tr-group:last-child {
|
||||
margin-bottom: 3px !important;
|
||||
}
|
||||
|
||||
.icon-checked {
|
||||
color: $green
|
||||
}
|
||||
|
||||
.icon-mandatory {
|
||||
color: $dark-green
|
||||
color: $dark-green;
|
||||
}
|
||||
|
||||
.icon-unchecked {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
$transition: 500ms cubic-bezier(0.4, 0.1, 0.1, 0.5);
|
||||
$transition: 300ms cubic-bezier(0.6, 0.3, 0.3, 0.6);
|
||||
|
||||
$danger-color: #d9acac;
|
||||
$info-color: #ade3eb;
|
||||
|
@ -68,27 +68,12 @@ $default-color: #e0ddde;
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.collapsing {
|
||||
height:100% !important;
|
||||
}
|
||||
|
||||
.collapse-item .content {
|
||||
padding: 2rem 0;
|
||||
transition: transform $transition;
|
||||
will-change: transform;
|
||||
$offset: 10px;
|
||||
|
||||
&.collapsing {
|
||||
transform: translateY(-$offset);
|
||||
}
|
||||
|
||||
&.collapse-comp {
|
||||
transform: translateY(-$offset);
|
||||
}
|
||||
|
||||
&.expanding {
|
||||
transform: translateX(0px);
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
transform: translateX(0px);
|
||||
}
|
||||
}
|
||||
|
||||
.collapse-item .text {
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
.attack-report.footer-text {
|
||||
text-align: right;
|
||||
font-size: 0.8em;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.attack-report div.attack-legend {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.attack-report .hidden-list{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.attack-technique-list-component .dropdown-list .btn-lg {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.attack-report .link-to-technique {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.attack-report .selected-technique {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.attack-report .padded{
|
||||
padding: 2em 2em 0 2em;
|
||||
}
|
||||
|
||||
.ReactTable .rt-td,.rt-resizable-header-content{
|
||||
white-space: pre-line !important;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.attack-link{
|
||||
padding: 0 7px 3px 7px !important;
|
||||
}
|
||||
|
||||
.attack-report .toggle-btn{
|
||||
position:relative;
|
||||
height:30px;
|
||||
width:30px;
|
||||
}
|
||||
|
||||
.attack-report .toggle-btn {
|
||||
padding: 0;
|
||||
margin-bottom: 30px;
|
||||
margin-left: 6px;
|
||||
outline: none !important;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.attack-report .toggle-btn svg {
|
||||
position:absolute;
|
||||
left:0;
|
||||
-webkit-transition: opacity 0.15s ease-in-out;
|
||||
-moz-transition: opacity 0.15s ease-in-out;
|
||||
-o-transition: opacity 0.15s ease-in-out;
|
||||
transition: opacity 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.attack-report .toggle-btn.toggled-on .switch-off{
|
||||
opacity:0;
|
||||
}
|
||||
|
||||
.attack-report .toggle-btn.toggled-off .switch-on{
|
||||
opacity:0;
|
||||
}
|
||||
|
||||
.attack-report .switch-off {
|
||||
-webkit-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.technique-status-icon {
|
||||
padding-right:5px;
|
||||
margin-bottom: 1px;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// colors
|
||||
$light-grey: #e0ddde;
|
||||
$light-blue: #ade3eb;
|
||||
$light-red: #d9acac;
|
||||
$black: #3a3a3a;
|
||||
|
||||
.attack-matrix .status-0 {
|
||||
background-color: $light-grey !important;
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.attack-matrix .status-1 {
|
||||
background-color: $light-blue !important;
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.attack-matrix .status-2 {
|
||||
background-color: $light-red !important;
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.attack-matrix div.rt-td:hover {
|
||||
transform: scale(1.08);
|
||||
filter: brightness(110%);
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.attack-matrix .rt-thead,.-header {
|
||||
border-bottom: 1px solid #0000000f;
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
.report-nav {
|
||||
margin-bottom: 2em !important;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000000;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.report-nav > li > a{
|
||||
height: 50px !important;
|
||||
}
|
||||
|
||||
.report-nav > li.active{
|
||||
font-weight: bold;
|
||||
}
|
Loading…
Reference in New Issue