From 11e6d3807a4b1f18b57065776ebc199f61255800 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 26 Mar 2019 20:09:09 +0200 Subject: [PATCH 01/35] Added custom post breach values to example.conf --- monkey/infection_monkey/example.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index ca8041382..cf79dbccc 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -98,4 +98,8 @@ "victims_max_exploit": 7, "victims_max_find": 30, "post_breach_actions" : [] + custom_PBA_linux_cmd = "" + custom_PBA_windows_cmd = "" + PBA_linux_filename = None + PBA_windows_filename = None } From 1c847ccc0664b88fedb25d7831439145097458dd Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 12 Mar 2019 12:15:10 +0200 Subject: [PATCH 02/35] Att&ck implementation started --- .../src/components/config-components/Att&ck.js | 18 ++++++++++++++++++ .../ui/src/components/pages/ConfigurePage.js | 6 +++++- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/config-components/Att&ck.js diff --git a/monkey/monkey_island/cc/ui/src/components/config-components/Att&ck.js b/monkey/monkey_island/cc/ui/src/components/config-components/Att&ck.js new file mode 100644 index 000000000..d5b13b6aa --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/config-components/Att&ck.js @@ -0,0 +1,18 @@ +import React from 'react'; + +// ATT&CK component +class AttackComponent extends React.Component { + constructor(props) { + super(props); + } + + render() { + return ( +
+ ATT&CK component +
+ ); + } +} + +export default AttackComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index bb369fa73..0d48251de 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -3,6 +3,7 @@ import Form from 'react-jsonschema-form'; import {Col, Nav, NavItem} from 'react-bootstrap'; import fileDownload from 'js-file-download'; import AuthComponent from '../AuthComponent'; +import AttackComponent from 'components/config-components/Att&ck' import { FilePond } from 'react-filepond'; import 'filepond/dist/filepond.min.css'; @@ -13,6 +14,8 @@ class ConfigurePageComponent extends AuthComponent { this.PBAlinuxPond = null; this.currentSection = 'basic'; this.currentFormData = {}; + this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal', 'ATT&CK']; + this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; this.uiSchema = { behaviour: { @@ -296,7 +299,8 @@ class ConfigurePageComponent extends AuthComponent { :
} - { this.state.selectedSection ? + { this.state.selectedSection === 'ATT&CK' ? + : this.state.selectedSection ?
Date: Fri, 15 Mar 2019 18:08:58 +0200 Subject: [PATCH 03/35] Sketches of config implementation --- monkey/monkey_island/cc/app.py | 4 +- monkey/monkey_island/cc/resources/attck.py | 13 ++++++ .../cc/services/attck/__init__.py | 1 + .../monkey_island/cc/services/attck/attck.py | 43 +++++++++++++++++ .../cc/services/attck/attck_schema.py | 46 +++++++++++++++++++ .../cc/ui/src/components/Main.js | 3 ++ .../components/config-components/Att&ck.js | 18 -------- .../cc/ui/src/components/pages/AttckPage.js | 45 ++++++++++++++++++ .../ui/src/components/pages/ConfigurePage.js | 12 ++--- 9 files changed, 158 insertions(+), 27 deletions(-) create mode 100644 monkey/monkey_island/cc/resources/attck.py create mode 100644 monkey/monkey_island/cc/services/attck/__init__.py create mode 100644 monkey/monkey_island/cc/services/attck/attck.py create mode 100644 monkey/monkey_island/cc/services/attck/attck_schema.py delete mode 100644 monkey/monkey_island/cc/ui/src/components/config-components/Att&ck.js create mode 100644 monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index d43930206..4d3148414 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -28,8 +28,9 @@ from cc.resources.root import Root from cc.resources.telemetry import Telemetry from cc.resources.telemetry_feed import TelemetryFeed from cc.resources.pba_file_download import PBAFileDownload -from cc.services.config import ConfigService from cc.resources.pba_file_upload import FileUpload +from cc.resources.attck import AttckConfiguration +from cc.services.config import ConfigService __author__ = 'Barak' @@ -123,5 +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') return app diff --git a/monkey/monkey_island/cc/resources/attck.py b/monkey/monkey_island/cc/resources/attck.py new file mode 100644 index 000000000..be0d09061 --- /dev/null +++ b/monkey/monkey_island/cc/resources/attck.py @@ -0,0 +1,13 @@ +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(schema=AttckService.get_config_schema(), + configuration=AttckService.get_config()) + diff --git a/monkey/monkey_island/cc/services/attck/__init__.py b/monkey/monkey_island/cc/services/attck/__init__.py new file mode 100644 index 000000000..98867ed4d --- /dev/null +++ b/monkey/monkey_island/cc/services/attck/__init__.py @@ -0,0 +1 @@ +__author__ = 'VakarisZ' diff --git a/monkey/monkey_island/cc/services/attck/attck.py b/monkey/monkey_island/cc/services/attck/attck.py new file mode 100644 index 000000000..1c7965a4a --- /dev/null +++ b/monkey/monkey_island/cc/services/attck/attck.py @@ -0,0 +1,43 @@ +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/services/attck/attck_schema.py b/monkey/monkey_island/cc/services/attck/attck_schema.py new file mode 100644 index 000000000..67f2316ef --- /dev/null +++ b/monkey/monkey_island/cc/services/attck/attck_schema.py @@ -0,0 +1,46 @@ +SCHEMA = { + "title": "ATT&CK configuration", + "type": "object", + "properties": { + "lateral_movement": { + "title": "Lateral movement", + "type": "object", + "properties": { + "T1210": { + "title": "T1210 Exploitation of Remote services", + "type": "bool", + "default": True, + "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." + } + } + }, + "credential_access": { + "title": "Credential access", + "type": "object", + "properties": { + "T1110": { + "title": "T1110 Brute force", + "type": "bool", + "default": True, + "description": "Adversaries may use brute force techniques to attempt access to accounts " + "when passwords are unknown or when password hashes are obtained." + } + } + }, + "defence_evasion": { + "title": "Defence evasion", + "type": "object", + "properties": { + "T1197": { + "title": "T1197 Bits jobs", + "type": "bool", + "default": True, + "description": "Adversaries may abuse BITS to download, execute, " + "and even clean up after running malicious code." + } + } + }, + } +} diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index da8e59113..5dfe465fa 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -14,6 +14,7 @@ import ReportPage from 'components/pages/ReportPage'; import LicensePage from 'components/pages/LicensePage'; import AuthComponent from 'components/AuthComponent'; import LoginPageComponent from 'components/pages/LoginPage'; +import AttckPage from 'components/pages/AttckPage' import 'normalize.css/normalize.css'; import 'react-data-components/css/table-twbs.css'; @@ -161,6 +162,7 @@ class AppComponent extends AuthComponent {
    +
  • ATT&CK Configuration
  • Configuration
  • Log
@@ -186,6 +188,7 @@ class AppComponent extends AuthComponent { {this.renderRoute('/start-over', )} {this.renderRoute('/report', )} {this.renderRoute('/license', )} + {this.renderRoute('/attck', )} diff --git a/monkey/monkey_island/cc/ui/src/components/config-components/Att&ck.js b/monkey/monkey_island/cc/ui/src/components/config-components/Att&ck.js deleted file mode 100644 index d5b13b6aa..000000000 --- a/monkey/monkey_island/cc/ui/src/components/config-components/Att&ck.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; - -// ATT&CK component -class AttackComponent extends React.Component { - constructor(props) { - super(props); - } - - render() { - return ( -
- ATT&CK component -
- ); - } -} - -export default AttackComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js new file mode 100644 index 000000000..5d9e300c7 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js @@ -0,0 +1,45 @@ +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'; + +class AttckComponent extends AuthComponent { + constructor(props) { + super(props); + this.currentSection = 'ATT&CK matrix'; + this.currentFormData = {}; + this.sectionsOrder = ['ATT&CK matrix']; + // set schema from server + this.state = { + schema: {}, + configuration: {}, + lastAction: 'none', + sections: [], + selectedSection: 'ATT&CK matrix', + }; + } + + componentDidMount() { + this.authFetch('/api/attck') + .then(res => res.json()) + .then(res => { + let sections = []; + for (let sectionKey of this.sectionsOrder) { + sections.push({key: sectionKey, title: res.configuration.title}); + } + this.setState({ + schema: res.schema, + configuration: res.configuration, + sections: sections, + selectedSection: 'ATT&CK matrix' + }) + }); + } + + render() { + return ( Vakaris ); + } +} + +export default AttckComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 0d48251de..a3626ae1a 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -3,7 +3,6 @@ import Form from 'react-jsonschema-form'; import {Col, Nav, NavItem} from 'react-bootstrap'; import fileDownload from 'js-file-download'; import AuthComponent from '../AuthComponent'; -import AttackComponent from 'components/config-components/Att&ck' import { FilePond } from 'react-filepond'; import 'filepond/dist/filepond.min.css'; @@ -14,8 +13,6 @@ class ConfigurePageComponent extends AuthComponent { this.PBAlinuxPond = null; this.currentSection = 'basic'; this.currentFormData = {}; - this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal', 'ATT&CK']; - this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; this.uiSchema = { behaviour: { @@ -280,6 +277,7 @@ class ConfigurePageComponent extends AuthComponent { displayedSchema = this.state.schema['properties'][this.state.selectedSection]; displayedSchema['definitions'] = this.state.schema['definitions']; } + return (

Monkey Configuration

@@ -299,14 +297,11 @@ class ConfigurePageComponent extends AuthComponent {
:
} - { this.state.selectedSection === 'ATT&CK' ? - : this.state.selectedSection ? + { this.state.selectedSection ? + onChange={this.onChange}>
{ this.state.allMonkeysAreDead ? '' : @@ -369,6 +364,7 @@ class ConfigurePageComponent extends AuthComponent {
: ''}
+ ); } From 17a51cd92eb56223197ff64b2669601a94ea43ca Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 19 Mar 2019 15:48:58 +0200 Subject: [PATCH 04/35] ATT&CK matrix formed in front-end --- monkey/monkey_island/cc/resources/attck.py | 3 +- .../cc/services/attck/attck_schema.py | 7 ++ .../src/components/attck/MatrixComponent.js | 79 ++++++++++++++ .../cc/ui/src/components/pages/AttckPage.js | 15 ++- .../src/components/ui-components/checkBox.js | 78 +++++++++++++ .../cc/ui/src/styles/CheckBox.scss | 103 ++++++++++++++++++ 6 files changed, 277 insertions(+), 8 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js create mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js create mode 100644 monkey/monkey_island/cc/ui/src/styles/CheckBox.scss diff --git a/monkey/monkey_island/cc/resources/attck.py b/monkey/monkey_island/cc/resources/attck.py index be0d09061..3b0d1605b 100644 --- a/monkey/monkey_island/cc/resources/attck.py +++ b/monkey/monkey_island/cc/resources/attck.py @@ -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']) diff --git a/monkey/monkey_island/cc/services/attck/attck_schema.py b/monkey/monkey_island/cc/services/attck/attck_schema.py index 67f2316ef..1d720aa9a 100644 --- a/monkey/monkey_island/cc/services/attck/attck_schema.py +++ b/monkey/monkey_island/cc/services/attck/attck_schema.py @@ -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." } } }, diff --git a/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js new file mode 100644 index 000000000..ec689210b --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js @@ -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 (
) + } 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) + }; + }); + } + + 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 5d9e300c7..ca073a7ed 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js @@ -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 ( Vakaris ); + let content; + if (Object.keys(this.state.configuration).length === 0) { + content = (

Fetching configuration...

); + } else { + content = (); + } + return
{content}
; } } diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js b/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js new file mode 100644 index 000000000..27eb78995 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js @@ -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 ( +
+ + + { + this.state.checked && + + + + + + } + { + !this.state.checked && + + + + + + } + +
+
+ ) + } +} +export default Checkbox; diff --git a/monkey/monkey_island/cc/ui/src/styles/CheckBox.scss b/monkey/monkey_island/cc/ui/src/styles/CheckBox.scss new file mode 100644 index 000000000..6dbf67e2c --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/CheckBox.scss @@ -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); + } +} From 7b8a758541444cce4a64939d991741b61b94ba9e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 20 Mar 2019 12:53:20 +0200 Subject: [PATCH 05/35] Adding toolbar and checkBox style fixes --- monkey/monkey_island/cc/ui/package-lock.json | 1047 +++++++++++++---- monkey/monkey_island/cc/ui/package.json | 3 + .../src/components/attck/MatrixComponent.js | 5 +- .../src/components/ui-components/checkBox.js | 17 +- .../cc/ui/src/styles/CheckBox.scss | 21 +- monkey/monkey_island/cc/ui/webpack.config.js | 8 + 6 files changed, 875 insertions(+), 226 deletions(-) diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index 11fafe535..21cd1e89c 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -408,8 +408,7 @@ "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", - "dev": true + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=" }, "accepts": { "version": "1.3.5", @@ -520,8 +519,7 @@ "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" }, "ansi-colors": { "version": "3.1.0", @@ -544,14 +542,12 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, "anymatch": { "version": "1.3.2", @@ -567,8 +563,50 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "5.1.1" + } + } + } }, "argparse": { "version": "1.0.9", @@ -604,8 +642,7 @@ "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" }, "array-flatten": { "version": "2.1.1", @@ -665,8 +702,7 @@ "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" }, "asn1.js": { "version": "4.10.1", @@ -708,8 +744,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "assertion-error": { "version": "1.1.0", @@ -741,6 +776,11 @@ "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", "dev": true }, + "async-foreach": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=" + }, "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", @@ -750,8 +790,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atob": { "version": "2.1.1", @@ -762,8 +801,7 @@ "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { "version": "1.7.0", @@ -2039,8 +2077,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", @@ -2141,7 +2178,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "dev": true, "optional": true, "requires": { "tweetnacl": "0.14.5" @@ -2182,6 +2218,14 @@ "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=", "dev": true }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "requires": { + "inherits": "2.0.3" + } + }, "bluebird": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", @@ -2280,7 +2324,6 @@ "version": "1.1.8", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", - "dev": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -2455,8 +2498,7 @@ "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" }, "builtin-status-codes": { "version": "3.0.0", @@ -2566,7 +2608,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "dev": true, "requires": { "camelcase": "2.1.1", "map-obj": "1.0.1" @@ -2575,8 +2616,7 @@ "camelcase": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" } } }, @@ -2589,8 +2629,7 @@ "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "center-align": { "version": "0.1.3", @@ -2621,7 +2660,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, "requires": { "ansi-styles": "2.2.1", "escape-string-regexp": "1.0.5", @@ -2770,6 +2808,32 @@ } } }, + "clone-deep": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", + "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", + "requires": { + "for-own": "1.0.0", + "is-plain-object": "2.0.4", + "kind-of": "6.0.2", + "shallow-clone": "1.0.0" + }, + "dependencies": { + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "requires": { + "for-in": "1.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -2778,8 +2842,7 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "collection-visit": { "version": "1.0.0", @@ -2825,7 +2888,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "dev": true, "requires": { "delayed-stream": "1.0.0" } @@ -2912,8 +2974,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.0", @@ -2996,6 +3057,11 @@ "date-now": "0.1.4" } }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, "constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", @@ -3139,8 +3205,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "create-ecdh": { "version": "4.0.3", @@ -3325,7 +3390,6 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, "requires": { "array-find-index": "1.0.2" } @@ -3355,7 +3419,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, "requires": { "assert-plus": "1.0.0" } @@ -3394,8 +3457,7 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decode-uri-component": { "version": "0.2.0", @@ -3550,8 +3612,12 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "depd": { "version": "1.1.2", @@ -3798,7 +3864,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "dev": true, "optional": true, "requires": { "jsbn": "0.1.1" @@ -3844,8 +3909,7 @@ "emojis-list": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", - "dev": true + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=" }, "encodeurl": { "version": "1.0.2", @@ -3974,7 +4038,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "requires": { "is-arrayish": "0.2.1" } @@ -4060,8 +4123,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "1.8.1", @@ -4781,8 +4843,7 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { "version": "1.1.0", @@ -4948,7 +5009,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, "requires": { "path-exists": "2.1.0", "pinkie-promise": "2.0.1" @@ -5042,8 +5102,7 @@ "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" }, "for-own": { "version": "0.1.5", @@ -5058,14 +5117,12 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "dev": true, "requires": { "asynckit": "0.4.0", "combined-stream": "1.0.6", @@ -5173,8 +5230,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "1.1.2", @@ -6074,6 +6130,17 @@ } } }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" + } + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -6086,11 +6153,53 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.3" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.1" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "requires": { + "globule": "1.2.1" + } + }, "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" }, "get-func-name": { "version": "2.0.0", @@ -6101,8 +6210,7 @@ "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" }, "get-stream": { "version": "3.0.0", @@ -6120,7 +6228,6 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, "requires": { "assert-plus": "1.0.0" } @@ -6129,7 +6236,6 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -6195,11 +6301,20 @@ "pinkie-promise": "2.0.1" } }, + "globule": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "requires": { + "glob": "7.1.3", + "lodash": "4.17.10", + "minimatch": "3.0.4" + } + }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, "growl": { "version": "1.10.5", @@ -6244,8 +6359,7 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { "version": "5.0.3", @@ -6284,7 +6398,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, "requires": { "ansi-regex": "2.1.1" } @@ -6324,6 +6437,11 @@ "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", "dev": true }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -6479,8 +6597,7 @@ "hosted-git-info": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", - "dev": true + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" }, "hpack.js": { "version": "2.1.6", @@ -6983,7 +7100,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, "requires": { "assert-plus": "1.0.0", "jsprim": "1.4.1", @@ -7109,11 +7225,15 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "in-publish": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", + "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=" + }, "indent-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, "requires": { "repeating": "2.0.1" } @@ -7128,7 +7248,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" @@ -7137,8 +7256,7 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "inquirer": { "version": "6.2.0", @@ -7240,8 +7358,7 @@ "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" }, "ip": { "version": "1.1.5", @@ -7273,8 +7390,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-binary-path": { "version": "1.0.1", @@ -7295,7 +7411,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, "requires": { "builtin-modules": "1.1.1" } @@ -7360,8 +7475,7 @@ "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" }, "is-extglob": { "version": "1.0.0", @@ -7373,7 +7487,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, "requires": { "number-is-nan": "1.0.1" } @@ -7381,8 +7494,7 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "is-glob": { "version": "2.0.1", @@ -7431,7 +7543,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "requires": { "isobject": "3.0.1" }, @@ -7439,8 +7550,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" } } }, @@ -7493,14 +7603,12 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" }, "is-windows": { "version": "1.0.2", @@ -7531,8 +7639,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "2.1.0", @@ -7556,8 +7663,7 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "istanbul": { "version": "0.4.5", @@ -7611,6 +7717,11 @@ } } }, + "js-base64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", + "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==" + }, "js-file-download": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.4.tgz", @@ -7635,7 +7746,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, "optional": true }, "jsesc": { @@ -7658,8 +7768,7 @@ "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { "version": "0.3.1", @@ -7675,8 +7784,7 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json3": { "version": "3.3.2", @@ -7703,7 +7811,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -8842,7 +8949,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, "requires": { "invert-kv": "1.0.0" } @@ -8861,7 +8967,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, "requires": { "graceful-fs": "4.1.11", "parse-json": "2.2.0", @@ -8874,7 +8979,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, "requires": { "is-utf8": "0.2.1" } @@ -8954,12 +9058,22 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" + }, "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", "dev": true }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, "lodash.curry": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", @@ -8976,6 +9090,16 @@ "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=" }, + "lodash.mergewith": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", + "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==" + }, + "lodash.tail": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", + "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=" + }, "lodash.topath": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz", @@ -9091,7 +9215,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, "requires": { "currently-unhandled": "0.4.1", "signal-exit": "3.0.2" @@ -9107,7 +9230,6 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", - "dev": true, "requires": { "pseudomap": "1.0.2", "yallist": "2.1.2" @@ -9148,8 +9270,7 @@ "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" }, "map-visit": { "version": "1.0.0", @@ -9246,7 +9367,6 @@ "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "dev": true, "requires": { "camelcase-keys": "2.1.0", "decamelize": "1.2.0", @@ -9313,14 +9433,12 @@ "mime-db": { "version": "1.29.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.29.0.tgz", - "integrity": "sha1-SNJtI1WJZRcErFkWygYAGRQmaHg=", - "dev": true + "integrity": "sha1-SNJtI1WJZRcErFkWygYAGRQmaHg=" }, "mime-types": { "version": "2.1.16", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.16.tgz", "integrity": "sha1-K4WKUuXs1RbbiXrCvodIeDBpjiM=", - "dev": true, "requires": { "mime-db": "1.29.0" } @@ -9356,7 +9474,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", - "dev": true, "requires": { "brace-expansion": "1.1.8" } @@ -9364,8 +9481,7 @@ "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "mississippi": { "version": "2.0.0", @@ -9406,11 +9522,26 @@ } } }, + "mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "requires": { + "for-in": "0.1.8", + "is-extendable": "0.1.1" + }, + "dependencies": { + "for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=" + } + } + }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, "requires": { "minimist": "0.0.8" }, @@ -9418,8 +9549,7 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" } } }, @@ -9590,8 +9720,7 @@ "neo-async": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.2.tgz", - "integrity": "sha512-vdqTKI9GBIYcAEbFAcpKPErKINfPF5zIuz3/niBfq8WUZjpT2tytLlFVrBgWdOtqI4uaA/Rb6No0hux39XXDuw==", - "dev": true + "integrity": "sha512-vdqTKI9GBIYcAEbFAcpKPErKINfPF5zIuz3/niBfq8WUZjpT2tytLlFVrBgWdOtqI4uaA/Rb6No0hux39XXDuw==" }, "next-tick": { "version": "1.0.0", @@ -9620,6 +9749,141 @@ "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==", "dev": true }, + "node-gyp": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "requires": { + "fstream": "1.0.11", + "glob": "7.1.3", + "graceful-fs": "4.1.11", + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "npmlog": "4.1.2", + "osenv": "0.1.5", + "request": "2.88.0", + "rimraf": "2.6.2", + "semver": "5.3.0", + "tar": "2.2.1", + "which": "1.3.0" + }, + "dependencies": { + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "requires": { + "fast-deep-equal": "2.0.1", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" + } + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "6.10.0", + "har-schema": "2.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "mime-db": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" + }, + "mime-types": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "requires": { + "mime-db": "1.38.0" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.8.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.1.3", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.22", + "oauth-sign": "0.9.0", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.4.3", + "tunnel-agent": "0.6.0", + "uuid": "3.3.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "1.1.31", + "punycode": "1.4.1" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, "node-libs-browser": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", @@ -9695,6 +9959,157 @@ } } }, + "node-sass": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.11.0.tgz", + "integrity": "sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==", + "requires": { + "async-foreach": "0.1.3", + "chalk": "1.1.3", + "cross-spawn": "3.0.1", + "gaze": "1.1.3", + "get-stdin": "4.0.1", + "glob": "7.1.3", + "in-publish": "2.0.0", + "lodash.assign": "4.2.0", + "lodash.clonedeep": "4.5.0", + "lodash.mergewith": "4.6.1", + "meow": "3.7.0", + "mkdirp": "0.5.1", + "nan": "2.13.1", + "node-gyp": "3.8.0", + "npmlog": "4.1.2", + "request": "2.88.0", + "sass-graph": "2.2.4", + "stdout-stream": "1.4.1", + "true-case-path": "1.0.3" + }, + "dependencies": { + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "requires": { + "fast-deep-equal": "2.0.1", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.4.1", + "uri-js": "4.2.2" + } + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "cross-spawn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "requires": { + "lru-cache": "4.1.3", + "which": "1.3.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "6.10.0", + "har-schema": "2.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "mime-db": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" + }, + "mime-types": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "requires": { + "mime-db": "1.38.0" + } + }, + "nan": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.1.tgz", + "integrity": "sha512-I6YB/YEuDeUZMmhscXKxGgZlFnhsn5y0hgOZBadkzfTRrZBtJDZeg6eQf7PYMIEclwmorTKK8GztsyOUSVBREA==" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.8.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.1.3", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.22", + "oauth-sign": "0.9.0", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.4.3", + "tunnel-agent": "0.6.0", + "uuid": "3.3.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "1.1.31", + "punycode": "1.4.1" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, "noms": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", @@ -9709,7 +10124,6 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true, "requires": { "abbrev": "1.0.9" } @@ -9718,7 +10132,6 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", "integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=", - "dev": true, "requires": { "hosted-git-info": "2.7.1", "is-builtin-module": "1.0.0", @@ -12445,6 +12858,17 @@ "path-key": "2.0.1" } }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "1.1.5", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, "nth-check": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", @@ -12463,8 +12887,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "oauth-sign": { "version": "0.8.2", @@ -12609,7 +13032,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1.0.2" } @@ -12694,8 +13116,7 @@ "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-locale": { "version": "2.1.0", @@ -12711,8 +13132,16 @@ "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } }, "output-file-sync": { "version": "1.1.2", @@ -12867,7 +13296,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, "requires": { "error-ex": "1.3.2" } @@ -12918,7 +13346,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, "requires": { "pinkie-promise": "2.0.1" } @@ -12926,8 +13353,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", @@ -12951,7 +13377,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, "requires": { "graceful-fs": "4.1.11", "pify": "2.3.0", @@ -12986,8 +13411,7 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "phantomjs-prebuilt": { "version": "2.1.16", @@ -13009,20 +13433,17 @@ "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" }, "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, "requires": { "pinkie": "2.0.4" } @@ -13267,8 +13688,7 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { "version": "1.1.20", @@ -13321,8 +13741,7 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "pure-color": { "version": "1.3.0", @@ -13344,8 +13763,7 @@ "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "querystring": { "version": "0.2.0", @@ -13666,6 +14084,14 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "react-minimalist-portal": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/react-minimalist-portal/-/react-minimalist-portal-2.3.1.tgz", + "integrity": "sha1-SFPj9Ip0oywbh2dgGIfN95Qe66M=", + "requires": { + "prop-types": "15.6.2" + } + }, "react-overlays": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-0.8.3.tgz", @@ -13806,6 +14232,15 @@ "classnames": "2.2.5" } }, + "react-tooltip-lite": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/react-tooltip-lite/-/react-tooltip-lite-1.9.1.tgz", + "integrity": "sha512-JH5T6kPZn7X90TnnNhuJ+wOb1eikT2xtpbOkndvqAHZlOyZOAZeAyVgk/3pGz0xi4h+bqXXisfwGtriliTYhDQ==", + "requires": { + "prop-types": "15.6.2", + "react-minimalist-portal": "2.3.1" + } + }, "react-transition-group": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.5.0.tgz", @@ -13831,7 +14266,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, "requires": { "load-json-file": "1.1.0", "normalize-package-data": "2.4.0", @@ -13842,7 +14276,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, "requires": { "find-up": "1.1.2", "read-pkg": "1.1.0" @@ -13928,7 +14361,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "dev": true, "requires": { "indent-string": "2.1.0", "strip-indent": "1.0.1" @@ -14085,7 +14517,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, "requires": { "is-finite": "1.0.2" } @@ -14155,14 +14586,12 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" }, "require-uncached": { "version": "1.0.3", @@ -14250,7 +14679,6 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, "requires": { "glob": "7.1.3" } @@ -14295,8 +14723,7 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=", - "dev": true + "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" }, "safe-regex": { "version": "1.1.0", @@ -14313,6 +14740,141 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "sass-graph": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", + "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", + "requires": { + "glob": "7.1.3", + "lodash": "4.17.10", + "scss-tokenizer": "0.2.3", + "yargs": "7.1.0" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.1" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + }, + "yargs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "requires": { + "camelcase": "3.0.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.3", + "os-locale": "1.4.0", + "read-pkg-up": "1.0.1", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "1.0.2", + "which-module": "1.0.0", + "y18n": "3.2.1", + "yargs-parser": "5.0.0" + } + }, + "yargs-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "requires": { + "camelcase": "3.0.0" + } + } + } + }, + "sass-loader": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", + "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==", + "requires": { + "clone-deep": "2.0.2", + "loader-utils": "1.2.3", + "lodash.tail": "4.1.1", + "neo-async": "2.5.2", + "pify": "3.0.0", + "semver": "5.6.0" + }, + "dependencies": { + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "1.2.0" + } + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "requires": { + "big.js": "5.2.2", + "emojis-list": "2.1.0", + "json5": "1.0.1" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" + } + } + }, "scheduler": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.2.tgz", @@ -14332,6 +14894,25 @@ "ajv-keywords": "3.2.0" } }, + "scss-tokenizer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", + "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", + "requires": { + "js-base64": "2.5.1", + "source-map": "0.4.4" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "requires": { + "amdefine": "1.0.1" + } + } + } + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -14350,8 +14931,7 @@ "semver": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", - "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", - "dev": true + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=" }, "send": { "version": "0.16.2", @@ -14459,8 +15039,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-immediate-shim": { "version": "1.0.1", @@ -14532,6 +15111,23 @@ } } }, + "shallow-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", + "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", + "requires": { + "is-extendable": "0.1.1", + "kind-of": "5.1.0", + "mixin-object": "2.0.1" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, "shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", @@ -14556,8 +15152,7 @@ "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "slash": { "version": "1.0.0", @@ -14862,7 +15457,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", - "dev": true, "requires": { "spdx-expression-parse": "3.0.0", "spdx-license-ids": "3.0.0" @@ -14871,14 +15465,12 @@ "spdx-exceptions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", - "dev": true + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" }, "spdx-expression-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, "requires": { "spdx-exceptions": "2.1.0", "spdx-license-ids": "3.0.0" @@ -14887,8 +15479,7 @@ "spdx-license-ids": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", - "dev": true + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==" }, "spdy": { "version": "3.4.7", @@ -14976,7 +15567,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", - "dev": true, "requires": { "asn1": "0.2.3", "assert-plus": "1.0.0", @@ -15024,6 +15614,48 @@ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true }, + "stdout-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", + "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", + "requires": { + "readable-stream": "2.3.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "5.1.1" + } + } + } + }, "stream-browserify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", @@ -15202,7 +15834,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, "requires": { "is-fullwidth-code-point": "2.0.0", "strip-ansi": "4.0.0" @@ -15211,14 +15842,12 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, "requires": { "ansi-regex": "3.0.0" } @@ -15241,7 +15870,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, "requires": { "ansi-regex": "2.1.1" } @@ -15256,7 +15884,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true, "requires": { "get-stdin": "4.0.1" } @@ -15293,8 +15920,7 @@ "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" }, "symbol-observable": { "version": "1.2.0", @@ -15358,6 +15984,16 @@ "integrity": "sha512-dQRhbNQkRnaqauC7WqSJ21EEksgT0fYZX2lqXzGkpo8JNig9zGZTYoMGvyI2nWmXlE2VSVXVDu7wLVGu/mQEsg==", "dev": true }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -15531,8 +16167,7 @@ "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" }, "trim-right": { "version": "1.0.1", @@ -15540,6 +16175,14 @@ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, + "true-case-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", + "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", + "requires": { + "glob": "7.1.3" + } + }, "tslib": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", @@ -15556,7 +16199,6 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, "requires": { "safe-buffer": "5.1.1" } @@ -15565,7 +16207,6 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true, "optional": true }, "type-check": { @@ -15826,7 +16467,6 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, "requires": { "punycode": "2.1.1" }, @@ -15834,8 +16474,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" } } }, @@ -15950,8 +16589,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util.promisify": { "version": "1.0.0", @@ -16008,7 +16646,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, "requires": { "spdx-correct": "3.0.0", "spdx-expression-parse": "3.0.0" @@ -16029,7 +16666,6 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, "requires": { "assert-plus": "1.0.0", "core-util-is": "1.0.2", @@ -18891,7 +19527,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", "integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=", - "dev": true, "requires": { "isexe": "2.0.0" } @@ -18902,6 +19537,14 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "2.1.1" + } + }, "window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", @@ -18928,7 +19571,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, "requires": { "string-width": "1.0.2", "strip-ansi": "3.0.1" @@ -18938,7 +19580,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, "requires": { "number-is-nan": "1.0.1" } @@ -18947,7 +19588,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", @@ -18959,8 +19599,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { "version": "0.2.1", @@ -19003,14 +19642,12 @@ "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" }, "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, "yargs": { "version": "3.10.0", diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index a74064c54..aad5ddf18 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -73,6 +73,7 @@ "json-loader": "^0.5.7", "jwt-decode": "^2.2.0", "moment": "^2.22.2", + "node-sass": "^4.11.0", "normalize.css": "^8.0.0", "npm": "^6.4.1", "prop-types": "^15.6.2", @@ -92,7 +93,9 @@ "react-router-dom": "^4.3.1", "react-table": "^6.8.6", "react-toggle": "^4.0.1", + "react-tooltip-lite": "^1.9.1", "redux": "^4.0.0", + "sass-loader": "^7.1.0", "sha3": "^2.0.0" } } diff --git a/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js index ec689210b..ff674fbc1 100644 --- a/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js @@ -12,7 +12,7 @@ let renderTechnique = function (technique) { if (technique == null){ return (
) } else { - return (
{technique.title}
) + return ({technique.title}) } }; @@ -59,7 +59,8 @@ class MatrixComponent extends AuthComponent { return { Header: key, id: key, - accessor: x => renderTechnique(x[key].technique) + accessor: x => renderTechnique(x[key].technique), + style: { 'white-space': 'unset' } }; }); } diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js b/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js index 27eb78995..143df9c86 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js @@ -1,11 +1,11 @@ import '../../styles/CheckBox.scss' import React from 'react'; -import MatrixComponent from "../attck/MatrixComponent"; +import Tooltip from 'react-tooltip-lite'; class Checkbox extends React.PureComponent { constructor() { - super(); + super(props); this.state = { checked: false, @@ -46,17 +46,22 @@ class Checkbox extends React.PureComponent { render() { const cl = this.composeStateClasses('ui-checkbox-btn'); - + let tooltip = ""; + if (this.props.hasOwnProperty("tooltipContent") && this.props.hasOwnProperty("tooltipDirection")){ + tooltip = () + } + } return (
- + { this.state.checked && - + @@ -64,7 +69,7 @@ class Checkbox extends React.PureComponent { { !this.state.checked && - + diff --git a/monkey/monkey_island/cc/ui/src/styles/CheckBox.scss b/monkey/monkey_island/cc/ui/src/styles/CheckBox.scss index 6dbf67e2c..fc325edf2 100644 --- a/monkey/monkey_island/cc/ui/src/styles/CheckBox.scss +++ b/monkey/monkey_island/cc/ui/src/styles/CheckBox.scss @@ -1,6 +1,6 @@ // readable -$desired-line-height: 24px; -$desired-height: 36px; +$desired-line-height: 100%; +$desired-height: 100%; $text-offset: 2px; // usable @@ -13,14 +13,15 @@ $light-grey: #EAF4F4; $medium-grey: #7B9EA8; $dark-grey: #7B9EA8; $green: #44CF6C; +$black: #000000; .ui-checkbox-btn { position: relative; display: inline-block; - - padding: (($dh - $dlh) / 2) ($dlh / 2); - border-radius: $dh / 2; // overcompensate background-color: rgba(red, .6); + text-align: center; + width: 100%; + height: 100%; input { display: none; } // turn off, but not forgotten @@ -33,17 +34,13 @@ $green: #44CF6C; .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; + color: $black; + fill: $black; } &.is-checked { @@ -57,8 +54,6 @@ $green: #44CF6C; .icon { position: relative; display: inline-block; - width: $dlh - 4; - height: $dlh; svg { position: absolute; diff --git a/monkey/monkey_island/cc/ui/webpack.config.js b/monkey/monkey_island/cc/ui/webpack.config.js index b6f7d2dfa..7c56ccff2 100644 --- a/monkey/monkey_island/cc/ui/webpack.config.js +++ b/monkey/monkey_island/cc/ui/webpack.config.js @@ -18,6 +18,14 @@ module.exports = { 'css-loader' ] }, + { + test: /\.scss$/, + use: [ + 'style-loader', + 'css-loader', + 'sass-loader' + ] + }, { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, use: { From b319bc8e5f150514e3a472788b00d77ddf40473b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 20 Mar 2019 16:45:43 +0200 Subject: [PATCH 06/35] Toolbar with technique description added to checkboxes --- .../src/components/attck/MatrixComponent.js | 9 ++- .../src/components/ui-components/checkbox.js | 61 +++++++++++++++++++ .../styles/{CheckBox.scss => Checkbox.scss} | 16 +---- .../cc/ui/src/styles/Tooltip.scss | 8 +++ 4 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js rename monkey/monkey_island/cc/ui/src/styles/{CheckBox.scss => Checkbox.scss} (84%) create mode 100644 monkey/monkey_island/cc/ui/src/styles/Tooltip.scss diff --git a/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js index ff674fbc1..98ce1604d 100644 --- a/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/attck/MatrixComponent.js @@ -1,10 +1,12 @@ import React from 'react'; import Form from 'react-jsonschema-form'; import {Col, Nav, NavItem} from 'react-bootstrap'; -import CheckBox from '../ui-components/checkBox' +import Checkbox from '../ui-components/checkbox' +import Tooltip from 'react-tooltip-lite' import AuthComponent from '../AuthComponent'; -import 'filepond/dist/filepond.min.css'; import ReactTable from "react-table"; +import 'filepond/dist/filepond.min.css'; +import '../../styles/Tooltip.scss'; let renderTechnique = function (technique) { @@ -12,7 +14,8 @@ let renderTechnique = function (technique) { if (technique == null){ return (
) } else { - return ({technique.title}) + return ( + {technique.title} ) } }; diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js b/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js new file mode 100644 index 000000000..1d3754036 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js @@ -0,0 +1,61 @@ +import '../../styles/Checkbox.scss' +import React from 'react'; + +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 ( +
+ + +
+
+ ) + } +} + +export default Checkbox; diff --git a/monkey/monkey_island/cc/ui/src/styles/CheckBox.scss b/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss similarity index 84% rename from monkey/monkey_island/cc/ui/src/styles/CheckBox.scss rename to monkey/monkey_island/cc/ui/src/styles/Checkbox.scss index fc325edf2..cb24563f7 100644 --- a/monkey/monkey_island/cc/ui/src/styles/CheckBox.scss +++ b/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss @@ -1,14 +1,4 @@ -// readable -$desired-line-height: 100%; -$desired-height: 100%; -$text-offset: 2px; - -// usable -$dlh: $desired-line-height; -$dh: $desired-height; -$to: $text-offset; - -// coooolors +// colors $light-grey: #EAF4F4; $medium-grey: #7B9EA8; $dark-grey: #7B9EA8; @@ -23,16 +13,16 @@ $black: #000000; width: 100%; height: 100%; - input { display: none; } // turn off, but not forgotten + input { display: none; } .icon, .text { display: inline-block; - vertical-align: top; color: inherit; } .text { + padding-top: 4px; font-size: 14px; } diff --git a/monkey/monkey_island/cc/ui/src/styles/Tooltip.scss b/monkey/monkey_island/cc/ui/src/styles/Tooltip.scss new file mode 100644 index 000000000..7d2ff9d35 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/styles/Tooltip.scss @@ -0,0 +1,8 @@ +$background: #000000; +$font: #fff; + +.react-tooltip-lite { + background: $background; + color: $font; + max-width: 400px !important; +} From 88229e74c96a67621be6d485657db93d3b2195c5 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 22 Mar 2019 14:41:49 +0200 Subject: [PATCH 07/35] 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; From f5da1bf3b6cbb9d9090486f4962a254b8815f473 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 25 Mar 2019 10:04:17 +0200 Subject: [PATCH 08/35] Matrix submit bugfix --- monkey/monkey_island/cc/ui/src/components/Main.js | 2 +- .../cc/ui/src/components/attack/MatrixComponent.js | 3 +-- .../components/pages/{AttckPage.js => AttackPage.js} | 10 +++++++--- 3 files changed, 9 insertions(+), 6 deletions(-) rename monkey/monkey_island/cc/ui/src/components/pages/{AttckPage.js => AttackPage.js} (77%) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 5dfe465fa..7fc9bbb80 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -14,7 +14,7 @@ import ReportPage from 'components/pages/ReportPage'; import LicensePage from 'components/pages/LicensePage'; import AuthComponent from 'components/AuthComponent'; import LoginPageComponent from 'components/pages/LoginPage'; -import AttckPage from 'components/pages/AttckPage' +import AttckPage from 'components/pages/AttackPage' import 'normalize.css/normalize.css'; import 'react-data-components/css/table-twbs.css'; diff --git a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js index 128a230be..309e2bbea 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js @@ -80,7 +80,6 @@ class MatrixComponent extends AuthComponent { }; onSubmit = () => { - console.log(this.state.matrixData); this.authFetch('/api/attack', { method: 'POST', @@ -144,7 +143,7 @@ class MatrixComponent extends AuthComponent { : ''}
-
diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js similarity index 77% rename from monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js rename to monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js index a3d1b45fb..397f558e9 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/AttckPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js @@ -3,7 +3,7 @@ import AuthComponent from '../AuthComponent'; import 'filepond/dist/filepond.min.css'; import MatrixComponent from '../attack/MatrixComponent' -class AttckComponent extends AuthComponent { +class AttackComponent extends AuthComponent { constructor(props) { super(props); this.currentSection = 'ATT&CK matrix'; @@ -35,9 +35,13 @@ class AttckComponent extends AuthComponent { render() { let content; - content = (); + if (Object.keys(this.state.configuration).length === 0) { + content = (

Fetching configuration...

); + } else { + content = (); + } return
{content}
; } } -export default AttckComponent; +export default AttackComponent; From cce76f37dfc174b5e0d1641956ad612e196a649c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 25 Mar 2019 21:37:55 +0200 Subject: [PATCH 09/35] Configuration "reset to defaults" added --- monkey/monkey_island/cc/app.py | 4 +- monkey/monkey_island/cc/resources/attack.py | 11 ++- .../cc/ui/src/components/Main.js | 4 +- .../src/components/attack/MatrixComponent.js | 70 ++++++++++++++----- 4 files changed, 64 insertions(+), 25 deletions(-) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index c313cea50..f43492713 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.attack import AttckConfiguration +from cc.resources.attack import AttackConfiguration 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/attack') + api.add_resource(AttackConfiguration, '/api/attack') return app diff --git a/monkey/monkey_island/cc/resources/attack.py b/monkey/monkey_island/cc/resources/attack.py index dd8be055e..25af9100c 100644 --- a/monkey/monkey_island/cc/resources/attack.py +++ b/monkey/monkey_island/cc/resources/attack.py @@ -6,13 +6,18 @@ from cc.auth import jwt_required from cc.services.attack.attack import AttackService -class AttckConfiguration(flask_restful.Resource): +class AttackConfiguration(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 {} + config_json = json.loads(request.data) + if 'reset_attack_matrix' in config_json: + AttackService.reset_config() + return jsonify(configuration=AttackService.get_config()['properties']) + else: + AttackService.update_config({'properties': json.loads(request.data)}) + return {} diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 7fc9bbb80..291cfde58 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -162,7 +162,7 @@ class AppComponent extends AuthComponent {
    -
  • ATT&CK Configuration
  • +
  • ATT&CK Configuration
  • Configuration
  • Log
@@ -188,7 +188,7 @@ class AppComponent extends AuthComponent { {this.renderRoute('/start-over', )} {this.renderRoute('/report', )} {this.renderRoute('/license', )} - {this.renderRoute('/attck', )} + {this.renderRoute('/attack', )} diff --git a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js index 309e2bbea..818a5b8e6 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js @@ -46,15 +46,18 @@ let parseTechniques = function (data, maxLen) { 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); - } + this.state = {lastAction: 'none', + configData: this.props.configuration, + maxTechniques: findMaxTechniques(Object.values(configCopy))}; + this.state.matrixTableData = parseTechniques(Object.values(configCopy), this.state.maxTechniques); + this.state.columns = this.getColumns(this.state.matrixTableData) + }; - getColumns() { - return Object.keys(this.data[0]).map((key)=>{ + + getColumns(matrixData) { + return Object.keys(matrixData[0]).map((key)=>{ return { Header: key, id: key, @@ -84,7 +87,7 @@ class MatrixComponent extends AuthComponent { { method: 'POST', headers: {'Content-Type': 'application/json'}, - body: JSON.stringify(this.state.matrixData) + body: JSON.stringify(this.state.configData) }) .then(res => { if (!res.ok) @@ -102,26 +105,54 @@ class MatrixComponent extends AuthComponent { }); }; - 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}); - } + resetConfig = () => { + this.authFetch('/api/attack', + { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify('reset_attack_matrix') + }) + .then(res => res.json()) + .then(res => { + this.updateStateFromConfig(res.configuration, 'reset') + }); + }; + + updateStateFromConfig = (config, lastAction = '') => { + let configCopy = JSON.parse(JSON.stringify(config)); + let maxTechniques = findMaxTechniques(Object.values(configCopy)); + let matrixTableData = parseTechniques(Object.values(configCopy), maxTechniques); + let columns = this.getColumns(matrixTableData); + this.setState({ + lastAction: lastAction, + configData: config, + maxTechniques: maxTechniques, + matrixTableData: matrixTableData, + columns: columns }); }; + handleTechniqueChange = (technique, value) => { + // Change value on configuration + Object.entries(this.state.configData).forEach(techType => { + if(techType[1].properties.hasOwnProperty(technique)){ + let tempMatrix = this.state.configData; + tempMatrix[techType[0]].properties[technique].value = value; + this.updateStateFromConfig(tempMatrix); + } + }); + + }; + render() { - let columns = this.getColumns(); return (
+ defaultPageSize={this.state.maxTechniques} />
{ this.state.lastAction === 'reset' ?
@@ -146,6 +177,9 @@ class MatrixComponent extends AuthComponent { +
); From b3f7b29640ce9cbc9a4b12982126148c96f897a7 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 27 Mar 2019 19:02:33 +0200 Subject: [PATCH 10/35] ATT&CK fields can be mapped together, ATT&CK configuration gets translated to monkey's configuration --- monkey/monkey_island/cc/app.py | 2 +- monkey/monkey_island/cc/resources/attack.py | 23 ----- .../cc/resources/attack_config.py | 28 ++++++ monkey/monkey_island/cc/resources/root.py | 4 +- monkey/monkey_island/cc/server_config.json | 2 +- .../cc/services/attack/attack.py | 44 ---------- .../cc/services/attack/attack_config.py | 86 +++++++++++++++++++ .../cc/services/attack/attack_schema.py | 26 ++++++ .../cc/services/config_schema.py | 36 +++++--- .../src/components/attack/MatrixComponent.js | 11 ++- 10 files changed, 177 insertions(+), 85 deletions(-) delete mode 100644 monkey/monkey_island/cc/resources/attack.py create mode 100644 monkey/monkey_island/cc/resources/attack_config.py delete mode 100644 monkey/monkey_island/cc/services/attack/attack.py create mode 100644 monkey/monkey_island/cc/services/attack/attack_config.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index f43492713..c793c42cc 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.attack import AttackConfiguration +from cc.resources.attack_config import AttackConfiguration from cc.services.config import ConfigService __author__ = 'Barak' diff --git a/monkey/monkey_island/cc/resources/attack.py b/monkey/monkey_island/cc/resources/attack.py deleted file mode 100644 index 25af9100c..000000000 --- a/monkey/monkey_island/cc/resources/attack.py +++ /dev/null @@ -1,23 +0,0 @@ -import flask_restful -import json -from flask import jsonify, request - -from cc.auth import jwt_required -from cc.services.attack.attack import AttackService - - -class AttackConfiguration(flask_restful.Resource): - @jwt_required() - def get(self): - return jsonify(configuration=AttackService.get_config()['properties']) - - @jwt_required() - def post(self): - config_json = json.loads(request.data) - if 'reset_attack_matrix' in config_json: - AttackService.reset_config() - return jsonify(configuration=AttackService.get_config()['properties']) - else: - AttackService.update_config({'properties': json.loads(request.data)}) - return {} - diff --git a/monkey/monkey_island/cc/resources/attack_config.py b/monkey/monkey_island/cc/resources/attack_config.py new file mode 100644 index 000000000..0e33fc6d1 --- /dev/null +++ b/monkey/monkey_island/cc/resources/attack_config.py @@ -0,0 +1,28 @@ +import flask_restful +import json +from flask import jsonify, request + +from cc.auth import jwt_required +from cc.services.attack.attack_config import * + + +class AttackConfiguration(flask_restful.Resource): + @jwt_required() + def get(self): + return jsonify(configuration=get_config()['properties']) + + @jwt_required() + def post(self): + """ + Based on request content this endpoint either resets ATT&CK configuration or updates it. + :return: Technique types dict with techniques on reset and nothing on update + """ + config_json = json.loads(request.data) + if 'reset_attack_matrix' in config_json: + reset_config() + return jsonify(configuration=get_config()['properties']) + else: + update_config({'properties': json.loads(request.data)}) + apply_to_monkey_config() + return {} + diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 46e2b4638..6296f9cfc 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -7,7 +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.attack.attack_config import reset_config as reset_attack_config from cc.services.node import NodeService from cc.services.report import ReportService from cc.utils import local_ip_addresses @@ -48,7 +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() + reset_attack_config() logger.info('DB was reset') return jsonify(status='OK') diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 2d1a5995b..d53173b82 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,3 +1,3 @@ { "server_config": "standard" -} \ No newline at end of file +} diff --git a/monkey/monkey_island/cc/services/attack/attack.py b/monkey/monkey_island/cc/services/attack/attack.py deleted file mode 100644 index b4f95b4df..000000000 --- a/monkey/monkey_island/cc/services/attack/attack.py +++ /dev/null @@ -1,44 +0,0 @@ -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/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py new file mode 100644 index 000000000..d03a7cc21 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -0,0 +1,86 @@ +import logging +from cc.database import mongo +from attack_schema import SCHEMA +from cc.services.config import ConfigService + +__author__ = "VakarisZ" + +logger = logging.getLogger(__name__) + + +def get_config(): + config = mongo.db.attack.find_one({'name': 'newconfig'}) or reset_config() + return config + + +def get_config_schema(): + return SCHEMA + + +def reset_config(): + update_config(SCHEMA) + + +def update_config(config_json): + mongo.db.attack.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) + return True + + +def apply_to_monkey_config(): + """ + Applies ATT&CK matrix in the database to the monkey configuration + :return: + """ + attack_techniques = get_techniques() + monkey_config = ConfigService.get_config(False, True, True) + monkey_schema = ConfigService.get_config_schema() + set_exploiters(attack_techniques, monkey_config, monkey_schema) + ConfigService.update_config(monkey_config, True) + + +def set_exploiters(attack_techniques, monkey_config, monkey_schema): + """ + Sets exploiters according to ATT&CK matrix + :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} + :param monkey_config: Monkey island's configuration + :param monkey_schema: Monkey configuration schema + """ + for exploiter in monkey_schema['definitions']['exploiter_classes']['anyOf']: + # Go trough each attack technique used by exploiter + for attack_technique in exploiter['attack_techniques']: + # If exploiter's attack technique is disabled, disable the exploiter + if not attack_techniques[attack_technique]: + remove_exploiter(exploiter['enum'][0], monkey_config) + break + # If exploiter's attack technique is enabled, enable the exploiter + else: + add_exploiter(exploiter['enum'][0], monkey_config) + + +def remove_exploiter(exploiter, monkey_config): + """ + Removes exploiter from monkey's configuration + :param exploiter: Exploiter class name found in SCHEMA->definitions->exploiter_classes -> anyOf -> enum + :param monkey_config: Monkey's configuration + """ + if exploiter in monkey_config['exploits']['general']['exploiter_classes']: + monkey_config['exploits']['general']['exploiter_classes'].remove(exploiter) + + +def add_exploiter(exploiter, monkey_config): + """ + Adds exploiter to monkey's configuration + :param exploiter: Exploiter class name found in SCHEMA->definitions->exploiter_classes -> anyOf -> enum + :param monkey_config: Monkey's configuration + """ + if not exploiter in monkey_config['exploits']['general']['exploiter_classes']: + monkey_config['exploits']['general']['exploiter_classes'].append(exploiter) + + +def get_techniques(): + attack_config = get_config() + techniques = {} + for key, attack_type in attack_config['properties'].items(): + for key, technique in attack_type['properties'].items(): + techniques[key] = technique['value'] + return techniques diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 8ffe73bdb..582eeb876 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -2,6 +2,22 @@ SCHEMA = { "title": "ATT&CK configuration", "type": "object", "properties": { + "initial_access": { + "title": "Initial access", + "type": "object", + "properties": { + "T1078": { + "title": "T1078 Valid accounts", + "type": "bool", + "value": True, + "necessary": False, + "description": "Adversaries may steal the credentials of a specific user or service account using " + "Credential Access techniques or capture credentials earlier in their " + "reconnaissance process.", + "mapped_to": ["T1003"] + } + } + }, "lateral_movement": { "title": "Lateral movement", "type": "object", @@ -36,6 +52,16 @@ SCHEMA = { "necessary": False, "description": "Adversaries may use brute force techniques to attempt access to accounts " "when passwords are unknown or when password hashes are obtained." + }, + "T1003": { + "title": "T1003 Credential dumping", + "type": "bool", + "value": True, + "necessary": False, + "description": "Credential dumping is the process of obtaining account login and password " + "information, normally in the form of a hash or a clear text password, " + "from the operating system and software.", + "mapped_to": ["T1078"] } } }, diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 382b591db..c5611e4e1 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -13,84 +13,96 @@ SCHEMA = { "enum": [ "SmbExploiter" ], - "title": "SMB Exploiter" + "title": "SMB Exploiter", + "attack_techniques": ["T1110", "T1210", "T1021", "T1035", "T1075"] }, { "type": "string", "enum": [ "WmiExploiter" ], - "title": "WMI Exploiter" + "title": "WMI Exploiter", + "attack_techniques": ["T1110"] }, { "type": "string", "enum": [ "MSSQLExploiter" ], - "title": "MSSQL Exploiter" + "title": "MSSQL Exploiter", + "attack_techniques": ["T1110"] }, { "type": "string", "enum": [ "RdpExploiter" ], - "title": "RDP Exploiter (UNSAFE)" + "title": "RDP Exploiter (UNSAFE)", + "attack_techniques": [] }, { "type": "string", "enum": [ "Ms08_067_Exploiter" ], - "title": "MS08-067 Exploiter (UNSAFE)" + "title": "MS08-067 Exploiter (UNSAFE)", + "attack_techniques": [] }, { "type": "string", "enum": [ "SSHExploiter" ], - "title": "SSH Exploiter" + "title": "SSH Exploiter", + "attack_techniques": ["T1110"] }, { "type": "string", "enum": [ "ShellShockExploiter" ], - "title": "ShellShock Exploiter" + "title": "ShellShock Exploiter", + "attack_techniques": [] }, { "type": "string", "enum": [ "SambaCryExploiter" ], - "title": "SambaCry Exploiter" + "title": "SambaCry Exploiter", + "attack_techniques": [] }, { "type": "string", "enum": [ "ElasticGroovyExploiter" ], - "title": "ElasticGroovy Exploiter" + "title": "ElasticGroovy Exploiter", + "attack_techniques": [] }, { "type": "string", "enum": [ "Struts2Exploiter" ], - "title": "Struts2 Exploiter" + "title": "Struts2 Exploiter", + "attack_techniques": [] }, { "type": "string", "enum": [ "WebLogicExploiter" ], - "title": "Oracle Web Logic Exploiter" + "title": "Oracle Web Logic Exploiter", + "attack_techniques": [] }, { "type": "string", "enum": [ "HadoopExploiter" ], - "title": "Hadoop/Yarn Exploiter" + "title": "Hadoop/Yarn Exploiter", + "attack_techniques": [] } ] }, diff --git a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js index 818a5b8e6..8267449d0 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js @@ -55,7 +55,6 @@ class MatrixComponent extends AuthComponent { this.state.columns = this.getColumns(this.state.matrixTableData) }; - getColumns(matrixData) { return Object.keys(matrixData[0]).map((key)=>{ return { @@ -132,12 +131,20 @@ class MatrixComponent extends AuthComponent { }); }; - handleTechniqueChange = (technique, value) => { + handleTechniqueChange = (technique, value, mapped=false) => { // Change value on configuration Object.entries(this.state.configData).forEach(techType => { if(techType[1].properties.hasOwnProperty(technique)){ let tempMatrix = this.state.configData; tempMatrix[techType[0]].properties[technique].value = value; + // Toggle all mapped techniques + if (! mapped && tempMatrix[techType[0]].properties[technique].hasOwnProperty('mapped_to')){ + console.log("Triggered"); + tempMatrix[techType[0]].properties[technique].mapped_to.forEach(mappedTechnique => { + console.log(mappedTechnique) + this.handleTechniqueChange(mappedTechnique, value, true) + }) + } this.updateStateFromConfig(tempMatrix); } }); From 5e73306b9cc909d000e423d412dd1a221acdf31b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 28 Mar 2019 17:05:29 +0200 Subject: [PATCH 11/35] Implemented enabling/disabling array and boolean configuration fields according to ATT&CK matrix. --- .../cc/services/attack/attack_config.py | 97 +++++++++++++------ .../cc/services/attack/attack_schema.py | 4 +- .../cc/services/config_schema.py | 6 +- .../src/components/attack/MatrixComponent.js | 4 +- monkey/monkey_island/requirements.txt | 3 +- 5 files changed, 80 insertions(+), 34 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py index d03a7cc21..713eeab36 100644 --- a/monkey/monkey_island/cc/services/attack/attack_config.py +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -1,4 +1,5 @@ import logging +from dpath import util from cc.database import mongo from attack_schema import SCHEMA from cc.services.config import ConfigService @@ -28,56 +29,96 @@ def update_config(config_json): def apply_to_monkey_config(): """ - Applies ATT&CK matrix in the database to the monkey configuration + Applies ATT&CK matrix to the monkey configuration :return: """ attack_techniques = get_techniques() monkey_config = ConfigService.get_config(False, True, True) monkey_schema = ConfigService.get_config_schema() - set_exploiters(attack_techniques, monkey_config, monkey_schema) + set_arrays(attack_techniques, monkey_config, monkey_schema) + set_booleans(attack_techniques, monkey_config, monkey_schema) ConfigService.update_config(monkey_config, True) -def set_exploiters(attack_techniques, monkey_config, monkey_schema): +def set_arrays(attack_techniques, monkey_config, monkey_schema): """ - Sets exploiters according to ATT&CK matrix + Sets exploiters/scanners/PBAs and other array type fields according to ATT&CK matrix :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} :param monkey_config: Monkey island's configuration :param monkey_schema: Monkey configuration schema """ - for exploiter in monkey_schema['definitions']['exploiter_classes']['anyOf']: - # Go trough each attack technique used by exploiter - for attack_technique in exploiter['attack_techniques']: - # If exploiter's attack technique is disabled, disable the exploiter - if not attack_techniques[attack_technique]: - remove_exploiter(exploiter['enum'][0], monkey_config) - break - # If exploiter's attack technique is enabled, enable the exploiter - else: - add_exploiter(exploiter['enum'][0], monkey_config) + for key, definition in monkey_schema['definitions'].items(): + for array_field in definition['anyOf']: + # Go trough each attack technique used by exploiter/scanner/PBA + if 'attack_techniques' not in array_field: + continue + for attack_technique in array_field['attack_techniques']: + # Skip if exploiter is marked with not yet implemented technique + if attack_technique not in attack_techniques: + continue + # If exploiter's attack technique is disabled, disable the exploiter/scanner/PBA + if not attack_techniques[attack_technique]: + r_alter_array(monkey_config, key, array_field['enum'][0], remove=True) + break + # If exploiter's attack technique is enabled, enable the exploiter/scanner/PBA + else: + r_alter_array(monkey_config, key, array_field['enum'][0], remove=False) -def remove_exploiter(exploiter, monkey_config): +def set_booleans(attack_techniques, monkey_config, monkey_schema): """ - Removes exploiter from monkey's configuration - :param exploiter: Exploiter class name found in SCHEMA->definitions->exploiter_classes -> anyOf -> enum - :param monkey_config: Monkey's configuration + Sets exploiters/scanners/PBAs and other array type fields according to ATT&CK matrix + :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} + :param monkey_config: Monkey island's configuration + :param monkey_schema: Monkey configuration schema """ - if exploiter in monkey_config['exploits']['general']['exploiter_classes']: - monkey_config['exploits']['general']['exploiter_classes'].remove(exploiter) + for key, value in monkey_schema['properties'].items(): + r_set_booleans([key], value, attack_techniques, monkey_config) -def add_exploiter(exploiter, monkey_config): - """ - Adds exploiter to monkey's configuration - :param exploiter: Exploiter class name found in SCHEMA->definitions->exploiter_classes -> anyOf -> enum - :param monkey_config: Monkey's configuration - """ - if not exploiter in monkey_config['exploits']['general']['exploiter_classes']: - monkey_config['exploits']['general']['exploiter_classes'].append(exploiter) +def r_set_booleans(path, value, attack_techniques, monkey_config): + if isinstance(value, dict): + dictionary = {} + if 'type' in value and value['type'] == 'boolean' and 'attack_techniques' in value: + set_bool_conf_val(path, should_enable_field(value['attack_techniques'], attack_techniques), monkey_config) + elif 'properties' in value: + dictionary = value['properties'] + else: + dictionary = value + for key, item in dictionary.items(): + path.append(key) + r_set_booleans(path, item, attack_techniques, monkey_config) + del path[-1] + + +def set_bool_conf_val(path, val, monkey_config): + util.set(monkey_config, '/'.join(path), val) + + +def should_enable_field(field_techniques, users_techniques): + for technique in field_techniques: + if not users_techniques[technique]: + return False + return True + + +def r_alter_array(config_value, array_name, field, remove=True): + if isinstance(config_value, dict): + if array_name in config_value and isinstance(config_value[array_name], list): + if remove and field in config_value[array_name]: + config_value[array_name].remove(field) + elif not remove and field not in config_value[array_name]: + config_value[array_name].append(field) + else: + for prop in config_value.items(): + r_alter_array(prop[1], array_name, field, remove) def get_techniques(): + """ + Parses ATT&CK config into a dic of techniques. + :return: Dictionary of techniques. Format: {"T1110": True, "T1075": False, ...} + """ attack_config = get_config() techniques = {} for key, attack_type in attack_config['properties'].items(): diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 582eeb876..87f7f7274 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -14,7 +14,7 @@ SCHEMA = { "description": "Adversaries may steal the credentials of a specific user or service account using " "Credential Access techniques or capture credentials earlier in their " "reconnaissance process.", - "mapped_to": ["T1003"] + "depends_on": ["T1003"] } } }, @@ -61,7 +61,7 @@ SCHEMA = { "description": "Credential dumping is the process of obtaining account login and password " "information, normally in the form of a hash or a clear text password, " "from the operating system and software.", - "mapped_to": ["T1078"] + "depends_on": ["T1078"] } } }, diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index c5611e4e1..7d7d0d440 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -116,6 +116,7 @@ SCHEMA = { "BackdoorUser" ], "title": "Back door user", + "attack_techniques": ["T1110"] }, ], }, @@ -135,7 +136,8 @@ SCHEMA = { "enum": [ "SSHFinger" ], - "title": "SSHFinger" + "title": "SSHFinger", + "attack_techniques": ["T1110"] }, { "type": "string", @@ -393,6 +395,7 @@ SCHEMA = { "title": "Harvest Azure Credentials", "type": "boolean", "default": True, + "attack_techniques": ["T1110", "T1078"], "description": "Determine if the Monkey should try to harvest password credentials from Azure VMs" }, @@ -406,6 +409,7 @@ SCHEMA = { "title": "Should use Mimikatz", "type": "boolean", "default": True, + "attack_techniques": ["T1110", "T1078"], "description": "Determines whether to use Mimikatz" }, } diff --git a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js index 8267449d0..ae0d5b92c 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js @@ -138,9 +138,9 @@ class MatrixComponent extends AuthComponent { let tempMatrix = this.state.configData; tempMatrix[techType[0]].properties[technique].value = value; // Toggle all mapped techniques - if (! mapped && tempMatrix[techType[0]].properties[technique].hasOwnProperty('mapped_to')){ + if (! mapped && tempMatrix[techType[0]].properties[technique].hasOwnProperty('depends_on')){ console.log("Triggered"); - tempMatrix[techType[0]].properties[technique].mapped_to.forEach(mappedTechnique => { + tempMatrix[techType[0]].properties[technique].depends_on.forEach(mappedTechnique => { console.log(mappedTechnique) this.handleTechniqueChange(mappedTechnique, value, true) }) diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index b8df0f5a9..41441e5b9 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -15,4 +15,5 @@ ipaddress enum34 pycryptodome boto3 -awscli \ No newline at end of file +awscli +dpath From 300f821b4c255fd236ab16fc4d64be9819b43320 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 29 Mar 2019 17:37:16 +0200 Subject: [PATCH 12/35] Refactored matrix parsing, added comments --- .../cc/resources/attack_config.py | 2 + .../cc/services/attack/attack_config.py | 63 ++++++++++++++----- .../cc/services/config_schema.py | 4 +- 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/monkey/monkey_island/cc/resources/attack_config.py b/monkey/monkey_island/cc/resources/attack_config.py index 0e33fc6d1..8b8e9787c 100644 --- a/monkey/monkey_island/cc/resources/attack_config.py +++ b/monkey/monkey_island/cc/resources/attack_config.py @@ -5,6 +5,8 @@ from flask import jsonify, request from cc.auth import jwt_required from cc.services.attack.attack_config import * +__author__ = "VakarisZ" + class AttackConfiguration(flask_restful.Resource): @jwt_required() diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py index 713eeab36..9deb26277 100644 --- a/monkey/monkey_island/cc/services/attack/attack_config.py +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -42,32 +42,28 @@ def apply_to_monkey_config(): def set_arrays(attack_techniques, monkey_config, monkey_schema): """ - Sets exploiters/scanners/PBAs and other array type fields according to ATT&CK matrix + Sets exploiters/scanners/PBAs and other array type fields in monkey's config according to ATT&CK matrix :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} :param monkey_config: Monkey island's configuration :param monkey_schema: Monkey configuration schema """ for key, definition in monkey_schema['definitions'].items(): for array_field in definition['anyOf']: - # Go trough each attack technique used by exploiter/scanner/PBA + # Check if current array field has attack_techniques assigned to it if 'attack_techniques' not in array_field: continue - for attack_technique in array_field['attack_techniques']: - # Skip if exploiter is marked with not yet implemented technique - if attack_technique not in attack_techniques: - continue - # If exploiter's attack technique is disabled, disable the exploiter/scanner/PBA - if not attack_techniques[attack_technique]: - r_alter_array(monkey_config, key, array_field['enum'][0], remove=True) - break - # If exploiter's attack technique is enabled, enable the exploiter/scanner/PBA - else: - r_alter_array(monkey_config, key, array_field['enum'][0], remove=False) + try: + should_remove = not should_enable_field(array_field['attack_techniques'], attack_techniques) + except KeyError: + # Monkey schema field contains not yet implemented technique + continue + # If exploiter's attack technique is disabled, disable the exploiter/scanner/PBA + r_alter_array(monkey_config, key, array_field['enum'][0], remove=should_remove) def set_booleans(attack_techniques, monkey_config, monkey_schema): """ - Sets exploiters/scanners/PBAs and other array type fields according to ATT&CK matrix + Sets boolean type fields, like "should use mimikatz?" in monkey's config according to ATT&CK matrix :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} :param monkey_config: Monkey island's configuration :param monkey_schema: Monkey configuration schema @@ -77,10 +73,24 @@ def set_booleans(attack_techniques, monkey_config, monkey_schema): def r_set_booleans(path, value, attack_techniques, monkey_config): + """ + Recursively walks trough monkey configuration (DFS) to find which boolean fields needs to be set and sets them + according to ATT&CK matrix. + :param path: Property names that leads to current value. E.g. ['monkey', 'system_info', 'should_use_mimikatz'] + :param value: Value of config property + :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} + :param monkey_config: Monkey island's configuration + """ if isinstance(value, dict): dictionary = {} + # If 'value' is a boolean value that should be set: if 'type' in value and value['type'] == 'boolean' and 'attack_techniques' in value: - set_bool_conf_val(path, should_enable_field(value['attack_techniques'], attack_techniques), monkey_config) + try: + set_bool_conf_val(path, should_enable_field(value['attack_techniques'], attack_techniques), monkey_config) + except KeyError: + # Monkey schema has a technique that is not yet implemented + pass + # If 'value' is dict, we go over each of it's fields to search for booleans elif 'properties' in value: dictionary = value['properties'] else: @@ -88,14 +98,30 @@ def r_set_booleans(path, value, attack_techniques, monkey_config): for key, item in dictionary.items(): path.append(key) r_set_booleans(path, item, attack_techniques, monkey_config) + # Method enumerated everything in current path, goes back a level. del path[-1] def set_bool_conf_val(path, val, monkey_config): + """ + Changes monkey's configuration by setting one of its boolean fields value + :param path: Path to boolean value in monkey's configuration. E.g. ['monkey', 'system_info', 'should_use_mimikatz'] + :param val: Boolean + :param monkey_config: Monkey's configuration + """ util.set(monkey_config, '/'.join(path), val) def should_enable_field(field_techniques, users_techniques): + """ + Determines whether a single config field should be enabled or not. + :param field_techniques: ATT&CK techniques that field uses + :param users_techniques: ATT&CK techniques that user chose + :return: True, if user enabled all techniques used by the field, false otherwise + """ + # Method can't decide field value because it has no attack techniques assign to it. + if not field_techniques: + raise KeyError for technique in field_techniques: if not users_techniques[technique]: return False @@ -103,6 +129,13 @@ def should_enable_field(field_techniques, users_techniques): def r_alter_array(config_value, array_name, field, remove=True): + """ + Recursively searches config (DFS) for array and removes/adds a field. + :param config_value: Some object/value from config + :param array_name: Name of array this method should search + :param field: Field in array that this method should add/remove + :param remove: Removes field from array if true, adds it if false + """ if isinstance(config_value, dict): if array_name in config_value and isinstance(config_value[array_name], list): if remove and field in config_value[array_name]: diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 7d7d0d440..0f7e072f1 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -14,7 +14,7 @@ SCHEMA = { "SmbExploiter" ], "title": "SMB Exploiter", - "attack_techniques": ["T1110", "T1210", "T1021", "T1035", "T1075"] + "attack_techniques": ["T1110", "T1210", "T1021", "T1035", "T1075", "T16616161"] }, { "type": "string", @@ -409,7 +409,7 @@ SCHEMA = { "title": "Should use Mimikatz", "type": "boolean", "default": True, - "attack_techniques": ["T1110", "T1078"], + "attack_techniques": ["T1110", "T1078", "T123123123"], "description": "Determines whether to use Mimikatz" }, } From 689e034524e93736cbbf7329a5dff83539cdbc7e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 1 Apr 2019 09:56:29 +0300 Subject: [PATCH 13/35] Removed UI checkbox component --- .../src/components/ui-components/checkBox.js | 83 ------------------- 1 file changed, 83 deletions(-) delete mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js b/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js deleted file mode 100644 index 143df9c86..000000000 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/checkBox.js +++ /dev/null @@ -1,83 +0,0 @@ -import '../../styles/CheckBox.scss' -import React from 'react'; -import Tooltip from 'react-tooltip-lite'; - -class Checkbox extends React.PureComponent { - - constructor() { - super(props); - - 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'); - let tooltip = ""; - if (this.props.hasOwnProperty("tooltipContent") && this.props.hasOwnProperty("tooltipDirection")){ - tooltip = () - } - } - return ( -
- - - { - this.state.checked && - - - - - - } - { - !this.state.checked && - - - - - - } - -
-
- ) - } -} -export default Checkbox; From ac1d084a5c0300729ea73e8fc6dd911f93f683ff Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 1 Apr 2019 09:58:32 +0300 Subject: [PATCH 14/35] Removed UI checkbox component again --- .../src/components/ui-components/checkbox.js | 61 ------------------- 1 file changed, 61 deletions(-) delete mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js b/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js deleted file mode 100644 index 1d3754036..000000000 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js +++ /dev/null @@ -1,61 +0,0 @@ -import '../../styles/Checkbox.scss' -import React from 'react'; - -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 ( -
- - -
-
- ) - } -} - -export default Checkbox; From 00c19aa3b975f8c1a98a3ff9def5f8f4191df0c1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 1 Apr 2019 10:01:53 +0300 Subject: [PATCH 15/35] Added correct checkbox file --- .../src/components/ui-components/checkbox.js | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js b/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js new file mode 100644 index 000000000..4cfd022f0 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js @@ -0,0 +1,68 @@ +import '../../styles/Checkbox.scss' +import React from 'react'; + +class Checkbox extends React.PureComponent { + + componentDidUpdate(prevProps) { + if (this.props.checked !== prevProps.checked) { + this.setState({checked: this.props.checked}); + } + } + + constructor(props) { + super(props); + this.state = { + checked: this.props.checked, + necessary: this.props.necessary, + 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, + }, () => { this.props.changeHandler(this.props.name, this.state.checked)}); + } + + // + ping() { + this.setState({ isAnimating: false }) + } + + // + composeStateClasses(core) { + let result = core; + if (this.state.necessary){ + return result + ' blocked' + } + 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 ( +
+ + +
+
+ ) + } +} + +export default Checkbox; From ae6e83d4c6f8b8fc295d048b74524d9679fbe0f9 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 1 Apr 2019 14:44:18 +0300 Subject: [PATCH 16/35] Test schema's change to production, minor comment improvements --- .../cc/services/attack/attack_schema.py | 10 +++-- .../cc/services/config_schema.py | 38 ++++++++++--------- .../src/components/attack/MatrixComponent.js | 8 ++-- .../ui/src/components/pages/ConfigurePage.js | 6 +-- .../src/components/ui-components/checkbox.js | 8 ++-- 5 files changed, 38 insertions(+), 32 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 87f7f7274..3cab5b620 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -11,7 +11,9 @@ SCHEMA = { "type": "bool", "value": True, "necessary": False, - "description": "Adversaries may steal the credentials of a specific user or service account using " + "description": "Mapped with T1003 Credential dumping because both techniques " + "require same credential harvesting modules. " + "Adversaries may steal the credentials of a specific user or service account using " "Credential Access techniques or capture credentials earlier in their " "reconnaissance process.", "depends_on": ["T1003"] @@ -48,7 +50,7 @@ SCHEMA = { "T1110": { "title": "T1110 Brute force", "type": "bool", - "value": False, + "value": True, "necessary": False, "description": "Adversaries may use brute force techniques to attempt access to accounts " "when passwords are unknown or when password hashes are obtained." @@ -58,7 +60,9 @@ SCHEMA = { "type": "bool", "value": True, "necessary": False, - "description": "Credential dumping is the process of obtaining account login and password " + "description": "Mapped with T1078 Valid Accounts because both techniques require" + " same credential harvesting modules. " + "Credential dumping is the process of obtaining account login and password " "information, normally in the form of a hash or a clear text password, " "from the operating system and software.", "depends_on": ["T1078"] diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 0f7e072f1..8c7e6c154 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -14,7 +14,7 @@ SCHEMA = { "SmbExploiter" ], "title": "SMB Exploiter", - "attack_techniques": ["T1110", "T1210", "T1021", "T1035", "T1075", "T16616161"] + "attack_techniques": ["T1110", "T1210", "T1075"] }, { "type": "string", @@ -22,7 +22,7 @@ SCHEMA = { "WmiExploiter" ], "title": "WMI Exploiter", - "attack_techniques": ["T1110"] + "attack_techniques": ["T1110", "T1210"] }, { "type": "string", @@ -30,7 +30,7 @@ SCHEMA = { "MSSQLExploiter" ], "title": "MSSQL Exploiter", - "attack_techniques": ["T1110"] + "attack_techniques": ["T1110", "T1210"] }, { "type": "string", @@ -54,7 +54,7 @@ SCHEMA = { "SSHExploiter" ], "title": "SSH Exploiter", - "attack_techniques": ["T1110"] + "attack_techniques": ["T1110", "T1210"] }, { "type": "string", @@ -62,7 +62,7 @@ SCHEMA = { "ShellShockExploiter" ], "title": "ShellShock Exploiter", - "attack_techniques": [] + "attack_techniques": ["T1210"] }, { "type": "string", @@ -70,7 +70,7 @@ SCHEMA = { "SambaCryExploiter" ], "title": "SambaCry Exploiter", - "attack_techniques": [] + "attack_techniques": ["T1210"] }, { "type": "string", @@ -78,7 +78,7 @@ SCHEMA = { "ElasticGroovyExploiter" ], "title": "ElasticGroovy Exploiter", - "attack_techniques": [] + "attack_techniques": ["T1210"] }, { "type": "string", @@ -86,7 +86,7 @@ SCHEMA = { "Struts2Exploiter" ], "title": "Struts2 Exploiter", - "attack_techniques": [] + "attack_techniques": ["T1210"] }, { "type": "string", @@ -94,7 +94,7 @@ SCHEMA = { "WebLogicExploiter" ], "title": "Oracle Web Logic Exploiter", - "attack_techniques": [] + "attack_techniques": ["T1210"] }, { "type": "string", @@ -102,7 +102,7 @@ SCHEMA = { "HadoopExploiter" ], "title": "Hadoop/Yarn Exploiter", - "attack_techniques": [] + "attack_techniques": ["T1210"] } ] }, @@ -116,7 +116,7 @@ SCHEMA = { "BackdoorUser" ], "title": "Back door user", - "attack_techniques": ["T1110"] + "attack_techniques": [] }, ], }, @@ -129,7 +129,8 @@ SCHEMA = { "enum": [ "SMBFinger" ], - "title": "SMBFinger" + "title": "SMBFinger", + "attack_techniques": ["T1210"] }, { "type": "string", @@ -137,7 +138,7 @@ SCHEMA = { "SSHFinger" ], "title": "SSHFinger", - "attack_techniques": ["T1110"] + "attack_techniques": ["T1210"] }, { "type": "string", @@ -158,14 +159,16 @@ SCHEMA = { "enum": [ "MySQLFinger" ], - "title": "MySQLFinger" + "title": "MySQLFinger", + "attack_techniques": ["T1210"] }, { "type": "string", "enum": [ "MSSQLFinger" ], - "title": "MSSQLFinger" + "title": "MSSQLFinger", + "attack_techniques": ["T1210"] }, { @@ -173,7 +176,8 @@ SCHEMA = { "enum": [ "ElasticFinger" ], - "title": "ElasticFinger" + "title": "ElasticFinger", + "attack_techniques": ["T1210"] } ] } @@ -409,7 +413,7 @@ SCHEMA = { "title": "Should use Mimikatz", "type": "boolean", "default": True, - "attack_techniques": ["T1110", "T1078", "T123123123"], + "attack_techniques": ["T1110", "T1078"], "description": "Determines whether to use Mimikatz" }, } diff --git a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js index ae0d5b92c..1803a7cd9 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js @@ -18,7 +18,7 @@ let findMaxTechniques = function (data){ return maxLen }; -// Parses config schema into data suitable for react-table (ATT&CK matrix) +// Parses ATT&CK config schema into data suitable for react-table (ATT&CK matrix) let parseTechniques = function (data, maxLen) { let techniques = []; // Create rows with attack techniques @@ -46,7 +46,7 @@ let parseTechniques = function (data, maxLen) { class MatrixComponent extends AuthComponent { constructor(props) { super(props); - // Copy configuration and parse it for ATT&CK matrix table + // Copy ATT&CK configuration and parse it for ATT&CK matrix table let configCopy = JSON.parse(JSON.stringify(this.props.configuration)); this.state = {lastAction: 'none', configData: this.props.configuration, @@ -117,6 +117,7 @@ class MatrixComponent extends AuthComponent { }); }; + // Updates state based on values in config supplied. updateStateFromConfig = (config, lastAction = '') => { let configCopy = JSON.parse(JSON.stringify(config)); let maxTechniques = findMaxTechniques(Object.values(configCopy)); @@ -131,6 +132,7 @@ class MatrixComponent extends AuthComponent { }); }; + // Handles change in technique, when user toggles it handleTechniqueChange = (technique, value, mapped=false) => { // Change value on configuration Object.entries(this.state.configData).forEach(techType => { @@ -139,9 +141,7 @@ class MatrixComponent extends AuthComponent { tempMatrix[techType[0]].properties[technique].value = value; // Toggle all mapped techniques if (! mapped && tempMatrix[techType[0]].properties[technique].hasOwnProperty('depends_on')){ - console.log("Triggered"); tempMatrix[techType[0]].properties[technique].depends_on.forEach(mappedTechnique => { - console.log(mappedTechnique) this.handleTechniqueChange(mappedTechnique, value, true) }) } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index a3626ae1a..bb369fa73 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -277,7 +277,6 @@ class ConfigurePageComponent extends AuthComponent { displayedSchema = this.state.schema['properties'][this.state.selectedSection]; displayedSchema['definitions'] = this.state.schema['definitions']; } - return (

Monkey Configuration

@@ -299,9 +298,11 @@ class ConfigurePageComponent extends AuthComponent { } { this.state.selectedSection ?
+ onChange={this.onChange} + noValidate={true}>
{ this.state.allMonkeysAreDead ? '' : @@ -364,7 +365,6 @@ class ConfigurePageComponent extends AuthComponent {
: ''}
- ); } diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js b/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js index 4cfd022f0..0b3f357d9 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js @@ -20,8 +20,7 @@ class Checkbox extends React.PureComponent { this.ping = this.ping.bind(this); this.composeStateClasses = this.composeStateClasses.bind(this); } - - // + toggleChecked() { if (this.state.isAnimating) return false; this.setState({ @@ -30,12 +29,12 @@ class Checkbox extends React.PureComponent { }, () => { this.props.changeHandler(this.props.name, this.state.checked)}); } - // + // Stops animation ping() { this.setState({ isAnimating: false }) } - // + // Creates class string for component composeStateClasses(core) { let result = core; if (this.state.necessary){ @@ -48,7 +47,6 @@ class Checkbox extends React.PureComponent { return result; } - // render() { const cl = this.composeStateClasses('ui-checkbox-btn'); return ( From 2a8d5b2e48f162819a798e71c3459b7ef5605431 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 2 Apr 2019 18:23:12 +0300 Subject: [PATCH 17/35] Removed unrelated file change --- monkey/infection_monkey/example.conf | 4 ---- 1 file changed, 4 deletions(-) diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index cf79dbccc..ca8041382 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -98,8 +98,4 @@ "victims_max_exploit": 7, "victims_max_find": 30, "post_breach_actions" : [] - custom_PBA_linux_cmd = "" - custom_PBA_windows_cmd = "" - PBA_linux_filename = None - PBA_windows_filename = None } From 5944908f2826121e9e5d14716af4d5ee961cfa66 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 4 Apr 2019 09:22:51 +0300 Subject: [PATCH 18/35] Renamed get_techniques method to be more precise --- monkey/monkey_island/cc/services/attack/attack_config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py index 9deb26277..8ebfa30ec 100644 --- a/monkey/monkey_island/cc/services/attack/attack_config.py +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -32,7 +32,7 @@ def apply_to_monkey_config(): Applies ATT&CK matrix to the monkey configuration :return: """ - attack_techniques = get_techniques() + attack_techniques = get_technique_values() monkey_config = ConfigService.get_config(False, True, True) monkey_schema = ConfigService.get_config_schema() set_arrays(attack_techniques, monkey_config, monkey_schema) @@ -147,9 +147,9 @@ def r_alter_array(config_value, array_name, field, remove=True): r_alter_array(prop[1], array_name, field, remove) -def get_techniques(): +def get_technique_values(): """ - Parses ATT&CK config into a dic of techniques. + Parses ATT&CK config into a dic of techniques and corresponding values. :return: Dictionary of techniques. Format: {"T1110": True, "T1075": False, ...} """ attack_config = get_config() From 2106d6dd0b72a8a79f8288bdfe5b8509671d2124 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 11 Apr 2019 14:48:40 +0300 Subject: [PATCH 19/35] Minor PR comments addressed --- monkey/monkey_island/cc/services/attack/attack_config.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py index 8ebfa30ec..3e243d9bb 100644 --- a/monkey/monkey_island/cc/services/attack/attack_config.py +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -86,7 +86,9 @@ def r_set_booleans(path, value, attack_techniques, monkey_config): # If 'value' is a boolean value that should be set: if 'type' in value and value['type'] == 'boolean' and 'attack_techniques' in value: try: - set_bool_conf_val(path, should_enable_field(value['attack_techniques'], attack_techniques), monkey_config) + set_bool_conf_val(path, + should_enable_field(value['attack_techniques'], attack_techniques), + monkey_config) except KeyError: # Monkey schema has a technique that is not yet implemented pass @@ -154,7 +156,7 @@ def get_technique_values(): """ attack_config = get_config() techniques = {} - for key, attack_type in attack_config['properties'].items(): + for type_name, attack_type in attack_config['properties'].items(): for key, technique in attack_type['properties'].items(): techniques[key] = technique['value'] return techniques From ce78ebab917a88d837e2602c31be7b5e22a73e80 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 15 Apr 2019 13:04:57 +0300 Subject: [PATCH 20/35] Refactored imports according to latest changes --- monkey/monkey_island/cc/resources/attack_config.py | 4 ++-- monkey/monkey_island/cc/resources/root.py | 2 +- monkey/monkey_island/cc/services/attack/attack_config.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/resources/attack_config.py b/monkey/monkey_island/cc/resources/attack_config.py index 8b8e9787c..482c3b5fe 100644 --- a/monkey/monkey_island/cc/resources/attack_config.py +++ b/monkey/monkey_island/cc/resources/attack_config.py @@ -2,8 +2,8 @@ import flask_restful import json from flask import jsonify, request -from cc.auth import jwt_required -from cc.services.attack.attack_config import * +from monkey_island.cc.auth import jwt_required +from monkey_island.cc.services.attack.attack_config import * __author__ = "VakarisZ" diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 5f29186ca..cd2f7e6a5 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -7,7 +7,7 @@ from flask import request, make_response, jsonify from monkey_island.cc.auth import jwt_required from monkey_island.cc.database import mongo from monkey_island.cc.services.config import ConfigService -from cc.services.attack.attack_config import reset_config as reset_attack_config +from monkey_island.cc.services.attack.attack_config import reset_config as reset_attack_config from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.report import ReportService from monkey_island.cc.utils import local_ip_addresses diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py index 3e243d9bb..c153a40f7 100644 --- a/monkey/monkey_island/cc/services/attack/attack_config.py +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -1,8 +1,8 @@ import logging from dpath import util -from cc.database import mongo -from attack_schema import SCHEMA -from cc.services.config import ConfigService +from monkey_island.cc.database import mongo +from monkey_island.cc.services.attack.attack_schema import SCHEMA +from monkey_island.cc.services.config import ConfigService __author__ = "VakarisZ" From ef90d8a788d470eadb3f531c473cb771dee6448a Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 17 Apr 2019 13:52:04 +0300 Subject: [PATCH 21/35] Added legend and improved matrix UI --- .../cc/ui/src/components/pages/AttackPage.js | 21 ++++++++++++++++++- .../cc/ui/src/styles/Checkbox.scss | 21 ++++++++++++++----- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js index 397f558e9..d0db2d1c4 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js @@ -2,6 +2,8 @@ import React from 'react'; import AuthComponent from '../AuthComponent'; import 'filepond/dist/filepond.min.css'; import MatrixComponent from '../attack/MatrixComponent' +import {Col} from "react-bootstrap"; +import '../../styles/Checkbox.scss' class AttackComponent extends AuthComponent { constructor(props) { @@ -38,7 +40,24 @@ class AttackComponent extends AuthComponent { 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/Checkbox.scss b/monkey/monkey_island/cc/ui/src/styles/Checkbox.scss index b590b2a08..03cd38370 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: #7a7d7b; +$dark-green: #007d02; $green: #44CF6C; $black: #000000; @@ -34,13 +34,12 @@ $black: #000000; } &.blocked { - background-color: $dark-grey; - color: $black; - fill: $black; + background-color: $dark-green; + color: $light-grey; + fill: $light-grey; } &.is-checked { - border: 1px solid $green; background-color: $green; color: white; fill: white; @@ -92,3 +91,15 @@ $black: #000000; background-color: rgba(white, .08); } } + +.icon-checked{ + color:$green +} + +.icon-mandatory{ + color:$dark-green +} + +.icon-unchecked{ + color:$black; +} From b17dbdc9de10e0f89a1f3aef43969ce98702d29b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 17 Apr 2019 13:58:52 +0300 Subject: [PATCH 22/35] Legend style fix --- monkey/monkey_island/cc/ui/src/styles/App.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css index 8b1c45d7a..7303603b9 100644 --- a/monkey/monkey_island/cc/ui/src/styles/App.css +++ b/monkey/monkey_island/cc/ui/src/styles/App.css @@ -520,3 +520,8 @@ body { .attack-matrix .messages { margin-bottom: 30px; } + +.attack-legend { + text-align: center; + margin-bottom: 20px; +} From d36df7a0a36ef12329c084137a3a7f107b0dcb13 Mon Sep 17 00:00:00 2001 From: itaymmguardicore <30774653+itaymmguardicore@users.noreply.github.com> Date: Fri, 19 Apr 2019 16:52:47 +0300 Subject: [PATCH 23/35] Update monkey/monkey_island/cc/services/attack/attack_config.py dict typo Co-Authored-By: VakarisZ <36815064+VakarisZ@users.noreply.github.com> --- monkey/monkey_island/cc/services/attack/attack_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py index c153a40f7..d0a08b316 100644 --- a/monkey/monkey_island/cc/services/attack/attack_config.py +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -151,7 +151,7 @@ def r_alter_array(config_value, array_name, field, remove=True): def get_technique_values(): """ - Parses ATT&CK config into a dic of techniques and corresponding values. + Parses ATT&CK config into a dict of techniques and corresponding values. :return: Dictionary of techniques. Format: {"T1110": True, "T1075": False, ...} """ attack_config = get_config() From 7c3ba141004b23bf723266e06e1defd043df1563 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Sat, 20 Apr 2019 11:45:20 +0300 Subject: [PATCH 24/35] Small pr refactoring --- .../cc/resources/attack_config.py | 12 ++++++------ .../src/components/attack/MatrixComponent.js | 18 ++++++++---------- .../src/components/ui-components/checkbox.js | 6 +++--- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/monkey/monkey_island/cc/resources/attack_config.py b/monkey/monkey_island/cc/resources/attack_config.py index 482c3b5fe..ad94f209e 100644 --- a/monkey/monkey_island/cc/resources/attack_config.py +++ b/monkey/monkey_island/cc/resources/attack_config.py @@ -3,7 +3,7 @@ import json from flask import jsonify, request from monkey_island.cc.auth import jwt_required -from monkey_island.cc.services.attack.attack_config import * +import monkey_island.cc.services.attack.attack_config as attack_config __author__ = "VakarisZ" @@ -11,7 +11,7 @@ __author__ = "VakarisZ" class AttackConfiguration(flask_restful.Resource): @jwt_required() def get(self): - return jsonify(configuration=get_config()['properties']) + return jsonify(configuration=attack_config.get_config()['properties']) @jwt_required() def post(self): @@ -21,10 +21,10 @@ class AttackConfiguration(flask_restful.Resource): """ config_json = json.loads(request.data) if 'reset_attack_matrix' in config_json: - reset_config() - return jsonify(configuration=get_config()['properties']) + attack_config.reset_config() + return jsonify(configuration=attack_config.get_config()['properties']) else: - update_config({'properties': json.loads(request.data)}) - apply_to_monkey_config() + attack_config.update_config({'properties': json.loads(request.data)}) + attack_config.apply_to_monkey_config() return {} diff --git a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js index 1803a7cd9..fa25307db 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js @@ -46,13 +46,7 @@ let parseTechniques = function (data, maxLen) { class MatrixComponent extends AuthComponent { constructor(props) { super(props); - // Copy ATT&CK configuration and parse it for ATT&CK matrix table - let configCopy = JSON.parse(JSON.stringify(this.props.configuration)); - this.state = {lastAction: 'none', - configData: this.props.configuration, - maxTechniques: findMaxTechniques(Object.values(configCopy))}; - this.state.matrixTableData = parseTechniques(Object.values(configCopy), this.state.maxTechniques); - this.state.columns = this.getColumns(this.state.matrixTableData) + this.state = this.getStateFromConfig(this.props.configuration, 'none'); }; getColumns(matrixData) { @@ -68,7 +62,7 @@ class MatrixComponent extends AuthComponent { renderTechnique(technique) { if (technique == null){ - return (
) + return (
) } else { return ( { + this.setState(this.getStateFromConfig(config, lastAction)); + }; + + getStateFromConfig = (config, lastAction) => { let configCopy = JSON.parse(JSON.stringify(config)); let maxTechniques = findMaxTechniques(Object.values(configCopy)); let matrixTableData = parseTechniques(Object.values(configCopy), maxTechniques); let columns = this.getColumns(matrixTableData); - this.setState({ + return { lastAction: lastAction, configData: config, maxTechniques: maxTechniques, matrixTableData: matrixTableData, columns: columns - }); + }; }; // Handles change in technique, when user toggles it diff --git a/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js b/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js index 0b3f357d9..cde3435ed 100644 --- a/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js +++ b/monkey/monkey_island/cc/ui/src/components/ui-components/checkbox.js @@ -1,7 +1,7 @@ import '../../styles/Checkbox.scss' import React from 'react'; -class Checkbox extends React.PureComponent { +class CheckboxComponent extends React.PureComponent { componentDidUpdate(prevProps) { if (this.props.checked !== prevProps.checked) { @@ -20,7 +20,7 @@ class Checkbox extends React.PureComponent { this.ping = this.ping.bind(this); this.composeStateClasses = this.composeStateClasses.bind(this); } - + toggleChecked() { if (this.state.isAnimating) return false; this.setState({ @@ -63,4 +63,4 @@ class Checkbox extends React.PureComponent { } } -export default Checkbox; +export default CheckboxComponent; From cdd327073075a77ca9f7b6f51de112e160f2bb32 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 2 May 2019 17:11:30 +0300 Subject: [PATCH 25/35] Attack configuration page moved into island configuration --- monkey/monkey_island/cc/app.py | 4 +- .../cc/resources/attack_config.py | 12 +- .../cc/resources/attack_telem.py | 4 +- monkey/monkey_island/cc/resources/root.py | 17 +- .../cc/services/attack/attack_config.py | 284 +++++++++--------- .../cc/services/attack/attack_telem.py | 21 +- monkey/monkey_island/cc/services/database.py | 25 ++ .../cc/ui/src/components/Main.js | 3 - .../src/components/attack/MatrixComponent.js | 199 ++++-------- .../cc/ui/src/components/pages/AttackPage.js | 66 ---- .../ui/src/components/pages/ConfigurePage.js | 242 +++++++++++---- .../{checkbox.js => Checkbox.js} | 19 +- monkey/monkey_island/cc/ui/src/styles/App.css | 4 + 13 files changed, 469 insertions(+), 431 deletions(-) create mode 100644 monkey/monkey_island/cc/services/database.py delete mode 100644 monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js rename monkey/monkey_island/cc/ui/src/components/ui-components/{checkbox.js => Checkbox.js} (67%) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index b7461bcca..243f1f71a 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -28,7 +28,7 @@ from monkey_island.cc.resources.root import Root from monkey_island.cc.resources.telemetry import Telemetry from monkey_island.cc.resources.telemetry_feed import TelemetryFeed from monkey_island.cc.resources.pba_file_download import PBAFileDownload -from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.database import Database from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.resources.pba_file_upload import FileUpload from monkey_island.cc.resources.attack_telem import AttackTelem @@ -100,7 +100,7 @@ def init_app(mongo_url): with app.app_context(): database.init() - ConfigService.init_config() + Database.reset_db() app.add_url_rule('/', 'serve_home', serve_home) app.add_url_rule('/', 'serve_static_file', serve_static_file) diff --git a/monkey/monkey_island/cc/resources/attack_config.py b/monkey/monkey_island/cc/resources/attack_config.py index ad94f209e..da7651f24 100644 --- a/monkey/monkey_island/cc/resources/attack_config.py +++ b/monkey/monkey_island/cc/resources/attack_config.py @@ -3,7 +3,7 @@ import json from flask import jsonify, request from monkey_island.cc.auth import jwt_required -import monkey_island.cc.services.attack.attack_config as attack_config +from monkey_island.cc.services.attack.attack_config import AttackConfig __author__ = "VakarisZ" @@ -11,7 +11,7 @@ __author__ = "VakarisZ" class AttackConfiguration(flask_restful.Resource): @jwt_required() def get(self): - return jsonify(configuration=attack_config.get_config()['properties']) + return jsonify(configuration=AttackConfig.get_config()['properties']) @jwt_required() def post(self): @@ -21,10 +21,10 @@ class AttackConfiguration(flask_restful.Resource): """ config_json = json.loads(request.data) if 'reset_attack_matrix' in config_json: - attack_config.reset_config() - return jsonify(configuration=attack_config.get_config()['properties']) + AttackConfig.reset_config() + return jsonify(configuration=AttackConfig.get_config()['properties']) else: - attack_config.update_config({'properties': json.loads(request.data)}) - attack_config.apply_to_monkey_config() + AttackConfig.update_config({'properties': json.loads(request.data)}) + AttackConfig.apply_to_monkey_config() return {} diff --git a/monkey/monkey_island/cc/resources/attack_telem.py b/monkey/monkey_island/cc/resources/attack_telem.py index bef0a8585..8c30bb13c 100644 --- a/monkey/monkey_island/cc/resources/attack_telem.py +++ b/monkey/monkey_island/cc/resources/attack_telem.py @@ -1,7 +1,7 @@ import flask_restful from flask import request import json -from monkey_island.cc.services.attack.attack_telem import set_results +from monkey_island.cc.services.attack.attack_telem import AttackTelemService import logging __author__ = 'VakarisZ' @@ -20,5 +20,5 @@ class AttackTelem(flask_restful.Resource): :param technique: Technique ID, e.g. T1111 """ data = json.loads(request.data) - set_results(technique, data) + AttackTelemService.set_results(technique, data) return {} diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index cd2f7e6a5..b180afd1b 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -6,12 +6,10 @@ from flask import request, make_response, jsonify from monkey_island.cc.auth import jwt_required from monkey_island.cc.database import mongo -from monkey_island.cc.services.config import ConfigService -from monkey_island.cc.services.attack.attack_config import reset_config as reset_attack_config from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.report import ReportService from monkey_island.cc.utils import local_ip_addresses -from monkey_island.cc.services.post_breach_files import remove_PBA_files +from monkey_island.cc.services.database import Database __author__ = 'Barak' @@ -27,7 +25,7 @@ class Root(flask_restful.Resource): if not action: return Root.get_server_info() elif action == "reset": - return Root.reset_db() + return jwt_required()(Database.reset_db()) elif action == "killall": return Root.kill_all() elif action == "is-up": @@ -41,17 +39,6 @@ class Root(flask_restful.Resource): return jsonify(ip_addresses=local_ip_addresses(), mongo=str(mongo.db), completed_steps=Root.get_completed_steps()) - @staticmethod - @jwt_required() - def reset_db(): - remove_PBA_files() - # 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() - reset_attack_config() - logger.info('DB was reset') - return jsonify(status='OK') - @staticmethod @jwt_required() def kill_all(): diff --git a/monkey/monkey_island/cc/services/attack/attack_config.py b/monkey/monkey_island/cc/services/attack/attack_config.py index d0a08b316..f4f112e42 100644 --- a/monkey/monkey_island/cc/services/attack/attack_config.py +++ b/monkey/monkey_island/cc/services/attack/attack_config.py @@ -9,154 +9,160 @@ __author__ = "VakarisZ" logger = logging.getLogger(__name__) -def get_config(): - config = mongo.db.attack.find_one({'name': 'newconfig'}) or reset_config() - return config +class AttackConfig(object): + def __init__(self): + pass + @staticmethod + def get_config(): + config = mongo.db.attack.find_one({'name': 'newconfig'}) + return config -def get_config_schema(): - return SCHEMA + @staticmethod + def get_config_schema(): + return SCHEMA + @staticmethod + def reset_config(): + AttackConfig.update_config(SCHEMA) -def reset_config(): - update_config(SCHEMA) + @staticmethod + def update_config(config_json): + mongo.db.attack.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) + return True + @staticmethod + def apply_to_monkey_config(): + """ + Applies ATT&CK matrix to the monkey configuration + :return: + """ + attack_techniques = AttackConfig.get_technique_values() + monkey_config = ConfigService.get_config(False, True, True) + monkey_schema = ConfigService.get_config_schema() + AttackConfig.set_arrays(attack_techniques, monkey_config, monkey_schema) + AttackConfig.set_booleans(attack_techniques, monkey_config, monkey_schema) + ConfigService.update_config(monkey_config, True) -def update_config(config_json): - mongo.db.attack.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True) - return True + @staticmethod + def set_arrays(attack_techniques, monkey_config, monkey_schema): + """ + Sets exploiters/scanners/PBAs and other array type fields in monkey's config according to ATT&CK matrix + :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} + :param monkey_config: Monkey island's configuration + :param monkey_schema: Monkey configuration schema + """ + for key, definition in monkey_schema['definitions'].items(): + for array_field in definition['anyOf']: + # Check if current array field has attack_techniques assigned to it + if 'attack_techniques' not in array_field: + continue + try: + should_remove = not AttackConfig.should_enable_field(array_field['attack_techniques'], attack_techniques) + except KeyError: + # Monkey schema field contains not yet implemented technique + continue + # If exploiter's attack technique is disabled, disable the exploiter/scanner/PBA + AttackConfig.r_alter_array(monkey_config, key, array_field['enum'][0], remove=should_remove) + @staticmethod + def set_booleans(attack_techniques, monkey_config, monkey_schema): + """ + Sets boolean type fields, like "should use mimikatz?" in monkey's config according to ATT&CK matrix + :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} + :param monkey_config: Monkey island's configuration + :param monkey_schema: Monkey configuration schema + """ + for key, value in monkey_schema['properties'].items(): + AttackConfig.r_set_booleans([key], value, attack_techniques, monkey_config) -def apply_to_monkey_config(): - """ - Applies ATT&CK matrix to the monkey configuration - :return: - """ - attack_techniques = get_technique_values() - monkey_config = ConfigService.get_config(False, True, True) - monkey_schema = ConfigService.get_config_schema() - set_arrays(attack_techniques, monkey_config, monkey_schema) - set_booleans(attack_techniques, monkey_config, monkey_schema) - ConfigService.update_config(monkey_config, True) + @staticmethod + def r_set_booleans(path, value, attack_techniques, monkey_config): + """ + Recursively walks trough monkey configuration (DFS) to find which boolean fields needs to be set and sets them + according to ATT&CK matrix. + :param path: Property names that leads to current value. E.g. ['monkey', 'system_info', 'should_use_mimikatz'] + :param value: Value of config property + :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} + :param monkey_config: Monkey island's configuration + """ + if isinstance(value, dict): + dictionary = {} + # If 'value' is a boolean value that should be set: + if 'type' in value and value['type'] == 'boolean' and 'attack_techniques' in value: + try: + AttackConfig.set_bool_conf_val(path, + AttackConfig.should_enable_field(value['attack_techniques'], + attack_techniques), + monkey_config) + except KeyError: + # Monkey schema has a technique that is not yet implemented + pass + # If 'value' is dict, we go over each of it's fields to search for booleans + elif 'properties' in value: + dictionary = value['properties'] + else: + dictionary = value + for key, item in dictionary.items(): + path.append(key) + AttackConfig.r_set_booleans(path, item, attack_techniques, monkey_config) + # Method enumerated everything in current path, goes back a level. + del path[-1] + @staticmethod + def set_bool_conf_val(path, val, monkey_config): + """ + Changes monkey's configuration by setting one of its boolean fields value + :param path: Path to boolean value in monkey's configuration. E.g. ['monkey', 'system_info', 'should_use_mimikatz'] + :param val: Boolean + :param monkey_config: Monkey's configuration + """ + util.set(monkey_config, '/'.join(path), val) -def set_arrays(attack_techniques, monkey_config, monkey_schema): - """ - Sets exploiters/scanners/PBAs and other array type fields in monkey's config according to ATT&CK matrix - :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} - :param monkey_config: Monkey island's configuration - :param monkey_schema: Monkey configuration schema - """ - for key, definition in monkey_schema['definitions'].items(): - for array_field in definition['anyOf']: - # Check if current array field has attack_techniques assigned to it - if 'attack_techniques' not in array_field: - continue - try: - should_remove = not should_enable_field(array_field['attack_techniques'], attack_techniques) - except KeyError: - # Monkey schema field contains not yet implemented technique - continue - # If exploiter's attack technique is disabled, disable the exploiter/scanner/PBA - r_alter_array(monkey_config, key, array_field['enum'][0], remove=should_remove) + @staticmethod + def should_enable_field(field_techniques, users_techniques): + """ + Determines whether a single config field should be enabled or not. + :param field_techniques: ATT&CK techniques that field uses + :param users_techniques: ATT&CK techniques that user chose + :return: True, if user enabled all techniques used by the field, false otherwise + """ + # Method can't decide field value because it has no attack techniques assign to it. + if not field_techniques: + raise KeyError + for technique in field_techniques: + if not users_techniques[technique]: + return False + return True + @staticmethod + def r_alter_array(config_value, array_name, field, remove=True): + """ + Recursively searches config (DFS) for array and removes/adds a field. + :param config_value: Some object/value from config + :param array_name: Name of array this method should search + :param field: Field in array that this method should add/remove + :param remove: Removes field from array if true, adds it if false + """ + if isinstance(config_value, dict): + if array_name in config_value and isinstance(config_value[array_name], list): + if remove and field in config_value[array_name]: + config_value[array_name].remove(field) + elif not remove and field not in config_value[array_name]: + config_value[array_name].append(field) + else: + for prop in config_value.items(): + AttackConfig.r_alter_array(prop[1], array_name, field, remove) -def set_booleans(attack_techniques, monkey_config, monkey_schema): - """ - Sets boolean type fields, like "should use mimikatz?" in monkey's config according to ATT&CK matrix - :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} - :param monkey_config: Monkey island's configuration - :param monkey_schema: Monkey configuration schema - """ - for key, value in monkey_schema['properties'].items(): - r_set_booleans([key], value, attack_techniques, monkey_config) - - -def r_set_booleans(path, value, attack_techniques, monkey_config): - """ - Recursively walks trough monkey configuration (DFS) to find which boolean fields needs to be set and sets them - according to ATT&CK matrix. - :param path: Property names that leads to current value. E.g. ['monkey', 'system_info', 'should_use_mimikatz'] - :param value: Value of config property - :param attack_techniques: ATT&CK techniques dict. Format: {'T1110': True, ...} - :param monkey_config: Monkey island's configuration - """ - if isinstance(value, dict): - dictionary = {} - # If 'value' is a boolean value that should be set: - if 'type' in value and value['type'] == 'boolean' and 'attack_techniques' in value: - try: - set_bool_conf_val(path, - should_enable_field(value['attack_techniques'], attack_techniques), - monkey_config) - except KeyError: - # Monkey schema has a technique that is not yet implemented - pass - # If 'value' is dict, we go over each of it's fields to search for booleans - elif 'properties' in value: - dictionary = value['properties'] - else: - dictionary = value - for key, item in dictionary.items(): - path.append(key) - r_set_booleans(path, item, attack_techniques, monkey_config) - # Method enumerated everything in current path, goes back a level. - del path[-1] - - -def set_bool_conf_val(path, val, monkey_config): - """ - Changes monkey's configuration by setting one of its boolean fields value - :param path: Path to boolean value in monkey's configuration. E.g. ['monkey', 'system_info', 'should_use_mimikatz'] - :param val: Boolean - :param monkey_config: Monkey's configuration - """ - util.set(monkey_config, '/'.join(path), val) - - -def should_enable_field(field_techniques, users_techniques): - """ - Determines whether a single config field should be enabled or not. - :param field_techniques: ATT&CK techniques that field uses - :param users_techniques: ATT&CK techniques that user chose - :return: True, if user enabled all techniques used by the field, false otherwise - """ - # Method can't decide field value because it has no attack techniques assign to it. - if not field_techniques: - raise KeyError - for technique in field_techniques: - if not users_techniques[technique]: - return False - return True - - -def r_alter_array(config_value, array_name, field, remove=True): - """ - Recursively searches config (DFS) for array and removes/adds a field. - :param config_value: Some object/value from config - :param array_name: Name of array this method should search - :param field: Field in array that this method should add/remove - :param remove: Removes field from array if true, adds it if false - """ - if isinstance(config_value, dict): - if array_name in config_value and isinstance(config_value[array_name], list): - if remove and field in config_value[array_name]: - config_value[array_name].remove(field) - elif not remove and field not in config_value[array_name]: - config_value[array_name].append(field) - else: - for prop in config_value.items(): - r_alter_array(prop[1], array_name, field, remove) - - -def get_technique_values(): - """ - Parses ATT&CK config into a dict of techniques and corresponding values. - :return: Dictionary of techniques. Format: {"T1110": True, "T1075": False, ...} - """ - attack_config = get_config() - techniques = {} - for type_name, attack_type in attack_config['properties'].items(): - for key, technique in attack_type['properties'].items(): - techniques[key] = technique['value'] - return techniques + @staticmethod + def get_technique_values(): + """ + Parses ATT&CK config into a dict of techniques and corresponding values. + :return: Dictionary of techniques. Format: {"T1110": True, "T1075": False, ...} + """ + attack_config = AttackConfig.get_config() + techniques = {} + for type_name, attack_type in attack_config['properties'].items(): + for key, technique in attack_type['properties'].items(): + techniques[key] = technique['value'] + return techniques diff --git a/monkey/monkey_island/cc/services/attack/attack_telem.py b/monkey/monkey_island/cc/services/attack/attack_telem.py index a4e219270..d1255e4e9 100644 --- a/monkey/monkey_island/cc/services/attack/attack_telem.py +++ b/monkey/monkey_island/cc/services/attack/attack_telem.py @@ -9,11 +9,16 @@ __author__ = "VakarisZ" logger = logging.getLogger(__name__) -def set_results(technique, data): - """ - Adds ATT&CK technique results(telemetry) to the database - :param technique: technique ID string e.g. T1110 - :param data: Data, relevant to the technique - """ - data.update({'technique': technique}) - mongo.db.attack_results.insert(data) +class AttackTelemService(object): + def __init__(self): + pass + + @staticmethod + def set_results(technique, data): + """ + Adds ATT&CK technique results(telemetry) to the database + :param technique: technique ID string e.g. T1110 + :param data: Data, relevant to the technique + """ + data.update({'technique': technique}) + mongo.db.attack_results.insert(data) diff --git a/monkey/monkey_island/cc/services/database.py b/monkey/monkey_island/cc/services/database.py new file mode 100644 index 000000000..ead2b2058 --- /dev/null +++ b/monkey/monkey_island/cc/services/database.py @@ -0,0 +1,25 @@ +import logging + +from monkey_island.cc.services.config import ConfigService +from monkey_island.cc.services.attack.attack_config import AttackConfig +from monkey_island.cc.services.post_breach_files import remove_PBA_files +from flask import jsonify +from monkey_island.cc.database import mongo + + +logger = logging.getLogger(__name__) + + +class Database(object): + def __init__(self): + pass + + @staticmethod + def reset_db(): + remove_PBA_files() + # 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() + AttackConfig.reset_config() + logger.info('DB was reset') + return jsonify(status='OK') diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 291cfde58..da8e59113 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -14,7 +14,6 @@ import ReportPage from 'components/pages/ReportPage'; import LicensePage from 'components/pages/LicensePage'; import AuthComponent from 'components/AuthComponent'; import LoginPageComponent from 'components/pages/LoginPage'; -import AttckPage from 'components/pages/AttackPage' import 'normalize.css/normalize.css'; import 'react-data-components/css/table-twbs.css'; @@ -162,7 +161,6 @@ class AppComponent extends AuthComponent {
    -
  • ATT&CK Configuration
  • Configuration
  • Log
@@ -188,7 +186,6 @@ class AppComponent extends AuthComponent { {this.renderRoute('/start-over', )} {this.renderRoute('/report', )} {this.renderRoute('/license', )} - {this.renderRoute('/attack', )} diff --git a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js index fa25307db..1df65a50e 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js @@ -1,52 +1,52 @@ import React from 'react'; -import Checkbox from '../ui-components/checkbox' +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 ATT&CK 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; -}; +import {Col} from "react-bootstrap"; class MatrixComponent extends AuthComponent { constructor(props) { super(props); - this.state = this.getStateFromConfig(this.props.configuration, 'none'); + this.state = {lastAction: 'none'} + }; + + // Finds which attack type has most techniques and returns that number + static findMaxTechniques(data){ + let maxLen = 0; + data.forEach(function(techType) { + if (Object.keys(techType.properties).length > maxLen){ + maxLen = Object.keys(techType.properties).length + } + }); + return maxLen + }; + + // Parses ATT&CK config schema into data suitable for react-table (ATT&CK matrix) + static parseTechniques (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; }; getColumns(matrixData) { @@ -68,36 +68,13 @@ class MatrixComponent extends AuthComponent { + changeHandler={this.props.change}> {technique.title}
) } }; - onSubmit = () => { - this.authFetch('/api/attack', - { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify(this.state.configData) - }) - .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'}); - }); - }; - resetConfig = () => { this.authFetch('/api/attack', { @@ -111,82 +88,38 @@ class MatrixComponent extends AuthComponent { }); }; - // Updates state based on values in config supplied. - updateStateFromConfig = (config, lastAction = '') => { - this.setState(this.getStateFromConfig(config, lastAction)); - }; - - getStateFromConfig = (config, lastAction) => { + getTableData = (config) => { let configCopy = JSON.parse(JSON.stringify(config)); - let maxTechniques = findMaxTechniques(Object.values(configCopy)); - let matrixTableData = parseTechniques(Object.values(configCopy), maxTechniques); + let maxTechniques = MatrixComponent.findMaxTechniques(Object.values(configCopy)); + let matrixTableData = MatrixComponent.parseTechniques(Object.values(configCopy), maxTechniques); let columns = this.getColumns(matrixTableData); - return { - lastAction: lastAction, - configData: config, - maxTechniques: maxTechniques, - matrixTableData: matrixTableData, - columns: columns - }; - }; - - // Handles change in technique, when user toggles it - handleTechniqueChange = (technique, value, mapped=false) => { - // Change value on configuration - Object.entries(this.state.configData).forEach(techType => { - if(techType[1].properties.hasOwnProperty(technique)){ - let tempMatrix = this.state.configData; - tempMatrix[techType[0]].properties[technique].value = value; - // Toggle all mapped techniques - if (! mapped && tempMatrix[techType[0]].properties[technique].hasOwnProperty('depends_on')){ - tempMatrix[techType[0]].properties[technique].depends_on.forEach(mappedTechnique => { - this.handleTechniqueChange(mappedTechnique, value, true) - }) - } - this.updateStateFromConfig(tempMatrix); - } - }); - + return {'columns': columns, 'matrixTableData': matrixTableData, 'maxTechniques': maxTechniques} }; render() { + let tableData = this.getTableData(this.props.configuration); 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. -
- : ''} -
-
- - -
- +
+ +
+ +
); } } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js b/monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js deleted file mode 100644 index d0db2d1c4..000000000 --- a/monkey/monkey_island/cc/ui/src/components/pages/AttackPage.js +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; -import AuthComponent from '../AuthComponent'; -import 'filepond/dist/filepond.min.css'; -import MatrixComponent from '../attack/MatrixComponent' -import {Col} from "react-bootstrap"; -import '../../styles/Checkbox.scss' - -class AttackComponent extends AuthComponent { - constructor(props) { - super(props); - this.currentSection = 'ATT&CK matrix'; - this.currentFormData = {}; - this.sectionsOrder = ['ATT&CK matrix']; - // set schema from server - this.state = { - configuration: {}, - sections: [], - selectedSection: 'ATT&CK matrix', - }; - } - - componentDidMount() { - this.authFetch('/api/attack') - .then(res => res.json()) - .then(res => { - let sections = []; - for (let sectionKey of this.sectionsOrder) { - sections.push({key: sectionKey, title: res.configuration.title}); - } - this.setState({ - configuration: res.configuration, - sections: sections, - selectedSection: 'ATT&CK matrix' - }); - }); - } - - render() { - let content; - if (Object.keys(this.state.configuration).length === 0) { - content = (

Fetching configuration...

); - } else { - content = ( -
- - -
); - } - return
{content}
; - } -} - -export default AttackComponent; diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index bb369fa73..7aa143648 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -1,19 +1,26 @@ import React from 'react'; import Form from 'react-jsonschema-form'; -import {Col, Nav, NavItem} from 'react-bootstrap'; +import {Col, Modal, Nav, NavItem} from 'react-bootstrap'; import fileDownload from 'js-file-download'; import AuthComponent from '../AuthComponent'; import { FilePond } from 'react-filepond'; import 'filepond/dist/filepond.min.css'; +import MatrixComponent from "../attack/MatrixComponent"; + +const ATTACK_URL = '/api/attack'; +const CONFIG_URL = '/api/configuration/island'; class ConfigurePageComponent extends AuthComponent { + constructor(props) { super(props); this.PBAwindowsPond = null; this.PBAlinuxPond = null; - this.currentSection = 'basic'; + this.currentSection = 'attack'; this.currentFormData = {}; - this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; + this.initialConfig = {}; + this.initialAttackConfig = {}; + this.sectionsOrder = ['attack', 'basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; this.uiSchema = { behaviour: { custom_PBA_linux_cmd: { @@ -44,37 +51,94 @@ class ConfigurePageComponent extends AuthComponent { this.state = { schema: {}, configuration: {}, + attackConfig: {}, lastAction: 'none', sections: [], - selectedSection: 'basic', + selectedSection: 'attack', allMonkeysAreDead: true, PBAwinFile: [], - PBAlinuxFile: [] + PBAlinuxFile: [], + showAttackAlert: false }; } - componentDidMount() { - this.authFetch('/api/configuration/island') - .then(res => res.json()) - .then(res => { + setInitialConfig(config) { + this.initialConfig = JSON.parse(JSON.stringify(config)); + } + + setInitialAttackConfig(attackConfig) { + this.initialAttackConfig = JSON.parse(JSON.stringify(attackConfig)); + } + + componentDidMount = () => { + let urls = [CONFIG_URL, ATTACK_URL]; + Promise.all(urls.map(url => this.authFetch(url).then(res => res.json()))) + .then(data => { let sections = []; + let attackConfig = data[1]; + let monkeyConfig = data[0]; + this.setInitialConfig(monkeyConfig.configuration); + this.setInitialAttackConfig(attackConfig.configuration); for (let sectionKey of this.sectionsOrder) { - sections.push({key: sectionKey, title: res.schema.properties[sectionKey].title}); + if (sectionKey === 'attack') {sections.push({key:sectionKey, title: "ATT&CK"})} + else {sections.push({key: sectionKey, title: monkeyConfig.schema.properties[sectionKey].title});} } this.setState({ - schema: res.schema, - configuration: res.configuration, + schema: monkeyConfig.schema, + configuration: monkeyConfig.configuration, + attackConfig: attackConfig.configuration, sections: sections, - selectedSection: 'basic' + selectedSection: 'attack' }) }); this.updateMonkeysRunning(); - } + }; - onSubmit = ({formData}) => { - this.currentFormData = formData; + updateConfig = () => { + this.authFetch(CONFIG_URL) + .then(res => res.json()) + .then(data => { + this.setInitialConfig(data.configuration); + this.setState({configuration: data.configuration}) + }) + }; + + onSubmit = () => { + if (this.state.selectedSection === 'attack'){ + this.matrixSubmit() + } else { + this.configSubmit() + } + }; + + matrixSubmit = () => { + // Submit attack matrix + this.authFetch(ATTACK_URL, + { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify(this.state.attackConfig) + }) + .then(res => { + if (!res.ok) + { + throw Error() + } + return res; + }) + .then(() => {this.setInitialAttackConfig(this.state.attackConfig); + this.setState({lastAction: 'saved'})}) + .then(this.updateConfig()) + .catch(error => { + console.log('bad attack configuration'); + this.setState({lastAction: 'invalid_configuration'}); + }); + }; + + configSubmit = () => { + // Submit monkey configuration this.updateConfigSection(); - this.authFetch('/api/configuration/island', + this.authFetch(CONFIG_URL, { method: 'POST', headers: {'Content-Type': 'application/json'}, @@ -94,11 +158,33 @@ class ConfigurePageComponent extends AuthComponent { schema: res.schema, configuration: res.configuration }); + this.setInitialConfig(res.configuration); this.props.onStatusChange(); }).catch(error => { console.log('bad configuration'); this.setState({lastAction: 'invalid_configuration'}); }); + + }; + + // Alters attack configuration when user toggles technique + attackTechniqueChange = (technique, value, mapped=false) => { + // Change value in attack configuration + // Go trough each column in matrix, searching for technique + Object.entries(this.state.attackConfig).forEach(techType => { + if(techType[1].properties.hasOwnProperty(technique)){ + let tempMatrix = this.state.attackConfig; + tempMatrix[techType[0]].properties[technique].value = value; + this.setState({attackConfig: tempMatrix}); + // Toggle all mapped techniques + if (! mapped && tempMatrix[techType[0]].properties[technique].hasOwnProperty('depends_on')){ + tempMatrix[techType[0]].properties[technique].depends_on.forEach(mappedTechnique => { + this.attackTechniqueChange(mappedTechnique, value, true) + }) + } + + } + }); }; onChange = ({formData}) => { @@ -111,11 +197,49 @@ class ConfigurePageComponent extends AuthComponent { newConfig[this.currentSection] = this.currentFormData; this.currentFormData = {}; } - this.setState({configuration: newConfig}); + this.setState({configuration: newConfig, lastAction: 'none'}); }; + renderAttackAlertModal = () => { + return ( {this.setState({showAttackAlert: false})}}> + +

Warning

+

+ You have unsubmitted changes. Submit them before proceeding. +

+
+ +
+
+
) + }; + + userChangedConfig(){ + if(JSON.stringify(this.state.configuration) === JSON.stringify(this.initialConfig)){ + if(Object.keys(this.currentFormData).length === 0 || + JSON.stringify(this.initialConfig[this.currentSection]) === JSON.stringify(this.currentFormData)){ + return false; + } + } + return true; + } + + userChangedMatrix(){ + return (JSON.stringify(this.state.attackConfig) !== JSON.stringify(this.initialAttackConfig)) + } + setSelectedSection = (key) => { this.updateConfigSection(); + if ((key === 'attack' && this.userChangedConfig()) || + (this.currentSection === 'attack' && this.userChangedMatrix())){ + this.setState({showAttackAlert: true}); + return + } this.currentSection = key; this.setState({ selectedSection: key @@ -124,7 +248,7 @@ class ConfigurePageComponent extends AuthComponent { resetConfig = () => { this.removePBAfiles(); - this.authFetch('/api/configuration/island', + this.authFetch(CONFIG_URL, { method: 'POST', headers: {'Content-Type': 'application/json'}, @@ -137,8 +261,15 @@ class ConfigurePageComponent extends AuthComponent { schema: res.schema, configuration: res.configuration }); + this.setInitialConfig(res.configuration); this.props.onStatusChange(); - }); + }).then(this.authFetch(ATTACK_URL,{ method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify('reset_attack_matrix')})) + .then(res => res.json()) + .then(res => { + this.setState({attackConfig: res.configuration}) + }) }; removePBAfiles(){ @@ -273,19 +404,46 @@ class ConfigurePageComponent extends AuthComponent { render() { let displayedSchema = {}; - if (this.state.schema.hasOwnProperty('properties')) { + if (this.state.schema.hasOwnProperty('properties') && this.state.selectedSection !== 'attack') { displayedSchema = this.state.schema['properties'][this.state.selectedSection]; displayedSchema['definitions'] = this.state.schema['definitions']; } + let config_content = (
+
+ { this.state.allMonkeysAreDead ? + '' : +
+ + Some monkeys are currently running. Note that changing the configuration will only apply to new + infections. +
+ } +
+ ); + let attack_content = (); + let content = ''; + if (this.state.selectedSection === 'attack' && Object.entries(this.state.attackConfig).length !== 0 ) { + content = attack_content + } else if(this.state.selectedSection !== 'attack') { + content = config_content + } + + return ( + {this.renderAttackAlertModal()}

Monkey Configuration

{ this.state.selectedSection === 'basic_network' ? @@ -296,33 +454,15 @@ class ConfigurePageComponent extends AuthComponent {
:
} - { this.state.selectedSection ? -
-
- { this.state.allMonkeysAreDead ? - '' : -
- - Some monkeys are currently running. Note that changing the configuration will only apply to new - infections. -
- } -
- - -
-
- - : ''} + { content } +
+ + +
) } diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css index 7303603b9..02feb4d89 100644 --- a/monkey/monkey_island/cc/ui/src/styles/App.css +++ b/monkey/monkey_island/cc/ui/src/styles/App.css @@ -186,6 +186,10 @@ body { .nav-tabs > li > a { height: 63px } + +.nav > li > a:focus { + background-color: transparent !important; +} /* * Run Monkey Page */ From c3aa316c07104aa61ff93fb72d2ee457646357bb Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 3 May 2019 10:43:46 +0300 Subject: [PATCH 26/35] Added 'should_exploit' configuration field, minor fixes --- monkey/infection_monkey/config.py | 1 + monkey/infection_monkey/example.conf | 1 + monkey/infection_monkey/monkey.py | 21 +++---- monkey/monkey_island/cc/resources/root.py | 2 +- .../cc/services/config_schema.py | 41 +++++++------ .../ui/src/components/pages/ConfigurePage.js | 58 +++++++++++-------- 6 files changed, 71 insertions(+), 53 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 0d44cb973..b1d761a3f 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -205,6 +205,7 @@ class Configuration(object): # exploiters config ########################### + should_exploit = True skip_exploit_if_file_exist = False ms08_067_exploit_attempts = 5 diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 7ad23fa7b..b78426262 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -1,4 +1,5 @@ { + "should_exploit": true, "command_servers": [ "192.0.2.0:5000" ], diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index df7bcf820..f0d60db23 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -176,16 +176,17 @@ class InfectionMonkey(object): machine.set_default_server(self._default_server) # Order exploits according to their type - self._exploiters = sorted(self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value) - host_exploited = False - for exploiter in [exploiter(machine) for exploiter in self._exploiters]: - if self.try_exploiting(machine, exploiter): - host_exploited = True - VictimHostTelem('T1210', ScanStatus.USED.value, machine=machine).send() - break - if not host_exploited: - self._fail_exploitation_machines.add(machine) - VictimHostTelem('T1210', ScanStatus.SCANNED.value, machine=machine).send() + if WormConfiguration.should_exploit: + self._exploiters = sorted(self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value) + host_exploited = False + for exploiter in [exploiter(machine) for exploiter in self._exploiters]: + if self.try_exploiting(machine, exploiter): + host_exploited = True + VictimHostTelem('T1210', ScanStatus.USED.value, machine=machine).send() + break + if not host_exploited: + self._fail_exploitation_machines.add(machine) + VictimHostTelem('T1210', ScanStatus.SCANNED.value, machine=machine).send() if not self._keep_running: break diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index b180afd1b..f49af117c 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -25,7 +25,7 @@ class Root(flask_restful.Resource): if not action: return Root.get_server_info() elif action == "reset": - return jwt_required()(Database.reset_db()) + return jwt_required()(Database.reset_db)() elif action == "killall": return Root.kill_all() elif action == "is-up": diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 8c7e6c154..73476e645 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -14,7 +14,7 @@ SCHEMA = { "SmbExploiter" ], "title": "SMB Exploiter", - "attack_techniques": ["T1110", "T1210", "T1075"] + "attack_techniques": ["T1110", "T1075"] }, { "type": "string", @@ -54,55 +54,49 @@ SCHEMA = { "SSHExploiter" ], "title": "SSH Exploiter", - "attack_techniques": ["T1110", "T1210"] + "attack_techniques": ["T1110"] }, { "type": "string", "enum": [ "ShellShockExploiter" ], - "title": "ShellShock Exploiter", - "attack_techniques": ["T1210"] + "title": "ShellShock Exploiter" }, { "type": "string", "enum": [ "SambaCryExploiter" ], - "title": "SambaCry Exploiter", - "attack_techniques": ["T1210"] + "title": "SambaCry Exploiter" }, { "type": "string", "enum": [ "ElasticGroovyExploiter" ], - "title": "ElasticGroovy Exploiter", - "attack_techniques": ["T1210"] + "title": "ElasticGroovy Exploiter" }, { "type": "string", "enum": [ "Struts2Exploiter" ], - "title": "Struts2 Exploiter", - "attack_techniques": ["T1210"] + "title": "Struts2 Exploiter" }, { "type": "string", "enum": [ "WebLogicExploiter" ], - "title": "Oracle Web Logic Exploiter", - "attack_techniques": ["T1210"] + "title": "Oracle Web Logic Exploiter" }, { "type": "string", "enum": [ "HadoopExploiter" ], - "title": "Hadoop/Yarn Exploiter", - "attack_techniques": ["T1210"] + "title": "Hadoop/Yarn Exploiter" } ] }, @@ -184,9 +178,22 @@ SCHEMA = { }, "properties": { "basic": { - "title": "Basic - Credentials", + "title": "Basic - Exploits", "type": "object", "properties": { + "general": { + "title": "General", + "type": "object", + "properties": { + "should_exploit": { + "title": "Exploit network machines", + "type": "boolean", + "default": True, + "attack_techniques": ["T1210"], + "description": "Determines if monkey should try to safely exploit machines on the network" + } + } + }, "credentials": { "title": "Credentials", "type": "object", @@ -399,7 +406,7 @@ SCHEMA = { "title": "Harvest Azure Credentials", "type": "boolean", "default": True, - "attack_techniques": ["T1110", "T1078"], + "attack_techniques": ["T1003", "T1078"], "description": "Determine if the Monkey should try to harvest password credentials from Azure VMs" }, @@ -413,7 +420,7 @@ SCHEMA = { "title": "Should use Mimikatz", "type": "boolean", "default": True, - "attack_techniques": ["T1110", "T1078"], + "attack_techniques": ["T1003", "T1078"], "description": "Determines whether to use Mimikatz" }, } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 7aa143648..6c3257670 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -21,31 +21,39 @@ class ConfigurePageComponent extends AuthComponent { this.initialConfig = {}; this.initialAttackConfig = {}; this.sectionsOrder = ['attack', 'basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; - this.uiSchema = { - behaviour: { - custom_PBA_linux_cmd: { - "ui:widget": "textarea", - "ui:emptyValue": "" - }, - PBA_linux_file: { - "ui:widget": this.PBAlinux - }, - custom_PBA_windows_cmd: { - "ui:widget": "textarea", - "ui:emptyValue": "" - }, - PBA_windows_file: { - "ui:widget": this.PBAwindows - }, - PBA_linux_filename: { - classNames: "linux-pba-file-info", - "ui:emptyValue": "" - }, - PBA_windows_filename: { - classNames: "windows-pba-file-info", - "ui:emptyValue": "" + this.uiSchemas = { + basic: {"ui:order": ["general", "credentials"]}, + basic_network: {}, + monkey: { + behaviour: { + custom_PBA_linux_cmd: { + "ui:widget": "textarea", + "ui:emptyValue": "" + }, + PBA_linux_file: { + "ui:widget": this.PBAlinux + }, + custom_PBA_windows_cmd: { + "ui:widget": "textarea", + "ui:emptyValue": "" + }, + PBA_windows_file: { + "ui:widget": this.PBAwindows + }, + PBA_linux_filename: { + classNames: "linux-pba-file-info", + "ui:emptyValue": "" + }, + PBA_windows_filename: { + classNames: "windows-pba-file-info", + "ui:emptyValue": "" + } } - } + }, + cnc: {}, + network: {}, + exploits: {}, + internal: {} }; // set schema from server this.state = { @@ -409,7 +417,7 @@ class ConfigurePageComponent extends AuthComponent { displayedSchema['definitions'] = this.state.schema['definitions']; } let config_content = (
From 860b4eb81303f9471505b26abc2798596303b9ad Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 6 May 2019 15:28:46 +0300 Subject: [PATCH 27/35] Requirements fix --- monkey/monkey_island/requirements.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index aafb41f86..d013b2861 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -1,5 +1,5 @@ python-dateutil -tornado +tornado==5.1.1 werkzeug jinja2 markupsafe @@ -9,14 +9,13 @@ flask Flask-Pymongo Flask-Restful Flask-JWT -jsonschema +jsonschema==2.6.0 netifaces ipaddress enum34 pycryptodome boto3 awscli -dpath bson cffi PyInstaller From 4c0f73cb91e3704fa6c1fdf3cb2dcde790d59f44 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 7 May 2019 11:04:35 +0300 Subject: [PATCH 28/35] UI code style improved, reset_db fixed --- monkey/monkey_island/cc/app.py | 2 +- monkey/monkey_island/cc/services/database.py | 6 + .../src/components/attack/MatrixComponent.js | 33 +++-- .../ui/src/components/pages/ConfigurePage.js | 130 +++++++++++------- 4 files changed, 103 insertions(+), 68 deletions(-) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 243f1f71a..d0eff1da8 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -100,7 +100,7 @@ def init_app(mongo_url): with app.app_context(): database.init() - Database.reset_db() + Database.init_db() app.add_url_rule('/', 'serve_home', serve_home) app.add_url_rule('/', 'serve_static_file', serve_static_file) diff --git a/monkey/monkey_island/cc/services/database.py b/monkey/monkey_island/cc/services/database.py index ead2b2058..62e370e44 100644 --- a/monkey/monkey_island/cc/services/database.py +++ b/monkey/monkey_island/cc/services/database.py @@ -23,3 +23,9 @@ class Database(object): AttackConfig.reset_config() logger.info('DB was reset') return jsonify(status='OK') + + @staticmethod + def init_db(): + if not mongo.db.collection_names(): + Database.reset_db() + diff --git a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js index 1df65a50e..aae3dc4e8 100644 --- a/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/attack/MatrixComponent.js @@ -96,24 +96,29 @@ class MatrixComponent extends AuthComponent { return {'columns': columns, 'matrixTableData': matrixTableData, 'maxTechniques': maxTechniques} }; + renderLegend = () => { + return ( + ) + }; + render() { let tableData = this.getTableData(this.props.configuration); return (
- + {this.renderLegend()}
{ - console.log('bad attack configuration'); this.setState({lastAction: 'invalid_configuration'}); }); }; @@ -410,58 +413,79 @@ class ConfigurePageComponent extends AuthComponent { return pbaFile } + renderMatrix = () => { + return () + }; + + + renderConfigContent = (displayedSchema) => { + return (
+ {this.renderBasicNetworkWarning()} + + + +
) + }; + + renderRunningMonkeysWarning = () => { + return (
+ { this.state.allMonkeysAreDead ? + '' : +
+ + Some monkeys are currently running. Note that changing the configuration will only apply to new + infections. +
+ } +
) + }; + + renderBasicNetworkWarning = () => { + if (this.state.selectedSection === 'basic_network'){ + return (
+ + The Monkey scans its subnet if "Local network scan" is ticked. Additionally the monkey scans machines + according to its range class. +
) + } else { + return (
) + } + }; + + renderNav = () => { + return () + }; + render() { let displayedSchema = {}; if (this.state.schema.hasOwnProperty('properties') && this.state.selectedSection !== 'attack') { displayedSchema = this.state.schema['properties'][this.state.selectedSection]; displayedSchema['definitions'] = this.state.schema['definitions']; } - let config_content = (
-
- { this.state.allMonkeysAreDead ? - '' : -
- - Some monkeys are currently running. Note that changing the configuration will only apply to new - infections. -
- } -
- ); - let attack_content = (); let content = ''; if (this.state.selectedSection === 'attack' && Object.entries(this.state.attackConfig).length !== 0 ) { - content = attack_content + content = this.renderMatrix() } else if(this.state.selectedSection !== 'attack') { - content = config_content + content = this.renderConfigContent(displayedSchema) } - return ( {this.renderAttackAlertModal()}

Monkey Configuration

- - { - this.state.selectedSection === 'basic_network' ? -
- - The Monkey scans its subnet if "Local network scan" is ticked. Additionally the monkey scans machines - according to its range class. -
- :
- } + {this.renderNav()} + { this.renderRunningMonkeysWarning()} { content }