Configuration submission

This commit is contained in:
VakarisZ 2019-03-22 14:41:49 +02:00
parent b319bc8e5f
commit 88229e74c9
13 changed files with 245 additions and 153 deletions

View File

@ -29,7 +29,7 @@ from cc.resources.telemetry import Telemetry
from cc.resources.telemetry_feed import TelemetryFeed
from cc.resources.pba_file_download import PBAFileDownload
from cc.resources.pba_file_upload import FileUpload
from cc.resources.attck import AttckConfiguration
from cc.resources.attack import AttckConfiguration
from cc.services.config import ConfigService
__author__ = 'Barak'
@ -124,6 +124,6 @@ def init_app(mongo_url):
'/api/fileUpload/<string:file_type>?load=<string:filename>',
'/api/fileUpload/<string:file_type>?restore=<string:filename>')
api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/')
api.add_resource(AttckConfiguration, '/api/attck')
api.add_resource(AttckConfiguration, '/api/attack')
return app

View File

@ -0,0 +1,18 @@
import flask_restful
import json
from flask import jsonify, request
from cc.auth import jwt_required
from cc.services.attack.attack import AttackService
class AttckConfiguration(flask_restful.Resource):
@jwt_required()
def get(self):
return jsonify(configuration=AttackService.get_config()['properties'])
@jwt_required()
def post(self):
AttackService.update_config({'properties': json.loads(request.data)})
return {}

View File

@ -1,12 +0,0 @@
import flask_restful
from flask import jsonify
from cc.auth import jwt_required
from cc.services.attck.attck import AttckService
class AttckConfiguration(flask_restful.Resource):
@jwt_required()
def get(self):
return jsonify(configuration=AttckService.get_config()['properties'])

View File

@ -7,6 +7,7 @@ from flask import request, make_response, jsonify
from cc.auth import jwt_required
from cc.database import mongo
from cc.services.config import ConfigService
from cc.services.attack.attack import AttackService
from cc.services.node import NodeService
from cc.services.report import ReportService
from cc.utils import local_ip_addresses
@ -47,6 +48,7 @@ class Root(flask_restful.Resource):
# We can't drop system collections.
[mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')]
ConfigService.init_config()
AttackService.reset_config()
logger.info('DB was reset')
return jsonify(status='OK')

View File

@ -0,0 +1,44 @@
import logging
from cc.database import mongo
from attack_schema import SCHEMA
__author__ = "VakarisZ"
logger = logging.getLogger(__name__)
class AttackService:
default_config = None
def __init__(self):
pass
@staticmethod
def get_config():
config = mongo.db.attack.find_one({'name': 'newconfig'}) or AttackService.get_default_config()
return config
@staticmethod
def get_config_schema():
return SCHEMA
@staticmethod
def reset_config():
config = AttackService.get_default_config()
AttackService.update_config(config)
@staticmethod
def update_config(config_json):
mongo.db.attack.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True)
return True
@staticmethod
def parse_users_matrix(data):
pass
@staticmethod
def get_default_config():
if not AttackService.default_config:
AttackService.update_config(SCHEMA)
AttackService.default_config = SCHEMA
return AttackService.default_config

View File

@ -9,7 +9,8 @@ SCHEMA = {
"T1210": {
"title": "T1210 Exploitation of Remote services",
"type": "bool",
"default": True,
"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."
@ -17,7 +18,8 @@ SCHEMA = {
"T1075": {
"title": "T1075 Pass the hash",
"type": "bool",
"default": True,
"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."
}
@ -30,7 +32,8 @@ SCHEMA = {
"T1110": {
"title": "T1110 Brute force",
"type": "bool",
"default": True,
"value": False,
"necessary": False,
"description": "Adversaries may use brute force techniques to attempt access to accounts "
"when passwords are unknown or when password hashes are obtained."
}
@ -43,7 +46,8 @@ SCHEMA = {
"T1197": {
"title": "T1197 Bits jobs",
"type": "bool",
"default": True,
"value": True,
"necessary": True,
"description": "Adversaries may abuse BITS to download, execute, "
"and even clean up after running malicious code."
}

View File

@ -1,43 +0,0 @@
import logging
from cc.database import mongo
from attck_schema import SCHEMA
from jsonschema import Draft4Validator, validators
__author__ = "VakarisZ"
logger = logging.getLogger(__name__)
class AttckService:
default_config = None
def __init__(self):
pass
@staticmethod
def get_config():
config = mongo.db.attck.find_one({'name': 'newconfig'}) or AttckService.get_default_config()
return config
@staticmethod
def get_config_schema():
return SCHEMA
@staticmethod
def reset_config():
config = AttckService.get_default_config()
AttckService.update_config(config)
logger.info('Monkey config reset was called')
@staticmethod
def update_config(config_json):
mongo.db.attck.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True)
logger.info('Attck config was updated')
return True
@staticmethod
def get_default_config():
if not AttckService.default_config:
AttckService.update_config(SCHEMA)
AttckService.default_config = SCHEMA
return AttckService.default_config

View File

@ -0,0 +1,156 @@
import React from 'react';
import Checkbox from '../ui-components/checkbox'
import Tooltip from 'react-tooltip-lite'
import AuthComponent from '../AuthComponent';
import ReactTable from "react-table";
import 'filepond/dist/filepond.min.css';
import '../../styles/Tooltip.scss';
// 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
};
// Parses config schema into data suitable for react-table (ATT&CK matrix)
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];
if (rowColumn.technique){
rowColumn.technique.name = Object.keys(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.state = {lastAction: 'none', matrixData: this.props.configuration};
// Copy configuration and parse it for ATT&CK matrix table
let configCopy = JSON.parse(JSON.stringify(this.props.configuration));
this.maxTechniques = findMaxTechniques(Object.values(configCopy));
this.data = parseTechniques(Object.values(configCopy), this.maxTechniques);
}
getColumns() {
return Object.keys(this.data[0]).map((key)=>{
return {
Header: key,
id: key,
accessor: x => this.renderTechnique(x[key].technique),
style: { 'whiteSpace': 'unset' }
};
});
}
renderTechnique(technique) {
if (technique == null){
return (<div></div>)
} else {
return (<Tooltip content={technique.description} direction="down">
<Checkbox checked={technique.value}
necessary={technique.necessary}
name={technique.name}
changeHandler={this.handleTechniqueChange}>
{technique.title}
</Checkbox>
</Tooltip>)
}
};
onSubmit = () => {
console.log(this.state.matrixData);
this.authFetch('/api/attack',
{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(this.state.matrixData)
})
.then(res => {
if (!res.ok)
{
throw Error()
}
return res;
}).then(
this.setState({
lastAction: 'saved'
})
).catch(error => {
console.log('bad configuration');
this.setState({lastAction: 'invalid_configuration'});
});
};
handleTechniqueChange = (technique, value) => {
Object.entries(this.state.matrixData).forEach(techType => {
if(techType[1].properties.hasOwnProperty(technique)){
let tempMatrix = this.state.matrixData;
tempMatrix[techType[0]].properties[technique].value = value;
this.setState({matrixData: tempMatrix});
}
});
};
render() {
let columns = this.getColumns();
return (
<div className={"attack-matrix"}>
<form onSubmit={this.onSubmit}>
<ReactTable
columns={columns}
data={this.data}
showPagination={false}
defaultPageSize={this.maxTechniques} />
<div className={"messages"}>
{ this.state.lastAction === 'reset' ?
<div className="alert alert-success">
<i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>
Matrix reset to default.
</div>
: ''}
{ this.state.lastAction === 'saved' ?
<div className="alert alert-success">
<i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>
Matrix applied to configuration.
</div>
: ''}
{ this.state.lastAction === 'invalid_configuration' ?
<div className="alert alert-danger">
<i className="glyphicon glyphicon-exclamation-sign" style={{'marginRight': '5px'}}/>
An invalid matrix configuration supplied, check selected fields.
</div>
: ''}
</div>
<div className="text-center">
<button onClick={this.onSubmit} className="btn btn-success btn-lg" style={{margin: '5px'}}>
Apply to configuration
</button>
</div>
</form>
</div>);
}
}
export default MatrixComponent;

View File

@ -1,83 +0,0 @@
import React from 'react';
import Form from 'react-jsonschema-form';
import {Col, Nav, NavItem} from 'react-bootstrap';
import Checkbox from '../ui-components/checkbox'
import Tooltip from 'react-tooltip-lite'
import AuthComponent from '../AuthComponent';
import ReactTable from "react-table";
import 'filepond/dist/filepond.min.css';
import '../../styles/Tooltip.scss';
let renderTechnique = function (technique) {
console.log(technique);
if (technique == null){
return (<div></div>)
} else {
return (<Tooltip content={technique.description} direction="down"><Checkbox>
{technique.title}</Checkbox> </Tooltip>)
}
};
// 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),
style: { 'white-space': 'unset' }
};
});
}
render() {
console.log(this.data);
let columns = this.getColumns();
return (<ReactTable
columns={columns}
data={this.data}
showPagination={false}
defaultPageSize={this.maxTechniques}
/>);
}
}
export default MatrixComponent;

View File

@ -1,7 +1,7 @@
import React from 'react';
import AuthComponent from '../AuthComponent';
import 'filepond/dist/filepond.min.css';
import MatrixComponent from '../attck/MatrixComponent'
import MatrixComponent from '../attack/MatrixComponent'
class AttckComponent extends AuthComponent {
constructor(props) {
@ -12,14 +12,13 @@ class AttckComponent extends AuthComponent {
// set schema from server
this.state = {
configuration: {},
lastAction: 'none',
sections: [],
selectedSection: 'ATT&CK matrix',
};
}
componentDidMount() {
this.authFetch('/api/attck')
this.authFetch('/api/attack')
.then(res => res.json())
.then(res => {
let sections = [];
@ -36,11 +35,7 @@ class AttckComponent extends AuthComponent {
render() {
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>;
}
}

View File

@ -515,3 +515,8 @@ body {
}
}
/* Attack config page */
.attack-matrix .messages {
margin-bottom: 30px;
}

View File

@ -1,7 +1,7 @@
// colors
$light-grey: #EAF4F4;
$medium-grey: #7B9EA8;
$dark-grey: #7B9EA8;
$dark-grey: #7a7d7b;
$green: #44CF6C;
$black: #000000;
@ -33,6 +33,12 @@ $black: #000000;
fill: $black;
}
&.blocked {
background-color: $dark-grey;
color: $black;
fill: $black;
}
&.is-checked {
border: 1px solid $green;
background-color: $green;