Merge pull request #496 from VakarisZ/mitre_updates

ATT&CK UI/UX improvements
This commit is contained in:
VakarisZ 2019-12-04 16:30:37 +02:00 committed by GitHub
commit 34c2ff6bb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 14735 additions and 13956 deletions

View File

@ -48,7 +48,7 @@ script:
- cd - - cd -
- cd monkey_island/cc/ui - cd monkey_island/cc/ui
- eslint ./src --quiet - eslint ./src --quiet
- JS_WARNINGS_AMOUNT_UPPER_LIMIT=29 - JS_WARNINGS_AMOUNT_UPPER_LIMIT=37
- eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT - eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT
notifications: notifications:

View File

@ -1,6 +1,5 @@
import flask_restful import flask_restful
import json from flask import jsonify, request, json, current_app
from flask import jsonify, request
from monkey_island.cc.auth import jwt_required from monkey_island.cc.auth import jwt_required
from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.services.attack.attack_config import AttackConfig
@ -11,7 +10,11 @@ __author__ = "VakarisZ"
class AttackConfiguration(flask_restful.Resource): class AttackConfiguration(flask_restful.Resource):
@jwt_required() @jwt_required()
def get(self): 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() @jwt_required()
def post(self): def post(self):
@ -22,7 +25,7 @@ class AttackConfiguration(flask_restful.Resource):
config_json = json.loads(request.data) config_json = json.loads(request.data)
if 'reset_attack_matrix' in config_json: if 'reset_attack_matrix' in config_json:
AttackConfig.reset_config() AttackConfig.reset_config()
return jsonify(configuration=AttackConfig.get_config()['properties']) return jsonify(configuration=AttackConfig.get_config())
else: else:
AttackConfig.update_config({'properties': json.loads(request.data)}) AttackConfig.update_config({'properties': json.loads(request.data)})
AttackConfig.apply_to_monkey_config() AttackConfig.apply_to_monkey_config()

View File

@ -1,7 +1,8 @@
import flask_restful import flask_restful
from flask import jsonify
from monkey_island.cc.auth import jwt_required 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_report import AttackReportService
from monkey_island.cc.services.attack.attack_schema import SCHEMA
from flask import json, current_app
__author__ = "VakarisZ" __author__ = "VakarisZ"
@ -10,4 +11,9 @@ class AttackReport(flask_restful.Resource):
@jwt_required() @jwt_required()
def get(self): 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'])

View File

@ -15,7 +15,7 @@ class AttackConfig(object):
@staticmethod @staticmethod
def get_config(): def get_config():
config = mongo.db.attack.find_one({'name': 'newconfig'}) config = mongo.db.attack.find_one({'name': 'newconfig'})['properties']
return config return config
@staticmethod @staticmethod
@ -26,7 +26,7 @@ class AttackConfig(object):
:return: Technique object or None if technique is not found :return: Technique object or None if technique is not found
""" """
attack_config = AttackConfig.get_config() 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()): for type_key, technique in list(attack_type['properties'].items()):
if type_key == technique_id: if type_key == technique_id:
return technique return technique
@ -169,7 +169,19 @@ class AttackConfig(object):
""" """
attack_config = AttackConfig.get_config() attack_config = AttackConfig.get_config()
techniques = {} 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()): for key, technique in list(attack_type['properties'].items()):
techniques[key] = technique['value'] techniques[key] = technique['value']
return techniques 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

View File

@ -58,10 +58,11 @@ class AttackReportService:
'name': REPORT_NAME 'name': REPORT_NAME
} }
for tech_id, value in list(AttackConfig.get_technique_values().items()): for tech_id, tech_info in list(AttackConfig.get_techniques_for_report().items()):
if value:
try: 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: except KeyError as e:
LOG.error("Attack technique does not have it's report component added " LOG.error("Attack technique does not have it's report component added "
"to attack report service. %s" % e) "to attack report service. %s" % e)

View File

@ -2,64 +2,125 @@ SCHEMA = {
"title": "ATT&CK configuration", "title": "ATT&CK configuration",
"type": "object", "type": "object",
"properties": { "properties": {
"lateral_movement": { "execution": {
"title": "Lateral movement", "title": "Execution",
"type": "object", "type": "object",
"link": "https://attack.mitre.org/tactics/TA0002/",
"properties": { "properties": {
"T1210": { "T1059": {
"title": "T1210 Exploitation of Remote services", "title": "Command line interface",
"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",
"type": "bool", "type": "bool",
"value": True, "value": True,
"necessary": True, "necessary": True,
"description": "Files may be copied from one system to another to stage " "link": "https://attack.mitre.org/techniques/T1059",
"adversary tools or other files over the course of an operation." "description": "Adversaries may use command-line interfaces to interact with systems "
"and execute other software during the course of an operation.",
}, },
"T1021": { "T1129": {
"title": "T1021 Remote services", "title": "Execution through module load",
"type": "bool", "type": "bool",
"value": True, "value": True,
"necessary": False, "necessary": False,
"depends_on": ["T1110"], "link": "https://attack.mitre.org/techniques/T1129",
"description": "An adversary may use Valid Accounts to log into a service" "description": "The Windows module loader can be instructed to load DLLs from arbitrary "
" specifically designed to accept remote connections." "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": { "credential_access": {
"title": "Credential access", "title": "Credential access",
"type": "object", "type": "object",
"link": "https://attack.mitre.org/tactics/TA0006/",
"properties": { "properties": {
"T1110": { "T1110": {
"title": "T1110 Brute force", "title": "Brute force",
"type": "bool", "type": "bool",
"value": True, "value": True,
"necessary": False, "necessary": False,
"link": "https://attack.mitre.org/techniques/T1110",
"description": "Adversaries may use brute force techniques to attempt access to accounts " "description": "Adversaries may use brute force techniques to attempt access to accounts "
"when passwords are unknown or when password hashes are obtained.", "when passwords are unknown or when password hashes are obtained.",
"depends_on": ["T1210", "T1021"] "depends_on": ["T1210", "T1021"]
}, },
"T1003": { "T1003": {
"title": "T1003 Credential dumping", "title": "Credential dumping",
"type": "bool", "type": "bool",
"value": True, "value": True,
"necessary": False, "necessary": False,
"link": "https://attack.mitre.org/techniques/T1003",
"description": "Mapped with T1078 Valid Accounts because both techniques require" "description": "Mapped with T1078 Valid Accounts because both techniques require"
" same credential harvesting modules. " " same credential harvesting modules. "
"Credential dumping is the process of obtaining account login and password " "Credential dumping is the process of obtaining account login and password "
@ -68,10 +129,11 @@ SCHEMA = {
"depends_on": ["T1078"] "depends_on": ["T1078"]
}, },
"T1145": { "T1145": {
"title": "T1145 Private keys", "title": "Private keys",
"type": "bool", "type": "bool",
"value": True, "value": True,
"necessary": False, "necessary": False,
"link": "https://attack.mitre.org/techniques/T1145",
"description": "Adversaries may gather private keys from compromised systems for use in " "description": "Adversaries may gather private keys from compromised systems for use in "
"authenticating to Remote Services like SSH or for use in decrypting " "authenticating to Remote Services like SSH or for use in decrypting "
"other collected files such as email.", "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": { "discovery": {
"title": "Discovery", "title": "Discovery",
"type": "object", "type": "object",
"link": "https://attack.mitre.org/tactics/TA0007/",
"properties": { "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": { "T1082": {
"title": "T1082 System information discovery", "title": "System information discovery",
"type": "bool", "type": "bool",
"value": True, "value": True,
"necessary": False, "necessary": False,
"link": "https://attack.mitre.org/techniques/T1082",
"depends_on": ["T1016", "T1005"], "depends_on": ["T1016", "T1005"],
"description": "An adversary may attempt to get detailed information about the " "description": "An adversary may attempt to get detailed information about the "
"operating system and hardware, including version, patches, hotfixes, " "operating system and hardware, including version, patches, hotfixes, "
"service packs, and architecture." "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": { "T1016": {
"title": "T1016 System network configuration discovery", "title": "System network configuration discovery",
"type": "bool", "type": "bool",
"value": True, "value": True,
"necessary": False, "necessary": False,
"link": "https://attack.mitre.org/techniques/T1016",
"depends_on": ["T1005", "T1082"], "depends_on": ["T1005", "T1082"],
"description": "Adversaries will likely look for details about the network configuration " "description": "Adversaries will likely look for details about the network configuration "
"and settings of systems they access or through information discovery" "and settings of systems they access or through information discovery"
@ -200,15 +179,62 @@ SCHEMA = {
} }
} }
}, },
"collection": { "lateral_movement": {
"title": "Collection", "title": "Lateral movement",
"type": "object", "type": "object",
"link": "https://attack.mitre.org/tactics/TA0008/",
"properties": { "properties": {
"T1005": { "T1210": {
"title": "T1005 Data from local system", "title": "Exploitation of Remote services",
"type": "bool", "type": "bool",
"value": True, "value": True,
"necessary": False, "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"], "depends_on": ["T1016", "T1082"],
"description": "Sensitive data can be collected from local system sources, such as the file system " "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." "or databases of information residing on the system prior to Exfiltration."
@ -218,28 +244,32 @@ SCHEMA = {
"command_and_control": { "command_and_control": {
"title": "Command and Control", "title": "Command and Control",
"type": "object", "type": "object",
"link": "https://attack.mitre.org/tactics/TA0011/",
"properties": { "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": { "T1090": {
"title": "T1090 Connection proxy", "title": "Connection proxy",
"type": "bool", "type": "bool",
"value": True, "value": True,
"necessary": True, "necessary": True,
"link": "https://attack.mitre.org/techniques/T1090",
"description": "A connection proxy is used to direct network traffic between systems " "description": "A connection proxy is used to direct network traffic between systems "
"or act as an intermediary for network communications." "or act as an intermediary for network communications."
}, },
"T1188": { "T1065": {
"title": "T1188 Multi-hop proxy", "title": "Uncommonly used port",
"type": "bool", "type": "bool",
"value": True, "value": True,
"necessary": 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, " "description": "To disguise the source of malicious traffic, "
"adversaries may chain together multiple proxies." "adversaries may chain together multiple proxies."
} }
@ -248,12 +278,14 @@ SCHEMA = {
"exfiltration": { "exfiltration": {
"title": "Exfiltration", "title": "Exfiltration",
"type": "object", "type": "object",
"link": "https://attack.mitre.org/tactics/TA0010/",
"properties": { "properties": {
"T1041": { "T1041": {
"title": "T1041 Exfiltration Over Command and Control Channel", "title": "Exfiltration Over Command and Control Channel",
"type": "bool", "type": "bool",
"value": True, "value": True,
"necessary": True, "necessary": True,
"link": "https://attack.mitre.org/techniques/T1041",
"description": "Data exfiltration is performed over the Command and Control channel." "description": "Data exfiltration is performed over the Command and Control channel."
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -37,9 +37,9 @@
"css-loader": "^1.0.1", "css-loader": "^1.0.1",
"eslint": "^5.16.0", "eslint": "^5.16.0",
"eslint-loader": "^2.2.1", "eslint-loader": "^2.2.1",
"eslint-plugin-react": "^7.15.1", "eslint-plugin-react": "^7.16.0",
"file-loader": "^1.1.11", "file-loader": "^1.1.11",
"glob": "^7.1.4", "glob": "^7.1.6",
"html-loader": "^0.5.5", "html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"karma": "^3.1.4", "karma": "^3.1.4",
@ -56,21 +56,25 @@
"phantomjs-prebuilt": "^2.1.16", "phantomjs-prebuilt": "^2.1.16",
"react-addons-test-utils": "^15.6.2", "react-addons-test-utils": "^15.6.2",
"react-event-timeline": "^1.6.3", "react-event-timeline": "^1.6.3",
"react-hot-loader": "^4.12.14", "react-hot-loader": "^4.12.18",
"rimraf": "^2.7.1", "rimraf": "^2.7.1",
"style-loader": "^0.22.1", "style-loader": "^0.22.1",
"url-loader": "^1.1.2", "url-loader": "^1.1.2",
"webpack": "^4.41.0", "webpack": "^4.41.2",
"webpack-cli": "^3.3.9", "webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.8.1" "webpack-dev-server": "^3.9.0"
}, },
"dependencies": { "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", "@kunukn/react-collapse": "^1.2.7",
"bootstrap": "3.4.1", "bootstrap": "^3.4.1",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"core-js": "^2.6.9", "core-js": "^2.6.10",
"d3": "^5.11.0", "d3": "^5.14.1",
"downloadjs": "^1.4.7", "downloadjs": "^1.4.7",
"fetch": "^1.1.0", "fetch": "^1.1.0",
"file-saver": "^2.0.2", "file-saver": "^2.0.2",
@ -80,24 +84,24 @@
"moment": "^2.24.0", "moment": "^2.24.0",
"node-sass": "^4.13.0", "node-sass": "^4.13.0",
"normalize.css": "^8.0.0", "normalize.css": "^8.0.0",
"npm": "^6.11.3", "npm": "^6.13.1",
"pluralize": "^7.0.0", "pluralize": "^7.0.0",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"rainge": "^1.0.1", "rainge": "^1.0.1",
"rc-progress": "^2.5.2", "rc-progress": "^2.5.2",
"react": "^16.10.1", "react": "^16.12.0",
"react-bootstrap": "^0.32.4", "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-data-components": "^1.2.0",
"react-desktop-notification": "^1.0.9", "react-desktop-notification": "^1.0.9",
"react-dimensions": "^1.3.0", "react-dimensions": "^1.3.0",
"react-dom": "^16.10.1", "react-dom": "^16.12.0",
"react-fa": "^5.0.0", "react-fa": "^5.0.0",
"react-filepond": "^7.0.1", "react-filepond": "^7.0.1",
"react-graph-vis": "^1.0.2", "react-graph-vis": "^1.0.5",
"react-json-tree": "^0.11.2", "react-json-tree": "^0.11.2",
"react-jsonschema-form": "^1.8.0", "react-jsonschema-form": "^1.8.0",
"react-redux": "^5.1.1", "react-redux": "^5.1.2",
"react-router-dom": "^4.3.1", "react-router-dom": "^4.3.1",
"react-spinners": "^0.5.13", "react-spinners": "^0.5.13",
"react-table": "^6.10.3", "react-table": "^6.10.3",

View File

@ -1,7 +1,8 @@
import React from 'react'; 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 {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 RunServerPage from 'components/pages/RunServerPage';
import ConfigurePage from 'components/pages/ConfigurePage'; import ConfigurePage from 'components/pages/ConfigurePage';
@ -10,7 +11,6 @@ import MapPage from 'components/pages/MapPage';
import TelemetryPage from 'components/pages/TelemetryPage'; import TelemetryPage from 'components/pages/TelemetryPage';
import StartOverPage from 'components/pages/StartOverPage'; import StartOverPage from 'components/pages/StartOverPage';
import ReportPage from 'components/pages/ReportPage'; import ReportPage from 'components/pages/ReportPage';
import ZeroTrustReportPage from 'components/pages/ZeroTrustReportPage';
import LicensePage from 'components/pages/LicensePage'; import LicensePage from 'components/pages/LicensePage';
import AuthComponent from 'components/AuthComponent'; import AuthComponent from 'components/AuthComponent';
import LoginPageComponent from 'components/pages/LoginPage'; 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) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
@ -115,61 +122,57 @@ class AppComponent extends AuthComponent {
<Router> <Router>
<Grid fluid={true}> <Grid fluid={true}>
<Row> <Row>
<Col sm={3} md={2} className="sidebar"> <Col sm={3} md={2} className='sidebar'>
<div className="header"> <div className='header'>
<img src={logoImage} style={{width: '5vw', margin: '15px'}}/> <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> </div>
<ul className="navigation"> <ul className='navigation'>
<li> <li>
<NavLink to="/" exact={true}> <NavLink to='/' exact={true}>
<span className="number">1.</span> <span className='number'>1.</span>
Run Monkey Island Server Run Monkey Island Server
{this.state.completedSteps.run_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> </NavLink>
</li> </li>
<li> <li>
<NavLink to="/run-monkey"> <NavLink to='/run-monkey'>
<span className="number">2.</span> <span className='number'>2.</span>
Run Monkey Run Monkey
{this.state.completedSteps.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> </NavLink>
</li> </li>
<li> <li>
<NavLink to="/infection/map"> <NavLink to='/infection/map'>
<span className="number">3.</span> <span className='number'>3.</span>
Infection Map Infection Map
{this.state.completedSteps.infection_done ? {this.state.completedSteps.infection_done ?
<Icon name="check" className="pull-right checkmark text-success"/> <FontAwesomeIcon icon={faCheck} className='pull-right checkmark text-success'/>
: ''} : ''}
</NavLink> </NavLink>
</li> </li>
<li> <li>
<NavLink to="/report/security"> <NavLink to='/report/security'
<span className="number">4.</span> isActive={(match, location) => {
Security Report 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 ? {this.state.completedSteps.report_done ?
<Icon name="check" className="pull-right checkmark text-success"/> <FontAwesomeIcon icon={faCheck} className='pull-right checkmark text-success'/>
: ''} : ''}
</NavLink> </NavLink>
</li> </li>
<li> <li>
<NavLink to="/report/zero_trust"> <NavLink to='/start-over'>
<span className="number">5.</span> <span className='number'><FontAwesomeIcon icon={faUndo} style={{'marginLeft': '-1px'}}/></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>
Start Over Start Over
</NavLink> </NavLink>
</li> </li>
@ -177,23 +180,23 @@ class AppComponent extends AuthComponent {
<hr/> <hr/>
<ul> <ul>
<li><NavLink to="/configure">Configuration</NavLink></li> <li><NavLink to='/configure'>Configuration</NavLink></li>
<li><NavLink to="/infection/telemetry">Log</NavLink></li> <li><NavLink to='/infection/telemetry'>Log</NavLink></li>
</ul> </ul>
<hr/> <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> <span>Powered by</span>
<a href="http://www.guardicore.com" target="_blank"> <a href='http://www.guardicore.com' target='_blank'>
<img src={guardicoreLogoImage} alt="GuardiCore"/> <img src={guardicoreLogoImage} alt='GuardiCore'/>
</a> </a>
</div> </div>
<div className="license-link text-center"> <div className='license-link text-center'>
<NavLink to="/license">License</NavLink> <NavLink to='/license'>License</NavLink>
</div> </div>
<VersionComponent/> <VersionComponent/>
</Col> </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}/>)}/> <Route path='/login' render={() => (<LoginPageComponent onStatusChange={this.updateStatus}/>)}/>
{this.renderRoute('/', <RunServerPage onStatusChange={this.updateStatus}/>, true)} {this.renderRoute('/', <RunServerPage onStatusChange={this.updateStatus}/>, true)}
{this.renderRoute('/configure', <ConfigurePage onStatusChange={this.updateStatus}/>)} {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/map', <MapPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/infection/telemetry', <TelemetryPage onStatusChange={this.updateStatus}/>)} {this.renderRoute('/infection/telemetry', <TelemetryPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/start-over', <StartOverPage onStatusChange={this.updateStatus}/>)} {this.renderRoute('/start-over', <StartOverPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/report/security', <ReportPage onStatusChange={this.updateStatus}/>)} {this.redirectTo('/report', '/report/security')}
{this.renderRoute(reportZeroTrustRoute, <ZeroTrustReportPage onStatusChange={this.updateStatus}/>)} <Switch>
{this.renderRoute('/report/security', <ReportPage/>)}
{this.renderRoute('/report/attack', <ReportPage/>)}
{this.renderRoute('/report/zeroTrust', <ReportPage/>)}
</Switch>
{this.renderRoute('/license', <LicensePage onStatusChange={this.updateStatus}/>)} {this.renderRoute('/license', <LicensePage onStatusChange={this.updateStatus}/>)}
</Col> </Col>
</Row> </Row>

View File

@ -7,7 +7,11 @@ import 'filepond/dist/filepond.min.css';
import '../../styles/Tooltip.scss'; import '../../styles/Tooltip.scss';
import {Col} from 'react-bootstrap'; 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) { constructor(props) {
super(props); super(props);
this.state = {lastAction: 'none'} this.state = {lastAction: 'none'}
@ -37,12 +41,12 @@ class MatrixComponent extends AuthComponent {
if (i <= Object.keys(techType.properties).length) { if (i <= Object.keys(techType.properties).length) {
rowColumn.technique = Object.values(techType.properties)[i]; rowColumn.technique = Object.values(techType.properties)[i];
if (rowColumn.technique) { if (rowColumn.technique) {
rowColumn.technique.name = Object.keys(techType.properties)[i] rowColumn.technique.name = Object.keys(techType.properties)[i];
} }
} else { } else {
rowColumn.technique = null rowColumn.technique = null;
} }
row[rowColumn.techName] = rowColumn row[rowColumn.techName] = rowColumn;
}); });
techniques.push(row) techniques.push(row)
} }
@ -77,8 +81,8 @@ class MatrixComponent extends AuthComponent {
getTableData = (config) => { getTableData = (config) => {
let configCopy = JSON.parse(JSON.stringify(config)); let configCopy = JSON.parse(JSON.stringify(config));
let maxTechniques = MatrixComponent.findMaxTechniques(Object.values(configCopy)); let maxTechniques = ConfigMatrixComponent.findMaxTechniques(Object.values(configCopy));
let matrixTableData = MatrixComponent.parseTechniques(Object.values(configCopy), maxTechniques); let matrixTableData = ConfigMatrixComponent.parseTechniques(Object.values(configCopy), maxTechniques);
let columns = this.getColumns(matrixTableData); let columns = this.getColumns(matrixTableData);
return {'columns': columns, 'matrixTableData': matrixTableData, 'maxTechniques': maxTechniques} return {'columns': columns, 'matrixTableData': matrixTableData, 'maxTechniques': maxTechniques}
}; };
@ -87,15 +91,15 @@ class MatrixComponent extends AuthComponent {
return ( return (
<div id="header" className="row justify-content-between attack-legend"> <div id="header" className="row justify-content-between attack-legend">
<Col xs={4}> <Col xs={4}>
<i className="fa fa-circle-thin icon-unchecked"></i> <FontAwesomeIcon icon={faCircleThin} className="icon-unchecked"/>
<span> - Dissabled</span> <span> - Disabled</span>
</Col> </Col>
<Col xs={4}> <Col xs={4}>
<i className="fa fa-circle icon-checked"></i> <FontAwesomeIcon icon={faCircle} className="icon-checked"/>
<span> - Enabled</span> <span> - Enabled</span>
</Col> </Col>
<Col xs={4}> <Col xs={4}>
<i className="fa fa-circle icon-mandatory"></i> <FontAwesomeIcon icon={faCircle} className="icon-mandatory"/>
<span> - Mandatory</span> <span> - Mandatory</span>
</Col> </Col>
</div>) </div>)
@ -116,4 +120,4 @@ class MatrixComponent extends AuthComponent {
} }
} }
export default MatrixComponent; export default ConfigMatrixComponent;

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import '../../report-components/security/StolenPasswords' import '../../report-components/security/StolenPasswords'
import StolenPasswordsComponent from '../../report-components/security/StolenPasswords'; import StolenPasswordsComponent from '../../report-components/security/StolenPasswords';
import {ScanStatus} from './Helpers' import {ScanStatus} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachineFromSystemData, ScanStatus} from './Helpers'; import {renderMachineFromSystemData, ScanStatus} from './Helpers';

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachineFromSystemData, renderUsageFields, ScanStatus} from './Helpers' import {renderMachineFromSystemData, renderUsageFields, ScanStatus} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachineFromSystemData, renderMachine, ScanStatus} from './Helpers' import {renderMachineFromSystemData, renderMachine, ScanStatus} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachine, ScanStatus} from './Helpers' import {renderMachine, ScanStatus} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {getUsageColumns} from './Helpers' import {getUsageColumns} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {ScanStatus} from './Helpers'; import {ScanStatus} from './Helpers';

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachine, ScanStatus} from './Helpers' import {renderMachine, ScanStatus} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {getUsageColumns} from './Helpers' import {getUsageColumns} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
class T1065 extends React.Component { class T1065 extends React.Component {

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachine, ScanStatus} from './Helpers' import {renderMachine, ScanStatus} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachineFromSystemData, renderUsageFields, ScanStatus} from './Helpers' import {renderMachineFromSystemData, renderUsageFields, ScanStatus} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachine, ScanStatus} from './Helpers' import {renderMachine, ScanStatus} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachineFromSystemData, ScanStatus} from './Helpers' import {renderMachineFromSystemData, ScanStatus} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {ScanStatus} from './Helpers' import {ScanStatus} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {getUsageColumns} from './Helpers' import {getUsageColumns} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachineFromSystemData, ScanStatus} from './Helpers' import {renderMachineFromSystemData, ScanStatus} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachine, ScanStatus} from './Helpers' import {renderMachine, ScanStatus} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {getUsageColumns} from './Helpers'; import {getUsageColumns} from './Helpers';

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachineFromSystemData, ScanStatus} from './Helpers' import {renderMachineFromSystemData, ScanStatus} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachineFromSystemData, ScanStatus} from './Helpers' import {renderMachineFromSystemData, ScanStatus} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachine} from './Helpers' import {renderMachine} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachine} from './Helpers' import {renderMachine} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import '../../../styles/Collapse.scss'
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachine, ScanStatus} from './Helpers' import {renderMachine, ScanStatus} from './Helpers'

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import {Icon} from 'react-fa';
import Toggle from 'react-toggle'; import Toggle from 'react-toggle';
import {OverlayTrigger, Tooltip} from 'react-bootstrap'; import {OverlayTrigger, Tooltip} from 'react-bootstrap';
import download from 'downloadjs' import download from 'downloadjs'

View File

@ -1,5 +1,6 @@
import React from 'react'; 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 Toggle from 'react-toggle';
import {OverlayTrigger, Tooltip} from 'react-bootstrap'; import {OverlayTrigger, Tooltip} from 'react-bootstrap';
import download from 'downloadjs' import download from 'downloadjs'
@ -273,7 +274,7 @@ class PreviewPaneComponent extends AuthComponent {
<div className="preview-pane"> <div className="preview-pane">
{!info ? {!info ?
<span> <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 Select an item on the map for a detailed look
</span> </span>
: :

View File

@ -5,7 +5,7 @@ import FileSaver from 'file-saver';
import AuthComponent from '../AuthComponent'; import AuthComponent from '../AuthComponent';
import {FilePond} from 'react-filepond'; import {FilePond} from 'react-filepond';
import 'filepond/dist/filepond.min.css'; import 'filepond/dist/filepond.min.css';
import MatrixComponent from '../attack/MatrixComponent'; import ConfigMatrixComponent from '../attack/ConfigMatrixComponent';
const ATTACK_URL = '/api/attack'; const ATTACK_URL = '/api/attack';
const CONFIG_URL = '/api/configuration/island'; const CONFIG_URL = '/api/configuration/island';
@ -450,7 +450,7 @@ class ConfigurePageComponent extends AuthComponent {
} }
renderMatrix = () => { renderMatrix = () => {
return (<MatrixComponent configuration={this.state.attackConfig} return (<ConfigMatrixComponent configuration={this.state.attackConfig}
submit={this.componentDidMount} submit={this.componentDidMount}
reset={this.resetConfig} reset={this.resetConfig}
change={this.attackTechniqueChange}/>) change={this.attackTechniqueChange}/>)

View File

@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import {Col, Modal} from 'react-bootstrap'; import {Col, Modal} from 'react-bootstrap';
import {Link} from 'react-router-dom'; 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 InfMapPreviewPaneComponent from 'components/map/preview-pane/InfMapPreviewPane';
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
import {options, edgeGroupToColor} from 'components/map/MapOptions'; import {options, edgeGroupToColor} from 'components/map/MapOptions';
@ -157,13 +158,13 @@ class MapPageComponent extends AuthComponent {
<Col xs={8}> <Col xs={8}>
<div className="map-legend"> <div className="map-legend">
<b>Legend: </b> <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> <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> <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> <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> </div>
{this.renderTelemetryConsole()} {this.renderTelemetryConsole()}
<div style={{height: '80vh'}}> <div style={{height: '80vh'}}>
@ -180,7 +181,7 @@ class MapPageComponent extends AuthComponent {
Telemetry</Link> Telemetry</Link>
<button onClick={() => this.setState({showKillDialog: true})} className="btn btn-danger pull-right" <button onClick={() => this.setState({showKillDialog: true})} className="btn btn-danger pull-right"
style={{'width': '48%'}}> style={{'width': '48%'}}>
<Icon name="stop-circle" style={{'marginRight': '0.5em'}}/> <FontAwesomeIcon icon={faStopCircle} style={{'marginRight': '0.5em'}}/>
Kill All Monkeys Kill All Monkeys
</button> </button>
</div> </div>

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,9 @@ import {Button, Col, Well, Nav, NavItem, Collapse} from 'react-bootstrap';
import CopyToClipboard from 'react-copy-to-clipboard'; import CopyToClipboard from 'react-copy-to-clipboard';
import GridLoader from 'react-spinners/GridLoader'; 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 {Link} from 'react-router-dom';
import AuthComponent from '../AuthComponent'; import AuthComponent from '../AuthComponent';
import AwsRunTable from '../run-monkey/AwsRunTable'; import AwsRunTable from '../run-monkey/AwsRunTable';
@ -147,7 +149,7 @@ class RunMonkeyPageComponent extends AuthComponent {
<div style={{'overflow': 'auto', 'padding': '0.5em'}}> <div style={{'overflow': 'auto', 'padding': '0.5em'}}>
<CopyToClipboard text={cmdText} className="pull-right btn-sm"> <CopyToClipboard text={cmdText} className="pull-right btn-sm">
<Button style={{margin: '-0.5em'}} title="Copy to Clipboard"> <Button style={{margin: '-0.5em'}} title="Copy to Clipboard">
<Icon name="clipboard"/> <FontAwesomeIcon icon={faClipboard}/>
</Button> </Button>
</CopyToClipboard> </CopyToClipboard>
<code>{cmdText}</code> <code>{cmdText}</code>
@ -170,9 +172,9 @@ class RunMonkeyPageComponent extends AuthComponent {
static renderIconByState(state) { static renderIconByState(state) {
if (state === 'running') { 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') { } 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 { } else {
return ''; return '';
} }
@ -272,7 +274,7 @@ class RunMonkeyPageComponent extends AuthComponent {
className={'btn btn-default btn-md center-block'} className={'btn btn-default btn-md center-block'}
disabled={this.state.awsClicked}> disabled={this.state.awsClicked}>
Run on selected machines 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> </button>
</div> </div>
</div> </div>

View File

@ -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;

View File

@ -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;

View File

@ -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 Monkeys
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 Monkeys
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 isnt 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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -7,6 +7,7 @@ let monkeyLogoImage = require('../../../images/monkey-icon.svg');
export const ReportTypes = { export const ReportTypes = {
zeroTrust: 'Zero Trust', zeroTrust: 'Zero Trust',
security: 'Security', security: 'Security',
attack: 'ATT&CK',
null: '' null: ''
}; };

View File

@ -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;

View File

@ -3,6 +3,9 @@ import EventsModal from './EventsModal';
import {Badge, Button} from 'react-bootstrap'; import {Badge, Button} from 'react-bootstrap';
import * as PropTypes from 'prop-types'; 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 { export default class EventsButton extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -25,7 +28,7 @@ export default class EventsButton extends Component {
exportFilename={this.props.exportFilename}/> exportFilename={this.props.exportFilename}/>
<div className="text-center" style={{'display': 'grid'}}> <div className="text-center" style={{'display': 'grid'}}>
<Button className="btn btn-primary btn-lg" onClick={this.show}> <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> </Button>
</div> </div>
</Fragment>; </Fragment>;

View File

@ -2,12 +2,15 @@ import React, {Component} from 'react';
import {Button} from 'react-bootstrap'; import {Button} from 'react-bootstrap';
import * as PropTypes from 'prop-types'; 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 { export default class ExportEventsButton extends Component {
render() { render() {
return <Button className="btn btn-primary btn-lg" return <Button className="btn btn-primary btn-lg"
onClick={this.props.onClick} onClick={this.props.onClick}
> >
<i className="fa fa-download"/> Export <FontAwesomeIcon icon={faDownload}/> Export
</Button> </Button>
} }
} }

View File

@ -2,21 +2,24 @@ import React, {Component} from 'react';
import {statusToLabelType} from './StatusLabel'; import {statusToLabelType} from './StatusLabel';
import * as PropTypes from 'prop-types'; 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 = { const pillarToIcon = {
'Data': 'fa fa-database', 'Data': faDatabase,
'People': 'fa fa-user', 'People': faUser,
'Networks': 'fa fa-wifi', 'Networks': faWifi,
'Workloads': 'fa fa-cloud', 'Workloads': faCloud,
'Devices': 'fa fa-laptop', 'Devices': faLaptop,
'Visibility & Analytics': 'fa fa-eye-slash', 'Visibility & Analytics': faEyeSlash,
'Automation & Orchestration': 'fa fa-cogs' 'Automation & Orchestration': faCogs
}; };
export default class PillarLabel extends Component { export default class PillarLabel extends Component {
render() { render() {
const className = 'label ' + statusToLabelType[this.props.status]; const className = 'label ' + statusToLabelType[this.props.status];
return <div className={className} style={{margin: '2px', display: 'inline-block'}}><i return <div className={className} style={{margin: '2px', display: 'inline-block'}}>
className={pillarToIcon[this.props.pillar]}/> {this.props.pillar}</div> <FontAwesomeIcon icon={pillarToIcon[this.props.pillar]}/> {this.props.pillar}</div>
} }
} }

View File

@ -13,7 +13,7 @@ const columns = [
{ {
Header: 'Status', id: 'status', Header: 'Status', id: 'status',
accessor: x => { 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 maxWidth: MAX_WIDTH_STATUS_COLUMN
}, },

View File

@ -4,6 +4,9 @@ import {ZeroTrustStatuses} from './ZeroTrustPillars';
import {NavLink} from 'react-router-dom'; import {NavLink} from 'react-router-dom';
import {Panel} from 'react-bootstrap'; import {Panel} from 'react-bootstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronDown } from '@fortawesome/free-solid-svg-icons';
class ZeroTrustReportLegend extends Component { class ZeroTrustReportLegend extends Component {
render() { render() {
@ -13,7 +16,7 @@ class ZeroTrustReportLegend extends Component {
<Panel> <Panel>
<Panel.Heading> <Panel.Heading>
<Panel.Title toggle> <Panel.Title toggle>
<h3><i className="fa fa-chevron-down"/> Legend</h3> <h3><FontAwesomeIcon icon={faChevronDown} /> Legend</h3>
</Panel.Title> </Panel.Title>
</Panel.Heading> </Panel.Heading>
<Panel.Collapse> <Panel.Collapse>

View File

@ -5,6 +5,9 @@ import React from 'react';
import * as PropTypes from 'prop-types'; import * as PropTypes from 'prop-types';
import {Panel} from 'react-bootstrap'; 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 { export default class SinglePillarPrinciplesStatus extends AuthComponent {
render() { render() {
if (this.props.principlesStatus.length === 0) { if (this.props.principlesStatus.length === 0) {
@ -15,7 +18,7 @@ export default class SinglePillarPrinciplesStatus extends AuthComponent {
<Panel.Heading> <Panel.Heading>
<Panel.Title toggle> <Panel.Title toggle>
<h3 style={{textAlign: 'center', marginTop: '1px', marginBottom: '1px'}}> <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]}/> status={this.props.pillarsToStatuses[this.props.pillar]}/>
</h3> </h3>
</Panel.Title> </Panel.Title>

View File

@ -1,11 +1,14 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import * as PropTypes from 'prop-types'; 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 = { const statusToIcon = {
'Passed': 'fa-check', 'Passed': faCheck,
'Verify': 'fa-exclamation-triangle', 'Verify': faExclamationTriangle,
'Failed': 'fa-bomb', 'Failed': faBomb,
'Unexecuted': 'fa-question' 'Unexecuted': faQuestion
}; };
export const statusToLabelType = { export const statusToLabelType = {
@ -24,7 +27,7 @@ export default class StatusLabel extends Component {
return ( return (
<div className={'label ' + statusToLabelType[this.props.status]} style={{display: 'flow-root'}}> <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> </div>
); );
} }

View File

@ -1,6 +1,5 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import {Col, Grid, Row} from 'react-bootstrap'; import {Col, Grid, Row} from 'react-bootstrap';
import MonkeysStillAliveWarning from '../common/MonkeysStillAliveWarning';
import PillarsOverview from './PillarOverview'; import PillarsOverview from './PillarOverview';
import ZeroTrustReportLegend from './ReportLegend'; import ZeroTrustReportLegend from './ReportLegend';
import * as PropTypes from 'prop-types'; import * as PropTypes from 'prop-types';
@ -12,7 +11,6 @@ export default class SummarySection extends Component {
<Grid fluid={true}> <Grid fluid={true}>
<Row> <Row>
<Col xs={12} sm={12} md={12} lg={12}> <Col xs={12} sm={12} md={12} lg={12}>
<MonkeysStillAliveWarning allMonkeysAreDead={this.props.allMonkeysAreDead}/>
<p> <p>
Get a quick glance at how your network aligns with the <a 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"> 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> </Col>
</Row> </Row>
<Row className="show-grid"> <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} <PillarsOverview pillarsToStatuses={this.props.pillars.pillarsToStatuses}
grades={this.props.pillars.grades}/> grades={this.props.pillars.grades}/>
</Col> </Col>
<Col xs={4} sm={4} md={4} lg={4}> <Col xs={4} sm={4} md={5} lg={5}>
<ZeroTrustReportLegend/> <ZeroTrustReportLegend/>
</Col> </Col>
</Row> </Row>
@ -36,6 +34,5 @@ export default class SummarySection extends Component {
} }
SummarySection.propTypes = { SummarySection.propTypes = {
allMonkeysAreDead: PropTypes.bool,
pillars: PropTypes.object pillars: PropTypes.object
}; };

View File

@ -1,4 +1,5 @@
@import url('https://fonts.googleapis.com/css?family=Noto+Sans&display=swap'); @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 { body {
margin: 0; margin: 0;
@ -6,12 +7,10 @@ body {
} }
svg { svg {
-webkit-touch-callout: none; /* iOS Safari */ -webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */ -webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */ -khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Firefox */ -moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */ -ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */ user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */
} }

View File

@ -1,5 +1,7 @@
import React from 'react'; 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 { class VersionComponent extends React.Component {
constructor(props) { constructor(props) {
@ -32,7 +34,7 @@ class VersionComponent extends React.Component {
<div> <div>
<b>Newer version available!</b> <b>Newer version available!</b>
<br/> <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> </div>
: :
undefined undefined

View File

@ -14,10 +14,18 @@ class CheckboxComponent extends React.PureComponent {
changeHandler(name, checked) function will be called with these parameters: changeHandler(name, checked) function will be called with these parameters:
this.props.name (the name of this component) and this.props.name (the name of this component) and
this.state.checked (boolean indicating if this component is checked or not) 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) { constructor(props) {
super(props); super(props);
if (this.props.hasOwnProperty('status')){
this.status = this.props.status;
} else {
this.status = false
}
this.state = { this.state = {
status: this.status,
checked: this.props.checked, checked: this.props.checked,
necessary: this.props.necessary, necessary: this.props.necessary,
isAnimating: false isAnimating: false
@ -48,6 +56,9 @@ class CheckboxComponent extends React.PureComponent {
// Creates class string for component // Creates class string for component
composeStateClasses(core) { composeStateClasses(core) {
let result = core; let result = core;
if (this.state.status !== false) {
result += ' status-'+this.state.status;
}
if (this.state.necessary) { if (this.state.necessary) {
return result + ' blocked' return result + ' blocked'
} }
@ -56,7 +67,6 @@ class CheckboxComponent extends React.PureComponent {
} else { } else {
result += ' is-unchecked' result += ' is-unchecked'
} }
if (this.state.isAnimating) { if (this.state.isAnimating) {
result += ' do-ping'; result += ' do-ping';
} }
@ -69,11 +79,11 @@ class CheckboxComponent extends React.PureComponent {
<div <div
className={cl} className={cl}
onClick={this.state.necessary ? void (0) : this.toggleChecked}> onClick={this.state.necessary ? void (0) : this.toggleChecked}>
<input className="ui ui-checkbox" <input className='ui ui-checkbox'
type="checkbox" value={this.state.checked} type='checkbox' value={this.state.checked}
name={this.props.name}/> name={this.props.name}/>
<label className="text">{this.props.children}</label> <label className='text'>{this.props.children}</label>
<div className="ui-btn-ping" onTransitionEnd={this.stopAnimation}></div> <div className='ui-btn-ping' onTransitionEnd={this.stopAnimation}></div>
</div> </div>
) )
} }

View File

@ -525,7 +525,6 @@ body {
.label-danger { .label-danger {
background-color: #d9534f !important; background-color: #d9534f !important;
} }
} }
/* Attack pages */ /* Attack pages */
@ -537,10 +536,6 @@ body {
margin-bottom: 20px; margin-bottom: 20px;
} }
.attack-report .btn-collapse span:nth-of-type(2) {
flex: 0;
}
.icon-info { .icon-info {
color: #ade3eb !important; color: #ade3eb !important;
} }
@ -588,8 +583,3 @@ body {
margin-right: auto; margin-right: auto;
} }
.attack-report.footer-text {
text-align: right;
font-size: 0.8em;
margin-top: 20px;
}

View File

@ -12,6 +12,7 @@ $black: #000000;
text-align: center; text-align: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
cursor: pointer;
input { input {
display: none; display: none;
@ -39,6 +40,7 @@ $black: #000000;
background-color: $dark-green; background-color: $dark-green;
color: $light-grey; color: $light-grey;
fill: $light-grey; fill: $light-grey;
cursor: default;
} }
&.is-checked { &.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 { .icon-checked {
color: $green color: $green
} }
.icon-mandatory { .icon-mandatory {
color: $dark-green color: $dark-green;
} }
.icon-unchecked { .icon-unchecked {

View File

@ -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; $danger-color: #d9acac;
$info-color: #ade3eb; $info-color: #ade3eb;
@ -68,27 +68,12 @@ $default-color: #e0ddde;
overflow: hidden; overflow: hidden;
} }
.collapsing {
height:100% !important;
}
.collapse-item .content { .collapse-item .content {
padding: 2rem 0; 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 { .collapse-item .text {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}