From 92615b848d47967ed747ab93dedb1afe761cb3ea Mon Sep 17 00:00:00 2001
From: VakarisZ <vakarisz@yahoo.com>
Date: Wed, 13 Mar 2019 20:48:25 +0200
Subject: [PATCH] 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 ?
           <Form schema={displayedSchema}
-                uiSchema={uiSchema}
+                uiSchema={this.uiSchema}
                 formData={this.state.configuration[this.state.selectedSection]}
                 onSubmit={this.onSubmit}
                 onChange={this.onChange}