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 monkey_island/cc/ui
- eslint ./src --quiet
- JS_WARNINGS_AMOUNT_UPPER_LIMIT=29
- JS_WARNINGS_AMOUNT_UPPER_LIMIT=37
- eslint ./src --max-warnings $JS_WARNINGS_AMOUNT_UPPER_LIMIT
notifications:

View File

@ -1,6 +1,5 @@
import flask_restful
import json
from flask import jsonify, request
from flask import jsonify, request, json, current_app
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.services.attack.attack_config import AttackConfig
@ -11,7 +10,11 @@ __author__ = "VakarisZ"
class AttackConfiguration(flask_restful.Resource):
@jwt_required()
def get(self):
return jsonify(configuration=AttackConfig.get_config()['properties'])
return current_app.response_class(json.dumps({"configuration": AttackConfig.get_config()},
indent=None,
separators=(",", ":"),
sort_keys=False) + "\n",
mimetype=current_app.config['JSONIFY_MIMETYPE'])
@jwt_required()
def post(self):
@ -22,7 +25,7 @@ class AttackConfiguration(flask_restful.Resource):
config_json = json.loads(request.data)
if 'reset_attack_matrix' in config_json:
AttackConfig.reset_config()
return jsonify(configuration=AttackConfig.get_config()['properties'])
return jsonify(configuration=AttackConfig.get_config())
else:
AttackConfig.update_config({'properties': json.loads(request.data)})
AttackConfig.apply_to_monkey_config()

View File

@ -1,7 +1,8 @@
import flask_restful
from flask import jsonify
from monkey_island.cc.auth import jwt_required
from monkey_island.cc.services.attack.attack_report import AttackReportService
from monkey_island.cc.services.attack.attack_schema import SCHEMA
from flask import json, current_app
__author__ = "VakarisZ"
@ -10,4 +11,9 @@ class AttackReport(flask_restful.Resource):
@jwt_required()
def get(self):
return jsonify(AttackReportService.get_latest_report()['techniques'])
response_content = {'techniques': AttackReportService.get_latest_report()['techniques'], 'schema': SCHEMA}
return current_app.response_class(json.dumps(response_content,
indent=None,
separators=(",", ":"),
sort_keys=False) + "\n",
mimetype=current_app.config['JSONIFY_MIMETYPE'])

View File

@ -15,7 +15,7 @@ class AttackConfig(object):
@staticmethod
def get_config():
config = mongo.db.attack.find_one({'name': 'newconfig'})
config = mongo.db.attack.find_one({'name': 'newconfig'})['properties']
return config
@staticmethod
@ -26,7 +26,7 @@ class AttackConfig(object):
:return: Technique object or None if technique is not found
"""
attack_config = AttackConfig.get_config()
for config_key, attack_type in list(attack_config['properties'].items()):
for config_key, attack_type in list(attack_config.items()):
for type_key, technique in list(attack_type['properties'].items()):
if type_key == technique_id:
return technique
@ -169,7 +169,19 @@ class AttackConfig(object):
"""
attack_config = AttackConfig.get_config()
techniques = {}
for type_name, attack_type in list(attack_config['properties'].items()):
for type_name, attack_type in list(attack_config.items()):
for key, technique in list(attack_type['properties'].items()):
techniques[key] = technique['value']
return techniques
@staticmethod
def get_techniques_for_report():
"""
:return: Format: {"T1110": {"selected": True, "type": "Credential Access", "T1075": ...}
"""
attack_config = AttackConfig.get_config()
techniques = {}
for type_name, attack_type in list(attack_config.items()):
for key, technique in list(attack_type['properties'].items()):
techniques[key] = {'selected': technique['value'], 'type': SCHEMA['properties'][type_name]['title']}
return techniques

View File

@ -58,13 +58,14 @@ class AttackReportService:
'name': REPORT_NAME
}
for tech_id, value in list(AttackConfig.get_technique_values().items()):
if value:
try:
report['techniques'].update({tech_id: TECHNIQUES[tech_id].get_report_data()})
except KeyError as e:
LOG.error("Attack technique does not have it's report component added "
"to attack report service. %s" % e)
for tech_id, tech_info in list(AttackConfig.get_techniques_for_report().items()):
try:
technique_report_data = TECHNIQUES[tech_id].get_report_data()
technique_report_data.update(tech_info)
report['techniques'].update({tech_id: technique_report_data})
except KeyError as e:
LOG.error("Attack technique does not have it's report component added "
"to attack report service. %s" % e)
mongo.db.attack_report.replace_one({'name': REPORT_NAME}, report, upsert=True)
return report

View File

@ -2,64 +2,125 @@ SCHEMA = {
"title": "ATT&CK configuration",
"type": "object",
"properties": {
"lateral_movement": {
"title": "Lateral movement",
"execution": {
"title": "Execution",
"type": "object",
"link": "https://attack.mitre.org/tactics/TA0002/",
"properties": {
"T1210": {
"title": "T1210 Exploitation of Remote services",
"type": "bool",
"value": True,
"necessary": False,
"description": "Exploitation of a software vulnerability occurs when an adversary "
"takes advantage of a programming error in a program, service, or within the "
"operating system software or kernel itself to execute adversary-controlled code."
},
"T1075": {
"title": "T1075 Pass the hash",
"type": "bool",
"value": True,
"necessary": False,
"description": "Pass the hash (PtH) is a method of authenticating as a user without "
"having access to the user's cleartext password."
},
"T1105": {
"title": "T1105 Remote file copy",
"T1059": {
"title": "Command line interface",
"type": "bool",
"value": True,
"necessary": True,
"description": "Files may be copied from one system to another to stage "
"adversary tools or other files over the course of an operation."
"link": "https://attack.mitre.org/techniques/T1059",
"description": "Adversaries may use command-line interfaces to interact with systems "
"and execute other software during the course of an operation.",
},
"T1021": {
"title": "T1021 Remote services",
"T1129": {
"title": "Execution through module load",
"type": "bool",
"value": True,
"necessary": False,
"depends_on": ["T1110"],
"description": "An adversary may use Valid Accounts to log into a service"
" specifically designed to accept remote connections."
"link": "https://attack.mitre.org/techniques/T1129",
"description": "The Windows module loader can be instructed to load DLLs from arbitrary "
"local paths and arbitrary Universal Naming Convention (UNC) network paths.",
"depends_on": ["T1078", "T1003"]
},
"T1106": {
"title": "Execution through API",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1106",
"description": "Adversary tools may directly use the Windows application "
"programming interface (API) to execute binaries.",
"depends_on": ["T1210"]
},
"T1086": {
"title": "Powershell",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1086",
"description": "Adversaries can use PowerShell to perform a number of actions,"
" including discovery of information and execution of code.",
},
"T1064": {
"title": "Scripting",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1064",
"description": "Adversaries may use scripts to aid in operations and "
"perform multiple actions that would otherwise be manual.",
},
"T1035": {
"title": "Service execution",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1035",
"description": "Adversaries may execute a binary, command, or script via a method "
"that interacts with Windows services, such as the Service Control Manager.",
"depends_on": ["T1210"]
}
}
},
"defence_evasion": {
"title": "Defence evasion",
"type": "object",
"link": "https://attack.mitre.org/tactics/TA0005/",
"properties": {
"T1197": {
"title": "BITS jobs",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1197",
"description": "Adversaries may abuse BITS to download, execute, "
"and even clean up after running malicious code."
},
"T1107": {
"title": "File Deletion",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1107",
"description": "Adversaries may remove files over the course of an intrusion "
"to keep their footprint low or remove them at the end as part "
"of the post-intrusion cleanup process."
},
"T1222": {
"title": "File permissions modification",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1222",
"description": "Adversaries may modify file permissions/attributes to evade intended DACLs."
}
}
},
"credential_access": {
"title": "Credential access",
"type": "object",
"link": "https://attack.mitre.org/tactics/TA0006/",
"properties": {
"T1110": {
"title": "T1110 Brute force",
"title": "Brute force",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1110",
"description": "Adversaries may use brute force techniques to attempt access to accounts "
"when passwords are unknown or when password hashes are obtained.",
"depends_on": ["T1210", "T1021"]
},
"T1003": {
"title": "T1003 Credential dumping",
"title": "Credential dumping",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1003",
"description": "Mapped with T1078 Valid Accounts because both techniques require"
" same credential harvesting modules. "
"Credential dumping is the process of obtaining account login and password "
@ -68,10 +129,11 @@ SCHEMA = {
"depends_on": ["T1078"]
},
"T1145": {
"title": "T1145 Private keys",
"title": "Private keys",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1145",
"description": "Adversaries may gather private keys from compromised systems for use in "
"authenticating to Remote Services like SSH or for use in decrypting "
"other collected files such as email.",
@ -79,120 +141,37 @@ SCHEMA = {
}
}
},
"defence_evasion": {
"title": "Defence evasion",
"type": "object",
"properties": {
"T1197": {
"title": "T1197 BITS jobs",
"type": "bool",
"value": True,
"necessary": True,
"description": "Adversaries may abuse BITS to download, execute, "
"and even clean up after running malicious code."
},
"T1107": {
"title": "T1107 File Deletion",
"type": "bool",
"value": True,
"necessary": True,
"description": "Adversaries may remove files over the course of an intrusion "
"to keep their footprint low or remove them at the end as part "
"of the post-intrusion cleanup process."
},
"T1222": {
"title": "T1222 File permissions modification",
"type": "bool",
"value": True,
"necessary": True,
"description": "Adversaries may modify file permissions/attributes to evade intended DACLs."
}
}
},
"execution": {
"title": "Execution",
"type": "object",
"properties": {
"T1035": {
"title": "T1035 Service execution",
"type": "bool",
"value": True,
"necessary": False,
"description": "Adversaries may execute a binary, command, or script via a method "
"that interacts with Windows services, such as the Service Control Manager.",
"depends_on": ["T1210"]
},
"T1129": {
"title": "T1129 Execution through module load",
"type": "bool",
"value": True,
"necessary": False,
"description": "The Windows module loader can be instructed to load DLLs from arbitrary "
"local paths and arbitrary Universal Naming Convention (UNC) network paths.",
"depends_on": ["T1078", "T1003"]
},
"T1106": {
"title": "T1106 Execution through API",
"type": "bool",
"value": True,
"necessary": False,
"description": "Adversary tools may directly use the Windows application "
"programming interface (API) to execute binaries.",
"depends_on": ["T1210"]
},
"T1059": {
"title": "T1059 Command line interface",
"type": "bool",
"value": True,
"necessary": True,
"description": "Adversaries may use command-line interfaces to interact with systems "
"and execute other software during the course of an operation.",
},
"T1086": {
"title": "T1086 Powershell",
"type": "bool",
"value": True,
"necessary": True,
"description": "Adversaries can use PowerShell to perform a number of actions,"
" including discovery of information and execution of code.",
},
"T1064": {
"title": "T1064 Scripting",
"type": "bool",
"value": True,
"necessary": True,
"description": "Adversaries may use scripts to aid in operations and "
"perform multiple actions that would otherwise be manual.",
}
}
},
"discovery": {
"title": "Discovery",
"type": "object",
"link": "https://attack.mitre.org/tactics/TA0007/",
"properties": {
"T1018": {
"title": "Remote System Discovery",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1018",
"description": "Adversaries will likely attempt to get a listing of other systems by IP address, "
"hostname, or other logical identifier on a network for lateral movement."
},
"T1082": {
"title": "T1082 System information discovery",
"title": "System information discovery",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1082",
"depends_on": ["T1016", "T1005"],
"description": "An adversary may attempt to get detailed information about the "
"operating system and hardware, including version, patches, hotfixes, "
"service packs, and architecture."
},
"T1018": {
"title": "T1018 Remote System Discovery",
"type": "bool",
"value": True,
"necessary": True,
"description": "Adversaries will likely attempt to get a listing of other systems by IP address, "
"hostname, or other logical identifier on a network for lateral movement."
},
"T1016": {
"title": "T1016 System network configuration discovery",
"title": "System network configuration discovery",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1016",
"depends_on": ["T1005", "T1082"],
"description": "Adversaries will likely look for details about the network configuration "
"and settings of systems they access or through information discovery"
@ -200,15 +179,62 @@ SCHEMA = {
}
}
},
"collection": {
"title": "Collection",
"lateral_movement": {
"title": "Lateral movement",
"type": "object",
"link": "https://attack.mitre.org/tactics/TA0008/",
"properties": {
"T1005": {
"title": "T1005 Data from local system",
"T1210": {
"title": "Exploitation of Remote services",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1210",
"description": "Exploitation of a software vulnerability occurs when an adversary "
"takes advantage of a programming error in a program, service, or within the "
"operating system software or kernel itself to execute adversary-controlled code."
},
"T1075": {
"title": "Pass the hash",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1075",
"description": "Pass the hash (PtH) is a method of authenticating as a user without "
"having access to the user's cleartext password."
},
"T1105": {
"title": "Remote file copy",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1105",
"description": "Files may be copied from one system to another to stage "
"adversary tools or other files over the course of an operation."
},
"T1021": {
"title": "Remote services",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1021",
"depends_on": ["T1110"],
"description": "An adversary may use Valid Accounts to log into a service"
" specifically designed to accept remote connections."
}
}
},
"collection": {
"title": "Collection",
"type": "object",
"link": "https://attack.mitre.org/tactics/TA0009/",
"properties": {
"T1005": {
"title": "Data from local system",
"type": "bool",
"value": True,
"necessary": False,
"link": "https://attack.mitre.org/techniques/T1005",
"depends_on": ["T1016", "T1082"],
"description": "Sensitive data can be collected from local system sources, such as the file system "
"or databases of information residing on the system prior to Exfiltration."
@ -218,28 +244,32 @@ SCHEMA = {
"command_and_control": {
"title": "Command and Control",
"type": "object",
"link": "https://attack.mitre.org/tactics/TA0011/",
"properties": {
"T1065": {
"title": "T1065 Uncommonly used port",
"type": "bool",
"value": True,
"necessary": True,
"description": "Adversaries may conduct C2 communications over a non-standard "
"port to bypass proxies and firewalls that have been improperly configured."
},
"T1090": {
"title": "T1090 Connection proxy",
"title": "Connection proxy",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1090",
"description": "A connection proxy is used to direct network traffic between systems "
"or act as an intermediary for network communications."
},
"T1188": {
"title": "T1188 Multi-hop proxy",
"T1065": {
"title": "Uncommonly used port",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1065",
"description": "Adversaries may conduct C2 communications over a non-standard "
"port to bypass proxies and firewalls that have been improperly configured."
},
"T1188": {
"title": "Multi-hop proxy",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1188",
"description": "To disguise the source of malicious traffic, "
"adversaries may chain together multiple proxies."
}
@ -248,12 +278,14 @@ SCHEMA = {
"exfiltration": {
"title": "Exfiltration",
"type": "object",
"link": "https://attack.mitre.org/tactics/TA0010/",
"properties": {
"T1041": {
"title": "T1041 Exfiltration Over Command and Control Channel",
"title": "Exfiltration Over Command and Control Channel",
"type": "bool",
"value": True,
"necessary": True,
"link": "https://attack.mitre.org/techniques/T1041",
"description": "Data exfiltration is performed over the Command and Control channel."
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,7 +1,8 @@
import React from 'react';
import {BrowserRouter as Router, NavLink, Redirect, Route} from 'react-router-dom';
import {BrowserRouter as Router, NavLink, Redirect, Route, Switch} from 'react-router-dom';
import {Col, Grid, Row} from 'react-bootstrap';
import {Icon} from 'react-fa';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faCheck, faUndo } from '@fortawesome/free-solid-svg-icons'
import RunServerPage from 'components/pages/RunServerPage';
import ConfigurePage from 'components/pages/ConfigurePage';
@ -10,7 +11,6 @@ import MapPage from 'components/pages/MapPage';
import TelemetryPage from 'components/pages/TelemetryPage';
import StartOverPage from 'components/pages/StartOverPage';
import ReportPage from 'components/pages/ReportPage';
import ZeroTrustReportPage from 'components/pages/ZeroTrustReportPage';
import LicensePage from 'components/pages/LicensePage';
import AuthComponent from 'components/AuthComponent';
import LoginPageComponent from 'components/pages/LoginPage';
@ -82,6 +82,13 @@ class AppComponent extends AuthComponent {
}
};
redirectTo = (userPath, targetPath) => {
let pathQuery = new RegExp(userPath+'[\/]?$', 'g');
if(window.location.pathname.match(pathQuery)){
return <Redirect to={{pathname: targetPath}}/>
}
};
constructor(props) {
super(props);
this.state = {
@ -115,61 +122,57 @@ class AppComponent extends AuthComponent {
<Router>
<Grid fluid={true}>
<Row>
<Col sm={3} md={2} className="sidebar">
<div className="header">
<Col sm={3} md={2} className='sidebar'>
<div className='header'>
<img src={logoImage} style={{width: '5vw', margin: '15px'}}/>
<img src={infectionMonkeyImage} style={{width: '15vw'}} alt="Infection Monkey"/>
<img src={infectionMonkeyImage} style={{width: '15vw'}} alt='Infection Monkey'/>
</div>
<ul className="navigation">
<ul className='navigation'>
<li>
<NavLink to="/" exact={true}>
<span className="number">1.</span>
<NavLink to='/' exact={true}>
<span className='number'>1.</span>
Run Monkey Island Server
{this.state.completedSteps.run_server ?
<Icon name="check" className="pull-right checkmark text-success"/>
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark text-success'/>
: ''}
</NavLink>
</li>
<li>
<NavLink to="/run-monkey">
<span className="number">2.</span>
<NavLink to='/run-monkey'>
<span className='number'>2.</span>
Run Monkey
{this.state.completedSteps.run_monkey ?
<Icon name="check" className="pull-right checkmark text-success"/>
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark text-success'/>
: ''}
</NavLink>
</li>
<li>
<NavLink to="/infection/map">
<span className="number">3.</span>
<NavLink to='/infection/map'>
<span className='number'>3.</span>
Infection Map
{this.state.completedSteps.infection_done ?
<Icon name="check" className="pull-right checkmark text-success"/>
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark text-success'/>
: ''}
</NavLink>
</li>
<li>
<NavLink to="/report/security">
<span className="number">4.</span>
Security Report
<NavLink to='/report/security'
isActive={(match, location) => {
return (location.pathname === '/report/attack'
|| location.pathname === '/report/zeroTrust'
|| location.pathname === '/report/security')
}}>
<span className='number'>4.</span>
Security Reports
{this.state.completedSteps.report_done ?
<Icon name="check" className="pull-right checkmark text-success"/>
<FontAwesomeIcon icon={faCheck} className='pull-right checkmark text-success'/>
: ''}
</NavLink>
</li>
<li>
<NavLink to="/report/zero_trust">
<span className="number">5.</span>
Zero Trust Report
{this.state.completedSteps.report_done ?
<Icon name="check" className="pull-right checkmark text-success"/>
: ''}
</NavLink>
</li>
<li>
<NavLink to="/start-over">
<span className="number"><i className="fa fa-undo" style={{'marginLeft': '-1px'}}/></span>
<NavLink to='/start-over'>
<span className='number'><FontAwesomeIcon icon={faUndo} style={{'marginLeft': '-1px'}}/></span>
Start Over
</NavLink>
</li>
@ -177,23 +180,23 @@ class AppComponent extends AuthComponent {
<hr/>
<ul>
<li><NavLink to="/configure">Configuration</NavLink></li>
<li><NavLink to="/infection/telemetry">Log</NavLink></li>
<li><NavLink to='/configure'>Configuration</NavLink></li>
<li><NavLink to='/infection/telemetry'>Log</NavLink></li>
</ul>
<hr/>
<div className="guardicore-link text-center" style={{'marginBottom': '0.5em'}}>
<div className='guardicore-link text-center' style={{'marginBottom': '0.5em'}}>
<span>Powered by</span>
<a href="http://www.guardicore.com" target="_blank">
<img src={guardicoreLogoImage} alt="GuardiCore"/>
<a href='http://www.guardicore.com' target='_blank'>
<img src={guardicoreLogoImage} alt='GuardiCore'/>
</a>
</div>
<div className="license-link text-center">
<NavLink to="/license">License</NavLink>
<div className='license-link text-center'>
<NavLink to='/license'>License</NavLink>
</div>
<VersionComponent/>
</Col>
<Col sm={9} md={10} smOffset={3} mdOffset={2} className="main">
<Col sm={9} md={10} smOffset={3} mdOffset={2} className='main'>
<Route path='/login' render={() => (<LoginPageComponent onStatusChange={this.updateStatus}/>)}/>
{this.renderRoute('/', <RunServerPage onStatusChange={this.updateStatus}/>, true)}
{this.renderRoute('/configure', <ConfigurePage onStatusChange={this.updateStatus}/>)}
@ -201,8 +204,12 @@ class AppComponent extends AuthComponent {
{this.renderRoute('/infection/map', <MapPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/infection/telemetry', <TelemetryPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/start-over', <StartOverPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute('/report/security', <ReportPage onStatusChange={this.updateStatus}/>)}
{this.renderRoute(reportZeroTrustRoute, <ZeroTrustReportPage onStatusChange={this.updateStatus}/>)}
{this.redirectTo('/report', '/report/security')}
<Switch>
{this.renderRoute('/report/security', <ReportPage/>)}
{this.renderRoute('/report/attack', <ReportPage/>)}
{this.renderRoute('/report/zeroTrust', <ReportPage/>)}
</Switch>
{this.renderRoute('/license', <LicensePage onStatusChange={this.updateStatus}/>)}
</Col>
</Row>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
import React from 'react';
import {Icon} from 'react-fa';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faHandPointLeft } from '@fortawesome/free-solid-svg-icons'
import Toggle from 'react-toggle';
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
import download from 'downloadjs'
@ -273,7 +274,7 @@ class PreviewPaneComponent extends AuthComponent {
<div className="preview-pane">
{!info ?
<span>
<Icon name="hand-o-left" style={{'marginRight': '0.5em'}}/>
<FontAwesomeIcon icon={faHandPointLeft} style={{'marginRight': '0.5em'}}/>
Select an item on the map for a detailed look
</span>
:

View File

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

View File

@ -1,7 +1,8 @@
import React from 'react';
import {Col, Modal} from 'react-bootstrap';
import {Link} from 'react-router-dom';
import {Icon} from 'react-fa';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faStopCircle, faMinus } from '@fortawesome/free-solid-svg-icons'
import InfMapPreviewPaneComponent from 'components/map/preview-pane/InfMapPreviewPane';
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
import {options, edgeGroupToColor} from 'components/map/MapOptions';
@ -157,13 +158,13 @@ class MapPageComponent extends AuthComponent {
<Col xs={8}>
<div className="map-legend">
<b>Legend: </b>
<span>Exploit <i className="fa fa-lg fa-minus" style={{color: '#cc0200'}}/></span>
<span>Exploit <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#cc0200'}}/></span>
<b style={{color: '#aeaeae'}}> | </b>
<span>Scan <i className="fa fa-lg fa-minus" style={{color: '#ff9900'}}/></span>
<span>Scan <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#ff9900'}}/></span>
<b style={{color: '#aeaeae'}}> | </b>
<span>Tunnel <i className="fa fa-lg fa-minus" style={{color: '#0158aa'}}/></span>
<span>Tunnel <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#0158aa'}}/></span>
<b style={{color: '#aeaeae'}}> | </b>
<span>Island Communication <i className="fa fa-lg fa-minus" style={{color: '#a9aaa9'}}/></span>
<span>Island Communication <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#a9aaa9'}}/></span>
</div>
{this.renderTelemetryConsole()}
<div style={{height: '80vh'}}>
@ -180,7 +181,7 @@ class MapPageComponent extends AuthComponent {
Telemetry</Link>
<button onClick={() => this.setState({showKillDialog: true})} className="btn btn-danger pull-right"
style={{'width': '48%'}}>
<Icon name="stop-circle" style={{'marginRight': '0.5em'}}/>
<FontAwesomeIcon icon={faStopCircle} style={{'marginRight': '0.5em'}}/>
Kill All Monkeys
</button>
</div>

File diff suppressed because it is too large Load Diff

View File

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

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 = {
zeroTrust: 'Zero Trust',
security: 'Security',
attack: 'ATT&CK',
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 * as PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faList } from '@fortawesome/free-solid-svg-icons';
export default class EventsButton extends Component {
constructor(props) {
super(props);
@ -25,7 +28,7 @@ export default class EventsButton extends Component {
exportFilename={this.props.exportFilename}/>
<div className="text-center" style={{'display': 'grid'}}>
<Button className="btn btn-primary btn-lg" onClick={this.show}>
<i className="fa fa-list"/> Events {this.createEventsAmountBadge()}
<FontAwesomeIcon icon={faList}/> Events {this.createEventsAmountBadge()}
</Button>
</div>
</Fragment>;

View File

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

View File

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

View File

@ -13,7 +13,7 @@ const columns = [
{
Header: 'Status', id: 'status',
accessor: x => {
return <StatusLabel status={x.status} size="fa-3x" showText={false}/>;
return <StatusLabel status={x.status} size="3x" showText={false}/>;
},
maxWidth: MAX_WIDTH_STATUS_COLUMN
},

View File

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

View File

@ -5,6 +5,9 @@ import React from 'react';
import * as PropTypes from 'prop-types';
import {Panel} from 'react-bootstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronDown } from '@fortawesome/free-solid-svg-icons';
export default class SinglePillarPrinciplesStatus extends AuthComponent {
render() {
if (this.props.principlesStatus.length === 0) {
@ -15,7 +18,7 @@ export default class SinglePillarPrinciplesStatus extends AuthComponent {
<Panel.Heading>
<Panel.Title toggle>
<h3 style={{textAlign: 'center', marginTop: '1px', marginBottom: '1px'}}>
<i className="fa fa-chevron-down"/> <PillarLabel pillar={this.props.pillar}
<FontAwesomeIcon icon={faChevronDown}/> <PillarLabel pillar={this.props.pillar}
status={this.props.pillarsToStatuses[this.props.pillar]}/>
</h3>
</Panel.Title>

View File

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

View File

@ -1,6 +1,5 @@
import React, {Component} from 'react';
import {Col, Grid, Row} from 'react-bootstrap';
import MonkeysStillAliveWarning from '../common/MonkeysStillAliveWarning';
import PillarsOverview from './PillarOverview';
import ZeroTrustReportLegend from './ReportLegend';
import * as PropTypes from 'prop-types';
@ -12,7 +11,6 @@ export default class SummarySection extends Component {
<Grid fluid={true}>
<Row>
<Col xs={12} sm={12} md={12} lg={12}>
<MonkeysStillAliveWarning allMonkeysAreDead={this.props.allMonkeysAreDead}/>
<p>
Get a quick glance at how your network aligns with the <a
href="https://www.forrester.com/report/The+Zero+Trust+eXtended+ZTX+Ecosystem/-/E-RES137210">
@ -22,11 +20,11 @@ export default class SummarySection extends Component {
</Col>
</Row>
<Row className="show-grid">
<Col xs={8} sm={8} md={8} lg={8}>
<Col xs={8} sm={8} md={7} lg={7}>
<PillarsOverview pillarsToStatuses={this.props.pillars.pillarsToStatuses}
grades={this.props.pillars.grades}/>
</Col>
<Col xs={4} sm={4} md={4} lg={4}>
<Col xs={4} sm={4} md={5} lg={5}>
<ZeroTrustReportLegend/>
</Col>
</Row>
@ -36,6 +34,5 @@ export default class SummarySection extends Component {
}
SummarySection.propTypes = {
allMonkeysAreDead: PropTypes.bool,
pillars: PropTypes.object
};

View File

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

View File

@ -1,5 +1,7 @@
import React from 'react';
import {Icon} from 'react-fa';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faDownload } from '@fortawesome/free-solid-svg-icons';
class VersionComponent extends React.Component {
constructor(props) {
@ -32,7 +34,7 @@ class VersionComponent extends React.Component {
<div>
<b>Newer version available!</b>
<br/>
<b><a target="_blank" href={this.state.downloadLink}>Download here <Icon name="download"/></a></b>
<b><a target="_blank" href={this.state.downloadLink}>Download here <FontAwesomeIcon icon={faDownload}/></a></b>
</div>
:
undefined

View File

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

View File

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

View File

@ -12,6 +12,7 @@ $black: #000000;
text-align: center;
width: 100%;
height: 100%;
cursor: pointer;
input {
display: none;
@ -39,6 +40,7 @@ $black: #000000;
background-color: $dark-green;
color: $light-grey;
fill: $light-grey;
cursor: default;
}
&.is-checked {
@ -97,12 +99,47 @@ $black: #000000;
}
}
.attack-matrix .ui-checkbox-btn label {
padding-top: 0;
position: relative;
float: left;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding-left: 5px;
padding-right: 5px;
}
.attack-matrix div.rt-td>div {
height: 100%;
padding: 0;
}
.attack-matrix div.rt-td {
padding: 0;
border-right: 3px solid rgba(0,0,0,0) !important;
}
.attack-matrix .rt-tr-group div.rt-td:first-child {
border-left: 3px solid rgba(0,0,0,0) !important;
}
.attack-matrix .rt-tr-group {
border-top: 3px solid white !important;
border-bottom: none !important;
}
.attack-matrix .rt-tr-group:last-child {
margin-bottom: 3px !important;
}
.icon-checked {
color: $green
}
.icon-mandatory {
color: $dark-green
color: $dark-green;
}
.icon-unchecked {

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;
$info-color: #ade3eb;
@ -68,27 +68,12 @@ $default-color: #e0ddde;
overflow: hidden;
}
.collapsing {
height:100% !important;
}
.collapse-item .content {
padding: 2rem 0;
transition: transform $transition;
will-change: transform;
$offset: 10px;
&.collapsing {
transform: translateY(-$offset);
}
&.collapse-comp {
transform: translateY(-$offset);
}
&.expanding {
transform: translateX(0px);
}
&.expanded {
transform: translateX(0px);
}
}
.collapse-item .text {

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