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.telemetry_feed import TelemetryFeed
from cc.resources.pba_file_download import PBAFileDownload from cc.resources.pba_file_download import PBAFileDownload
from cc.resources.pba_file_upload import FileUpload 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 from cc.services.config import ConfigService
__author__ = 'Barak' __author__ = 'Barak'
@ -124,6 +124,6 @@ def init_app(mongo_url):
'/api/fileUpload/<string:file_type>?load=<string:filename>', '/api/fileUpload/<string:file_type>?load=<string:filename>',
'/api/fileUpload/<string:file_type>?restore=<string:filename>') '/api/fileUpload/<string:file_type>?restore=<string:filename>')
api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/')
api.add_resource(AttckConfiguration, '/api/attck') api.add_resource(AttckConfiguration, '/api/attack')
return app 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.auth import jwt_required
from cc.database import mongo from cc.database import mongo
from cc.services.config import ConfigService from cc.services.config import ConfigService
from cc.services.attack.attack import AttackService
from cc.services.node import NodeService from cc.services.node import NodeService
from cc.services.report import ReportService from cc.services.report import ReportService
from cc.utils import local_ip_addresses from cc.utils import local_ip_addresses
@ -47,6 +48,7 @@ class Root(flask_restful.Resource):
# We can't drop system collections. # We can't drop system collections.
[mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')] [mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')]
ConfigService.init_config() ConfigService.init_config()
AttackService.reset_config()
logger.info('DB was reset') logger.info('DB was reset')
return jsonify(status='OK') 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": { "T1210": {
"title": "T1210 Exploitation of Remote services", "title": "T1210 Exploitation of Remote services",
"type": "bool", "type": "bool",
"default": True, "value": True,
"necessary": False,
"description": "Exploitation of a software vulnerability occurs when an adversary " "description": "Exploitation of a software vulnerability occurs when an adversary "
"takes advantage of a programming error in a program, service, or within the " "takes advantage of a programming error in a program, service, or within the "
"operating system software or kernel itself to execute adversary-controlled code." "operating system software or kernel itself to execute adversary-controlled code."
@ -17,7 +18,8 @@ SCHEMA = {
"T1075": { "T1075": {
"title": "T1075 Pass the hash", "title": "T1075 Pass the hash",
"type": "bool", "type": "bool",
"default": True, "value": True,
"necessary": False,
"description": "Pass the hash (PtH) is a method of authenticating as a user without " "description": "Pass the hash (PtH) is a method of authenticating as a user without "
"having access to the user's cleartext password." "having access to the user's cleartext password."
} }
@ -30,7 +32,8 @@ SCHEMA = {
"T1110": { "T1110": {
"title": "T1110 Brute force", "title": "T1110 Brute force",
"type": "bool", "type": "bool",
"default": True, "value": False,
"necessary": False,
"description": "Adversaries may use brute force techniques to attempt access to accounts " "description": "Adversaries may use brute force techniques to attempt access to accounts "
"when passwords are unknown or when password hashes are obtained." "when passwords are unknown or when password hashes are obtained."
} }
@ -43,7 +46,8 @@ SCHEMA = {
"T1197": { "T1197": {
"title": "T1197 Bits jobs", "title": "T1197 Bits jobs",
"type": "bool", "type": "bool",
"default": True, "value": True,
"necessary": True,
"description": "Adversaries may abuse BITS to download, execute, " "description": "Adversaries may abuse BITS to download, execute, "
"and even clean up after running malicious code." "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 React from 'react';
import AuthComponent from '../AuthComponent'; import AuthComponent from '../AuthComponent';
import 'filepond/dist/filepond.min.css'; import 'filepond/dist/filepond.min.css';
import MatrixComponent from '../attck/MatrixComponent' import MatrixComponent from '../attack/MatrixComponent'
class AttckComponent extends AuthComponent { class AttckComponent extends AuthComponent {
constructor(props) { constructor(props) {
@ -12,14 +12,13 @@ class AttckComponent extends AuthComponent {
// set schema from server // set schema from server
this.state = { this.state = {
configuration: {}, configuration: {},
lastAction: 'none',
sections: [], sections: [],
selectedSection: 'ATT&CK matrix', selectedSection: 'ATT&CK matrix',
}; };
} }
componentDidMount() { componentDidMount() {
this.authFetch('/api/attck') this.authFetch('/api/attack')
.then(res => res.json()) .then(res => res.json())
.then(res => { .then(res => {
let sections = []; let sections = [];
@ -36,11 +35,7 @@ class AttckComponent extends AuthComponent {
render() { render() {
let content; let content;
if (Object.keys(this.state.configuration).length === 0) { content = (<MatrixComponent configuration={this.state.configuration} />);
content = (<h1>Fetching configuration...</h1>);
} else {
content = (<MatrixComponent configuration={this.state.configuration} />);
}
return <div>{content}</div>; 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 // colors
$light-grey: #EAF4F4; $light-grey: #EAF4F4;
$medium-grey: #7B9EA8; $medium-grey: #7B9EA8;
$dark-grey: #7B9EA8; $dark-grey: #7a7d7b;
$green: #44CF6C; $green: #44CF6C;
$black: #000000; $black: #000000;
@ -33,6 +33,12 @@ $black: #000000;
fill: $black; fill: $black;
} }
&.blocked {
background-color: $dark-grey;
color: $black;
fill: $black;
}
&.is-checked { &.is-checked {
border: 1px solid $green; border: 1px solid $green;
background-color: $green; background-color: $green;