ATT&CK matrix formed in front-end
This commit is contained in:
parent
d2670be767
commit
17a51cd92e
|
@ -8,6 +8,5 @@ from cc.services.attck.attck import AttckService
|
|||
class AttckConfiguration(flask_restful.Resource):
|
||||
@jwt_required()
|
||||
def get(self):
|
||||
return jsonify(schema=AttckService.get_config_schema(),
|
||||
configuration=AttckService.get_config())
|
||||
return jsonify(configuration=AttckService.get_config()['properties'])
|
||||
|
||||
|
|
|
@ -13,6 +13,13 @@ SCHEMA = {
|
|||
"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",
|
||||
"default": True,
|
||||
"description": "Pass the hash (PtH) is a method of authenticating as a user without "
|
||||
"having access to the user's cleartext password."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
import React from 'react';
|
||||
import Form from 'react-jsonschema-form';
|
||||
import {Col, Nav, NavItem} from 'react-bootstrap';
|
||||
import CheckBox from '../ui-components/checkBox'
|
||||
import AuthComponent from '../AuthComponent';
|
||||
import 'filepond/dist/filepond.min.css';
|
||||
import ReactTable from "react-table";
|
||||
|
||||
|
||||
let renderTechnique = function (technique) {
|
||||
console.log(technique);
|
||||
if (technique == null){
|
||||
return (<div></div>)
|
||||
} else {
|
||||
return (<div>{technique.title}</div>)
|
||||
}
|
||||
};
|
||||
|
||||
// Finds which attack type has most techniques and returns that number
|
||||
let findMaxTechniques = function (data){
|
||||
let maxLen = 0;
|
||||
data.forEach(function(techType) {
|
||||
if (Object.keys(techType.properties).length > maxLen){
|
||||
maxLen = Object.keys(techType.properties).length
|
||||
}
|
||||
});
|
||||
return maxLen
|
||||
};
|
||||
|
||||
let parseTechniques = function (data, maxLen) {
|
||||
let techniques = [];
|
||||
// Create rows with attack techniques
|
||||
for (let i = 0; i < maxLen; i++) {
|
||||
let row = {};
|
||||
data.forEach(function(techType){
|
||||
let rowColumn = {};
|
||||
rowColumn.techName = techType.title;
|
||||
if (i <= Object.keys(techType.properties).length) {
|
||||
rowColumn.technique = Object.values(techType.properties)[i];
|
||||
} else {
|
||||
rowColumn.technique = null
|
||||
}
|
||||
row[rowColumn.techName] = rowColumn
|
||||
});
|
||||
techniques.push(row)
|
||||
}
|
||||
return techniques;
|
||||
};
|
||||
|
||||
class MatrixComponent extends AuthComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.maxTechniques = findMaxTechniques(Object.values(this.props.configuration));
|
||||
this.data = parseTechniques(Object.values(this.props.configuration), this.maxTechniques);
|
||||
}
|
||||
|
||||
getColumns() {
|
||||
return Object.keys(this.data[0]).map((key)=>{
|
||||
return {
|
||||
Header: key,
|
||||
id: key,
|
||||
accessor: x => renderTechnique(x[key].technique)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log(this.data);
|
||||
let columns = this.getColumns();
|
||||
return (<ReactTable
|
||||
columns={columns}
|
||||
data={this.data}
|
||||
showPagination={false}
|
||||
defaultPageSize={this.maxTechniques}
|
||||
/>);
|
||||
}
|
||||
}
|
||||
|
||||
export default MatrixComponent;
|
|
@ -1,8 +1,7 @@
|
|||
import React from 'react';
|
||||
import Form from 'react-jsonschema-form';
|
||||
import {Col, Nav, NavItem} from 'react-bootstrap';
|
||||
import AuthComponent from '../AuthComponent';
|
||||
import 'filepond/dist/filepond.min.css';
|
||||
import MatrixComponent from '../attck/MatrixComponent'
|
||||
|
||||
class AttckComponent extends AuthComponent {
|
||||
constructor(props) {
|
||||
|
@ -12,7 +11,6 @@ class AttckComponent extends AuthComponent {
|
|||
this.sectionsOrder = ['ATT&CK matrix'];
|
||||
// set schema from server
|
||||
this.state = {
|
||||
schema: {},
|
||||
configuration: {},
|
||||
lastAction: 'none',
|
||||
sections: [],
|
||||
|
@ -29,16 +27,21 @@ class AttckComponent extends AuthComponent {
|
|||
sections.push({key: sectionKey, title: res.configuration.title});
|
||||
}
|
||||
this.setState({
|
||||
schema: res.schema,
|
||||
configuration: res.configuration,
|
||||
sections: sections,
|
||||
selectedSection: 'ATT&CK matrix'
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<Col xs={12} lg={8}> Vakaris </Col>);
|
||||
let content;
|
||||
if (Object.keys(this.state.configuration).length === 0) {
|
||||
content = (<h1>Fetching configuration...</h1>);
|
||||
} else {
|
||||
content = (<MatrixComponent configuration={this.state.configuration} />);
|
||||
}
|
||||
return <div>{content}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
import '../../styles/CheckBox.scss'
|
||||
import React from 'react';
|
||||
import MatrixComponent from "../attck/MatrixComponent";
|
||||
|
||||
class Checkbox extends React.PureComponent {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
checked: false,
|
||||
isAnimating: false,
|
||||
};
|
||||
|
||||
this.toggleChecked = this.toggleChecked.bind(this);
|
||||
this.ping = this.ping.bind(this);
|
||||
this.composeStateClasses = this.composeStateClasses.bind(this);
|
||||
}
|
||||
|
||||
//
|
||||
toggleChecked() {
|
||||
if (this.state.isAnimating) return false;
|
||||
this.setState({
|
||||
checked: !this.state.checked,
|
||||
isAnimating: true,
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
ping() {
|
||||
this.setState({ isAnimating: false })
|
||||
}
|
||||
|
||||
//
|
||||
composeStateClasses(core) {
|
||||
let result = core;
|
||||
|
||||
if (this.state.checked) { result += ' is-checked'; }
|
||||
else { result += ' is-unchecked' }
|
||||
|
||||
if (this.state.isAnimating) { result += ' do-ping'; }
|
||||
return result;
|
||||
}
|
||||
|
||||
//
|
||||
render() {
|
||||
|
||||
const cl = this.composeStateClasses('ui-checkbox-btn');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ cl }
|
||||
onClick={ this.toggleChecked }>
|
||||
|
||||
<input className="ui ui-checkbox" type="checkbox" checked={this.state.checked} />
|
||||
{
|
||||
this.state.checked &&
|
||||
<i className="icon">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M21 5q0.43 0 0.715 0.285t0.285 0.715q0 0.422-0.289 0.711l-12 12q-0.289 0.289-0.711 0.289t-0.711-0.289l-6-6q-0.289-0.289-0.289-0.711 0-0.43 0.285-0.715t0.715-0.285q0.422 0 0.711 0.289l5.289 5.297 11.289-11.297q0.289-0.289 0.711-0.289z"></path>
|
||||
</svg>
|
||||
</i>
|
||||
}
|
||||
{
|
||||
!this.state.checked &&
|
||||
<i className="icon">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24">
|
||||
<path d="M19 4q0.43 0 0.715 0.285t0.285 0.715q0 0.422-0.289 0.711l-6.297 6.289 6.297 6.289q0.289 0.289 0.289 0.711 0 0.43-0.285 0.715t-0.715 0.285q-0.422 0-0.711-0.289l-6.289-6.297-6.289 6.297q-0.289 0.289-0.711 0.289-0.43 0-0.715-0.285t-0.285-0.715q0-0.422 0.289-0.711l6.297-6.289-6.297-6.289q-0.289-0.289-0.289-0.711 0-0.43 0.285-0.715t0.715-0.285q0.422 0 0.711 0.289l6.289 6.297 6.289-6.297q0.289-0.289 0.711-0.289z"></path>
|
||||
</svg>
|
||||
</i>
|
||||
}
|
||||
<label className="text">{ this.props.children }</label>
|
||||
<div className="ui-btn-ping" onTransitionEnd={this.ping}></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default Checkbox;
|
|
@ -0,0 +1,103 @@
|
|||
// readable
|
||||
$desired-line-height: 24px;
|
||||
$desired-height: 36px;
|
||||
$text-offset: 2px;
|
||||
|
||||
// usable
|
||||
$dlh: $desired-line-height;
|
||||
$dh: $desired-height;
|
||||
$to: $text-offset;
|
||||
|
||||
// coooolors
|
||||
$light-grey: #EAF4F4;
|
||||
$medium-grey: #7B9EA8;
|
||||
$dark-grey: #7B9EA8;
|
||||
$green: #44CF6C;
|
||||
|
||||
.ui-checkbox-btn {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
padding: (($dh - $dlh) / 2) ($dlh / 2);
|
||||
border-radius: $dh / 2; // overcompensate
|
||||
background-color: rgba(red, .6);
|
||||
|
||||
input { display: none; } // turn off, but not forgotten
|
||||
|
||||
.icon,
|
||||
.text {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 14px;
|
||||
line-height: $dlh - $to;
|
||||
padding-top: $to;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
// color states
|
||||
&.is-unchecked {
|
||||
border: 1px solid $medium-grey;
|
||||
background-color: transparent;
|
||||
color: $dark-grey;
|
||||
fill: $dark-grey;
|
||||
}
|
||||
|
||||
&.is-checked {
|
||||
border: 1px solid $green;
|
||||
background-color: $green;
|
||||
color: white;
|
||||
fill: white;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: $dlh - 4;
|
||||
height: $dlh;
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
top: 0; right: 0; bottom: 0; left: 0;
|
||||
margin: auto;
|
||||
width: 16px;
|
||||
height: auto;
|
||||
fill: inherit;
|
||||
}
|
||||
|
||||
.is-checked & {
|
||||
color: white;
|
||||
fill: white;
|
||||
}
|
||||
}
|
||||
|
||||
// ping animation magic
|
||||
.ui-btn-ping {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 50%;
|
||||
transform: translate3d(-50%, -50%, 0); // center center by default
|
||||
|
||||
// set the square
|
||||
&:before {
|
||||
content: '';
|
||||
transform: scale(0, 0); // center center by default
|
||||
transition-property: background-color transform;
|
||||
transition-timing-function: cubic-bezier(0.0, 0.0, 0.2, 1);
|
||||
display: block;
|
||||
padding-bottom: 100%;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(white, .84);;
|
||||
}
|
||||
|
||||
.do-ping &:before {
|
||||
transform: scale(2.5, 2.5);
|
||||
transition-duration: .35s;
|
||||
background-color: rgba(white, .08);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue