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 ?