forked from p15670423/monkey
Moved attack matrix to attack report page
This commit is contained in:
parent
222644a3fb
commit
cc1f46af32
|
@ -1,7 +1,8 @@
|
||||||
import flask_restful
|
import flask_restful
|
||||||
from flask import jsonify
|
|
||||||
from monkey_island.cc.auth import jwt_required
|
from monkey_island.cc.auth import jwt_required
|
||||||
from monkey_island.cc.services.attack.attack_report import AttackReportService
|
from monkey_island.cc.services.attack.attack_report import AttackReportService
|
||||||
|
from monkey_island.cc.services.attack.attack_schema import SCHEMA
|
||||||
|
from flask import json, current_app
|
||||||
|
|
||||||
__author__ = "VakarisZ"
|
__author__ = "VakarisZ"
|
||||||
|
|
||||||
|
@ -10,4 +11,9 @@ class AttackReport(flask_restful.Resource):
|
||||||
|
|
||||||
@jwt_required()
|
@jwt_required()
|
||||||
def get(self):
|
def get(self):
|
||||||
return jsonify(AttackReportService.get_latest_report()['techniques'])
|
response_content = {'techniques': AttackReportService.get_latest_report()['techniques'], 'schema': SCHEMA}
|
||||||
|
return current_app.response_class(json.dumps(response_content,
|
||||||
|
indent=None,
|
||||||
|
separators=(",", ":"),
|
||||||
|
sort_keys=False) + "\n",
|
||||||
|
mimetype=current_app.config['JSONIFY_MIMETYPE'])
|
||||||
|
|
|
@ -173,3 +173,15 @@ class AttackConfig(object):
|
||||||
for key, technique in list(attack_type['properties'].items()):
|
for key, technique in list(attack_type['properties'].items()):
|
||||||
techniques[key] = technique['value']
|
techniques[key] = technique['value']
|
||||||
return techniques
|
return techniques
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_techniques_for_report():
|
||||||
|
"""
|
||||||
|
:return: Format: {"T1110": {"selected": True, "type": "Credential Access", "T1075": ...}
|
||||||
|
"""
|
||||||
|
attack_config = AttackConfig.get_config()
|
||||||
|
techniques = {}
|
||||||
|
for type_name, attack_type in list(attack_config.items()):
|
||||||
|
for key, technique in list(attack_type['properties'].items()):
|
||||||
|
techniques[key] = {'selected': technique['value'], 'type': SCHEMA['properties'][type_name]['title']}
|
||||||
|
return techniques
|
||||||
|
|
|
@ -58,10 +58,11 @@ class AttackReportService:
|
||||||
'name': REPORT_NAME
|
'name': REPORT_NAME
|
||||||
}
|
}
|
||||||
|
|
||||||
for tech_id, value in list(AttackConfig.get_technique_values().items()):
|
for tech_id, tech_info in list(AttackConfig.get_techniques_for_report().items()):
|
||||||
if value:
|
|
||||||
try:
|
try:
|
||||||
report['techniques'].update({tech_id: TECHNIQUES[tech_id].get_report_data()})
|
technique_report_data = TECHNIQUES[tech_id].get_report_data()
|
||||||
|
technique_report_data.update(tech_info)
|
||||||
|
report['techniques'].update({tech_id: technique_report_data})
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
LOG.error("Attack technique does not have it's report component added "
|
LOG.error("Attack technique does not have it's report component added "
|
||||||
"to attack report service. %s" % e)
|
"to attack report service. %s" % e)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import '../../styles/Collapse.scss';
|
||||||
import AuthComponent from '../AuthComponent';
|
import AuthComponent from '../AuthComponent';
|
||||||
import {ScanStatus} from '../attack/techniques/Helpers';
|
import {ScanStatus} from '../attack/techniques/Helpers';
|
||||||
import Collapse from '@kunukn/react-collapse';
|
import Collapse from '@kunukn/react-collapse';
|
||||||
|
import Matrix from './attack/Matrix';
|
||||||
|
|
||||||
import T1210 from '../attack/techniques/T1210';
|
import T1210 from '../attack/techniques/T1210';
|
||||||
import T1197 from '../attack/techniques/T1197';
|
import T1197 from '../attack/techniques/T1197';
|
||||||
|
@ -66,7 +67,8 @@ class AttackReportPageComponent extends AuthComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
report: this.props.report,
|
techniques: this.props.report['techniques'],
|
||||||
|
schema: this.props.report['schema'],
|
||||||
collapseOpen: ''
|
collapseOpen: ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -81,7 +83,7 @@ class AttackReportPageComponent extends AuthComponent {
|
||||||
this.setState(state => ({collapseOpen: state.collapseOpen === technique ? null : technique}));
|
this.setState(state => ({collapseOpen: state.collapseOpen === technique ? null : technique}));
|
||||||
|
|
||||||
getComponentClass(tech_id) {
|
getComponentClass(tech_id) {
|
||||||
switch (this.state.report[tech_id].status) {
|
switch (this.state.techniques[tech_id].status) {
|
||||||
case ScanStatus.SCANNED:
|
case ScanStatus.SCANNED:
|
||||||
return 'collapse-info';
|
return 'collapse-info';
|
||||||
case ScanStatus.USED:
|
case ScanStatus.USED:
|
||||||
|
@ -95,7 +97,7 @@ class AttackReportPageComponent extends AuthComponent {
|
||||||
return (
|
return (
|
||||||
<div key={tech_id} className={classNames('collapse-item', {'item--active': this.state.collapseOpen === tech_id})}>
|
<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)}>
|
<button className={classNames('btn-collapse', this.getComponentClass(tech_id))} onClick={() => this.onToggle(tech_id)}>
|
||||||
<span>{this.state.report[tech_id].title}</span>
|
<span>{this.state.techniques[tech_id].title}</span>
|
||||||
<span>
|
<span>
|
||||||
<i className={classNames('fa', this.state.collapseOpen === tech_id ? 'fa-chevron-down' : 'fa-chevron-up')}></i>
|
<i className={classNames('fa', this.state.collapseOpen === tech_id ? 'fa-chevron-down' : 'fa-chevron-up')}></i>
|
||||||
</span>
|
</span>
|
||||||
|
@ -118,22 +120,26 @@ class AttackReportPageComponent extends AuthComponent {
|
||||||
const TechniqueComponent = tech_components[technique];
|
const TechniqueComponent = tech_components[technique];
|
||||||
return (
|
return (
|
||||||
<div className={`content ${collapseState}`}>
|
<div className={`content ${collapseState}`}>
|
||||||
<TechniqueComponent data={this.state.report[technique]} reportData={this.props.reportData}/>
|
<TechniqueComponent data={this.state.techniques[technique]}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLegend() {
|
renderLegend() {
|
||||||
return (<div id="header" className="row justify-content-between attack-legend">
|
return (<div id="header" className="row justify-content-between attack-legend">
|
||||||
<Col xs={4}>
|
<Col xs={3}>
|
||||||
|
<i className="fa fa-circle-thin icon-unchecked"></i>
|
||||||
|
<span> - Dissabled</span>
|
||||||
|
</Col>
|
||||||
|
<Col xs={3}>
|
||||||
<i className="fa fa-circle icon-default"></i>
|
<i className="fa fa-circle icon-default"></i>
|
||||||
<span> - Unscanned</span>
|
<span> - Unscanned</span>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={4}>
|
<Col xs={3}>
|
||||||
<i className="fa fa-circle icon-info"></i>
|
<i className="fa fa-circle icon-info"></i>
|
||||||
<span> - Scanned</span>
|
<span> - Scanned</span>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={4}>
|
<Col xs={3}>
|
||||||
<i className="fa fa-circle icon-danger"></i>
|
<i className="fa fa-circle icon-danger"></i>
|
||||||
<span> - Used</span>
|
<span> - Used</span>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -142,7 +148,7 @@ class AttackReportPageComponent extends AuthComponent {
|
||||||
|
|
||||||
generateReportContent() {
|
generateReportContent() {
|
||||||
let content = [];
|
let content = [];
|
||||||
Object.keys(this.state.report).forEach((tech_id) => {
|
Object.keys(this.state.techniques).forEach((tech_id) => {
|
||||||
content.push(this.getTechniqueCollapse(tech_id))
|
content.push(this.getTechniqueCollapse(tech_id))
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
|
@ -153,8 +159,9 @@ class AttackReportPageComponent extends AuthComponent {
|
||||||
<p>
|
<p>
|
||||||
This report shows information about ATT&CK techniques used by Infection Monkey.
|
This report shows information about ATT&CK techniques used by Infection Monkey.
|
||||||
</p>
|
</p>
|
||||||
<div>
|
|
||||||
{this.renderLegend()}
|
{this.renderLegend()}
|
||||||
|
<Matrix techniques={this.state.techniques} schema={this.state.schema}/>
|
||||||
|
<div>
|
||||||
<section className="attack-report">{content}</section>
|
<section className="attack-report">{content}</section>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
|
@ -163,7 +170,7 @@ class AttackReportPageComponent extends AuthComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (Object.keys(this.state.report).length === 0 && this.state.runStarted) {
|
if (Object.keys(this.state.techniques).length === 0 && this.state.runStarted) {
|
||||||
return (<h1>No techniques were scanned</h1>);
|
return (<h1>No techniques were scanned</h1>);
|
||||||
} else {
|
} else {
|
||||||
return (<div> {this.generateReportContent()}</div>);
|
return (<div> {this.generateReportContent()}</div>);
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Checkbox from '../../ui-components/Checkbox';
|
||||||
|
import ReactTable from 'react-table';
|
||||||
|
import 'filepond/dist/filepond.min.css';
|
||||||
|
|
||||||
|
class MatrixComponent extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {techniques: this.props.techniques,
|
||||||
|
schema: this.props.schema}
|
||||||
|
}
|
||||||
|
|
||||||
|
getColumns() {
|
||||||
|
let columns = [];
|
||||||
|
for(let 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: tech_type.title,
|
||||||
|
id: type_key,
|
||||||
|
accessor: x => MatrixComponent.renderTechnique(x[tech_type.title]),
|
||||||
|
style: {'whiteSpace': 'unset'}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTableRows() {
|
||||||
|
let rows = [];
|
||||||
|
for (let 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(let 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static renderTechnique(technique) {
|
||||||
|
if (technique == null || typeof technique === undefined) {
|
||||||
|
return (<div/>)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Checkbox checked={technique.selected}
|
||||||
|
necessary={true}
|
||||||
|
name={technique.title}
|
||||||
|
changeHandler={function(){}}
|
||||||
|
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 MatrixComponent;
|
|
@ -14,10 +14,18 @@ class CheckboxComponent extends React.PureComponent {
|
||||||
changeHandler(name, checked) function will be called with these parameters:
|
changeHandler(name, checked) function will be called with these parameters:
|
||||||
this.props.name (the name of this component) and
|
this.props.name (the name of this component) and
|
||||||
this.state.checked (boolean indicating if this component is checked or not)
|
this.state.checked (boolean indicating if this component is checked or not)
|
||||||
|
this.props.status (int) adds a class "status-x" to this checkbox. Used for styling.
|
||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
if (this.props.hasOwnProperty("status")){
|
||||||
|
this.status = this.props.status;
|
||||||
|
} else {
|
||||||
|
this.status = false
|
||||||
|
}
|
||||||
|
console.log(this.props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
status: this.status,
|
||||||
checked: this.props.checked,
|
checked: this.props.checked,
|
||||||
necessary: this.props.necessary,
|
necessary: this.props.necessary,
|
||||||
isAnimating: false
|
isAnimating: false
|
||||||
|
@ -48,6 +56,9 @@ class CheckboxComponent extends React.PureComponent {
|
||||||
// Creates class string for component
|
// Creates class string for component
|
||||||
composeStateClasses(core) {
|
composeStateClasses(core) {
|
||||||
let result = core;
|
let result = core;
|
||||||
|
if (this.state.status) {
|
||||||
|
result += ' status-'+this.state.status;
|
||||||
|
}
|
||||||
if (this.state.necessary) {
|
if (this.state.necessary) {
|
||||||
return result + ' blocked'
|
return result + ' blocked'
|
||||||
}
|
}
|
||||||
|
@ -56,7 +67,6 @@ class CheckboxComponent extends React.PureComponent {
|
||||||
} else {
|
} else {
|
||||||
result += ' is-unchecked'
|
result += ' is-unchecked'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.isAnimating) {
|
if (this.state.isAnimating) {
|
||||||
result += ' do-ping';
|
result += ' do-ping';
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue