From 88229e74c96a67621be6d485657db93d3b2195c5 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 22 Mar 2019 14:41:49 +0200 Subject: [PATCH] Configuration submission --- monkey/monkey_island/cc/app.py | 4 +- monkey/monkey_island/cc/resources/attack.py | 18 ++ monkey/monkey_island/cc/resources/attck.py | 12 -- monkey/monkey_island/cc/resources/root.py | 2 + .../cc/services/{attck => attack}/__init__.py | 0 .../cc/services/attack/attack.py | 44 +++++ .../attack_schema.py} | 12 +- .../monkey_island/cc/services/attck/attck.py | 43 ----- .../src/components/attack/MatrixComponent.js | 156 ++++++++++++++++++ .../src/components/attck/MatrixComponent.js | 83 ---------- .../cc/ui/src/components/pages/AttckPage.js | 11 +- monkey/monkey_island/cc/ui/src/styles/App.css | 5 + .../cc/ui/src/styles/Checkbox.scss | 8 +- 13 files changed, 245 insertions(+), 153 deletions(-) create mode 100644 monkey/monkey_island/cc/resources/attack.py delete mode 100644 monkey/monkey_island/cc/resources/attck.py rename monkey/monkey_island/cc/services/{attck => attack}/__init__.py (100%) create mode 100644 monkey/monkey_island/cc/services/attack/attack.py rename monkey/monkey_island/cc/services/{attck/attck_schema.py => attack/attack_schema.py} (87%) delete mode 100644 monkey/monkey_island/cc/services/attck/attck.py create mode 100644 monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js delete mode 100644 monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 4d3148414..c313cea50 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -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/?load=', '/api/fileUpload/?restore=') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') - api.add_resource(AttckConfiguration, '/api/attck') + api.add_resource(AttckConfiguration, '/api/attack') return app diff --git a/monkey/monkey_island/cc/resources/attack.py b/monkey/monkey_island/cc/resources/attack.py new file mode 100644 index 000000000..dd8be055e --- /dev/null +++ b/monkey/monkey_island/cc/resources/attack.py @@ -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 {} + diff --git a/monkey/monkey_island/cc/resources/attck.py b/monkey/monkey_island/cc/resources/attck.py deleted file mode 100644 index 3b0d1605b..000000000 --- a/monkey/monkey_island/cc/resources/attck.py +++ /dev/null @@ -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']) - diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 923535096..46e2b4638 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -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') diff --git a/monkey/monkey_island/cc/services/attck/__init__.py b/monkey/monkey_island/cc/services/attack/__init__.py similarity index 100% rename from monkey/monkey_island/cc/services/attck/__init__.py rename to monkey/monkey_island/cc/services/attack/__init__.py diff --git a/monkey/monkey_island/cc/services/attack/attack.py b/monkey/monkey_island/cc/services/attack/attack.py new file mode 100644 index 000000000..b4f95b4df --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/attack.py @@ -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 diff --git a/monkey/monkey_island/cc/services/attck/attck_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py similarity index 87% rename from monkey/monkey_island/cc/services/attck/attck_schema.py rename to monkey/monkey_island/cc/services/attack/attack_schema.py index 1d720aa9a..8ffe73bdb 100644 --- a/monkey/monkey_island/cc/services/attck/attck_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -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." } diff --git a/monkey/monkey_island/cc/services/attck/attck.py b/monkey/monkey_island/cc/services/attck/attck.py deleted file mode 100644 index 1c7965a4a..000000000 --- a/monkey/monkey_island/cc/services/attck/attck.py +++ /dev/null @@ -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 diff --git a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js new file mode 100644 index 000000000..128a230be --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js @@ -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 (
) + } else { + return ( + + {technique.title} + + ) + } + }; + + 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 ( +
+
+ +
+ { this.state.lastAction === 'reset' ? +
+ + Matrix reset to default. +
+ : ''} + { this.state.lastAction === 'saved' ? +
+ + Matrix applied to configuration. +
+ : ''} + { this.state.lastAction === 'invalid_configuration' ? +
+ + An invalid matrix configuration supplied, check selected fields. +
+ : ''} +
+
+ +
+ +
); + } +} + +export default MatrixComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js deleted file mode 100644 index 98ce1604d..000000000 --- a/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js +++ /dev/null @@ -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 (
) - } else { - return ( - {technique.title} ) - } -}; - -// 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 (); - } -} - -export default MatrixComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js index ca073a7ed..a3d1b45fb 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js @@ -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 = (

Fetching configuration...

); - } else { - content = (); - } + content = (); return
{content}
; } } diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css index 6155a4dcc..8b1c45d7a 100644 --- a/monkey/monkey_island/cc/ui/src/styles/App.css +++ b/monkey/monkey_island/cc/ui/src/styles/App.css @@ -515,3 +515,8 @@ body { } } + +/* Attack config page */ +.attack-matrix .messages { + margin-bottom: 30px; +} diff --git a/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss b/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss index cb24563f7..b590b2a08 100644 --- a/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss +++ b/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss @@ -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;