Merge pull request #2243 from guardicore/2216-pass-servers-to-exploiters

2216 pass servers to exploiters
This commit is contained in:
Mike Salvatore 2022-09-05 10:04:07 -04:00
commit ee262778de
19 changed files with 80 additions and 39 deletions

View File

@ -2,7 +2,7 @@ import logging
import threading
from abc import abstractmethod
from datetime import datetime
from typing import Dict
from typing import Dict, Sequence
from common.event_queue import IAgentEventQueue
from common.utils.exceptions import FailedExploitationError
@ -35,6 +35,7 @@ class HostExploiter:
self.event_queue = None
self.options = {}
self.exploit_result = {}
self.servers = []
def set_start_time(self):
self.exploit_info["started"] = datetime.now().isoformat()
@ -58,6 +59,7 @@ class HostExploiter:
def exploit_host(
self,
host,
servers: Sequence[str],
current_depth: int,
telemetry_messenger: ITelemetryMessenger,
event_queue: IAgentEventQueue,
@ -66,6 +68,7 @@ class HostExploiter:
interrupt: threading.Event,
):
self.host = host
self.servers = servers
self.current_depth = current_depth
self.telemetry_messenger = telemetry_messenger
self.event_queue = event_queue

View File

@ -1,5 +1,5 @@
import threading
from typing import Dict, Type
from typing import Dict, Sequence, Type
from common.event_queue import IAgentEventQueue
from infection_monkey.model import VictimHost
@ -31,11 +31,17 @@ class ExploiterWrapper:
self._agent_binary_repository = agent_binary_repository
def exploit_host(
self, host: VictimHost, current_depth: int, options: Dict, interrupt: threading.Event
self,
host: VictimHost,
servers: Sequence[str],
current_depth: int,
options: Dict,
interrupt: threading.Event,
):
exploiter = self._exploit_class()
return exploiter.exploit_host(
host,
servers,
current_depth,
self._telemetry_messenger,
self._event_queue,

View File

@ -104,7 +104,7 @@ class HadoopExploiter(WebRCE):
def _build_command(self, path, http_path):
# Build command to execute
monkey_cmd = build_monkey_commandline(self.host, self.current_depth + 1)
monkey_cmd = build_monkey_commandline(self.servers, self.current_depth + 1)
if self.host.is_windows():
base_command = HADOOP_WINDOWS_COMMAND
else:

View File

@ -115,7 +115,7 @@ class Log4ShellExploiter(WebRCE):
def _build_command(self, path: PurePath, http_path) -> str:
# Build command to execute
monkey_cmd = build_monkey_commandline(self.host, self.current_depth + 1, location=path)
monkey_cmd = build_monkey_commandline(self.servers, self.current_depth + 1, location=path)
if self.host.is_windows():
base_command = LOG4SHELL_WINDOWS_COMMAND
else:

View File

@ -179,7 +179,7 @@ class MSSQLExploiter(HostExploiter):
def _build_agent_launch_command(self, agent_path_on_victim: PureWindowsPath) -> str:
agent_args = build_monkey_commandline(
self.host, self.current_depth + 1, agent_path_on_victim
self.servers, self.current_depth + 1, agent_path_on_victim
)
return f"{agent_path_on_victim} {DROPPER_ARG} {agent_args}"

View File

@ -15,7 +15,7 @@ from infection_monkey.exploit.powershell_utils.powershell_client import (
PowerShellClient,
)
from infection_monkey.exploit.tools.helpers import get_agent_dst_path, get_random_file_suffix
from infection_monkey.model import DROPPER_ARG, RUN_MONKEY, VictimHost
from infection_monkey.model import DROPPER_ARG, RUN_MONKEY
from infection_monkey.utils.commands import build_monkey_commandline
from infection_monkey.utils.environment import is_windows_os
from infection_monkey.utils.threading import interruptible_iter
@ -169,7 +169,7 @@ class PowerShellExploiter(HostExploiter):
def _run_monkey_executable_on_victim(self, executable_path):
monkey_execution_command = build_monkey_execution_command(
self.host, self.current_depth + 1, executable_path
self.servers, self.current_depth + 1, executable_path
)
logger.info(
@ -179,9 +179,9 @@ class PowerShellExploiter(HostExploiter):
self._client.execute_cmd_as_detached_process(monkey_execution_command)
def build_monkey_execution_command(host: VictimHost, depth: int, executable_path: str) -> str:
def build_monkey_execution_command(servers: List[str], depth: int, executable_path: str) -> str:
monkey_params = build_monkey_commandline(
target_host=host,
servers,
depth=depth,
location=executable_path,
)

View File

@ -91,14 +91,14 @@ class SMBExploiter(HostExploiter):
cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {
"dropper_path": remote_full_path
} + build_monkey_commandline(
self.host,
self.servers,
self.current_depth + 1,
str(dest_path),
)
else:
cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {
"monkey_path": remote_full_path
} + build_monkey_commandline(self.host, self.current_depth + 1)
} + build_monkey_commandline(self.servers, self.current_depth + 1)
smb_conn = None
for str_bind_format, port in SMBExploiter.KNOWN_PROTOCOLS.values():

View File

@ -245,7 +245,7 @@ class SSHExploiter(HostExploiter):
try:
cmdline = f"{monkey_path_on_victim} {MONKEY_ARG}"
cmdline += build_monkey_commandline(self.host, self.current_depth + 1)
cmdline += build_monkey_commandline(self.servers, self.current_depth + 1)
cmdline += " > /dev/null 2>&1 &"
ssh.exec_command(cmdline, timeout=SSH_EXEC_TIMEOUT)

View File

@ -370,14 +370,16 @@ class WebRCE(HostExploiter):
default_path = self.get_default_dropper_path()
if default_path is False:
return False
monkey_cmd = build_monkey_commandline(self.host, self.current_depth + 1, default_path)
monkey_cmd = build_monkey_commandline(
self.servers, self.current_depth + 1, default_path
)
command = RUN_MONKEY % {
"monkey_path": path,
"monkey_type": DROPPER_ARG,
"parameters": monkey_cmd,
}
else:
monkey_cmd = build_monkey_commandline(self.host, self.current_depth + 1)
monkey_cmd = build_monkey_commandline(self.servers, self.current_depth + 1)
command = RUN_MONKEY % {
"monkey_path": path,
"monkey_type": MONKEY_ARG,

View File

@ -103,14 +103,14 @@ class WmiExploiter(HostExploiter):
cmdline = DROPPER_CMDLINE_WINDOWS % {
"dropper_path": remote_full_path
} + build_monkey_commandline(
self.host,
self.servers,
self.current_depth + 1,
DROPPER_TARGET_PATH_WIN64,
)
else:
cmdline = MONKEY_CMDLINE_WINDOWS % {
"monkey_path": remote_full_path
} + build_monkey_commandline(self.host, self.current_depth + 1)
} + build_monkey_commandline(self.servers, self.current_depth + 1)
# execute the remote monkey
result = WmiTools.get_object(wmi_connection, "Win32_Process").Create(

View File

@ -2,7 +2,7 @@ import logging
import threading
import time
from ipaddress import IPv4Interface
from typing import Any, Callable, Collection, List, Optional
from typing import Any, Callable, Collection, List, Optional, Sequence
from common.agent_configuration import CustomPBAConfiguration, PluginConfiguration
from common.utils import Timer
@ -35,6 +35,7 @@ class AutomatedMaster(IMaster):
def __init__(
self,
current_depth: Optional[int],
servers: Sequence[str],
puppet: IPuppet,
telemetry_messenger: ITelemetryMessenger,
victim_host_factory: VictimHostFactory,
@ -43,6 +44,7 @@ class AutomatedMaster(IMaster):
credentials_store: IPropagationCredentialsRepository,
):
self._current_depth = current_depth
self._servers = servers
self._puppet = puppet
self._telemetry_messenger = telemetry_messenger
self._control_channel = control_channel
@ -175,7 +177,7 @@ class AutomatedMaster(IMaster):
logger.info(f"Current depth is {current_depth}")
if maximum_depth_reached(config.propagation.maximum_depth, current_depth):
self._propagator.propagate(config.propagation, current_depth, self._stop)
self._propagator.propagate(config.propagation, current_depth, self._servers, self._stop)
else:
logger.info("Skipping propagation: maximum depth reached")

View File

@ -53,6 +53,7 @@ class Exploiter:
exploitation_config: ExploitationConfiguration,
hosts_to_exploit: Queue,
current_depth: int,
servers: Sequence[str],
results_callback: Callback,
scan_completed: Event,
stop: Event,
@ -67,6 +68,7 @@ class Exploiter:
exploiters_to_run,
hosts_to_exploit,
current_depth,
servers,
results_callback,
scan_completed,
stop,
@ -103,6 +105,7 @@ class Exploiter:
exploiters_to_run: Sequence[PluginConfiguration],
hosts_to_exploit: Queue,
current_depth: int,
servers: Sequence[str],
results_callback: Callback,
scan_completed: Event,
stop: Event,
@ -113,7 +116,7 @@ class Exploiter:
try:
victim_host = hosts_to_exploit.get(timeout=QUEUE_TIMEOUT)
self._run_all_exploiters(
exploiters_to_run, victim_host, current_depth, results_callback, stop
exploiters_to_run, victim_host, current_depth, servers, results_callback, stop
)
except queue.Empty:
if _all_hosts_have_been_processed(scan_completed, hosts_to_exploit):
@ -130,6 +133,7 @@ class Exploiter:
exploiters_to_run: Sequence[PluginConfiguration],
victim_host: VictimHost,
current_depth: int,
servers: Sequence[str],
results_callback: Callback,
stop: Event,
):
@ -147,7 +151,7 @@ class Exploiter:
continue
exploiter_results = self._run_exploiter(
exploiter_name, exploiter.options, victim_host, current_depth, stop
exploiter_name, exploiter.options, victim_host, current_depth, servers, stop
)
results_callback(exploiter_name, victim_host, exploiter_results)
@ -160,6 +164,7 @@ class Exploiter:
options: Dict,
victim_host: VictimHost,
current_depth: int,
servers: Sequence[str],
stop: Event,
) -> ExploiterResultData:
logger.debug(f"Attempting to use {exploiter_name} on {victim_host.ip_addr}")
@ -169,7 +174,7 @@ class Exploiter:
try:
return self._puppet.exploit_host(
exploiter_name, victim_host, current_depth, options, stop
exploiter_name, victim_host, current_depth, servers, options, stop
)
except Exception as ex:
msg = (

View File

@ -47,7 +47,11 @@ class Propagator:
self._hosts_to_exploit: Queue = Queue()
def propagate(
self, propagation_config: PropagationConfiguration, current_depth: int, stop: Event
self,
propagation_config: PropagationConfiguration,
current_depth: int,
servers: Sequence[str],
stop: Event,
):
logger.info("Attempting to propagate")
@ -66,7 +70,13 @@ class Propagator:
exploit_thread = create_daemon_thread(
target=self._exploit_hosts,
name="PropagatorExploitThread",
args=(propagation_config.exploitation, current_depth, network_scan_completed, stop),
args=(
propagation_config.exploitation,
current_depth,
servers,
network_scan_completed,
stop,
),
)
scan_thread.start()
@ -167,6 +177,7 @@ class Propagator:
self,
exploitation_config: ExploitationConfiguration,
current_depth: int,
servers: Sequence[str],
network_scan_completed: Event,
stop: Event,
):
@ -176,6 +187,7 @@ class Propagator:
exploitation_config,
self._hosts_to_exploit,
current_depth,
servers,
self._process_exploit_attempts,
network_scan_completed,
stop,

View File

@ -154,7 +154,8 @@ class InfectionMonkey:
logger.debug(f"Default server set to: {self._control_client.server_address}")
else:
raise Exception(
f"Failed to connect to the island via any known server address: {self._opts.servers}"
f"Failed to connect to the island via "
f"any known server address: {self._opts.servers}"
)
self._control_client.wakeup(parent=self._opts.parent)
@ -219,6 +220,7 @@ class InfectionMonkey:
self._master = AutomatedMaster(
self._current_depth,
self._opts.servers,
puppet,
telemetry_messenger,
victim_host_factory,

View File

@ -70,11 +70,12 @@ class Puppet(IPuppet):
name: str,
host: VictimHost,
current_depth: int,
servers: Sequence[str],
options: Dict,
interrupt: threading.Event,
) -> ExploiterResultData:
exploiter = self._plugin_registry.get_plugin(name, PluginType.EXPLOITER)
return exploiter.exploit_host(host, current_depth, options, interrupt)
return exploiter.exploit_host(host, servers, current_depth, options, interrupt)
def run_payload(self, name: str, options: Dict, interrupt: threading.Event):
payload = self._plugin_registry.get_plugin(name, PluginType.PAYLOAD)

View File

@ -6,7 +6,6 @@ import pytest
from infection_monkey.exploit import powershell
from infection_monkey.exploit.tools.helpers import AGENT_BINARY_PATH_WIN64
from infection_monkey.model.host import VictimHost
# Use the path_win32api_get_user_name fixture for all tests in this module
pytestmark = pytest.mark.usefixtures("patch_win32api_get_user_name")
@ -16,6 +15,8 @@ PASSWORD_LIST = ["pass1", "pass2"]
LM_HASH_LIST = ["bogo_lm_1"]
NT_HASH_LIST = ["bogo_nt_1", "bogo_nt_2"]
bogus_servers = ["1.1.1.1:5000", "2.2.2.2:5007"]
mock_agent_binary_repository = MagicMock()
mock_agent_binary_repository.get_agent_binary.return_value = BytesIO(b"BINARY_EXECUTABLE")
@ -33,6 +34,7 @@ def powershell_arguments(http_and_https_both_enabled_host):
}
arguments = {
"host": http_and_https_both_enabled_host,
"servers": bogus_servers,
"options": options,
"current_depth": 2,
"telemetry_messenger": MagicMock(),
@ -179,11 +181,10 @@ def test_login_attempts_correctly_reported(monkeypatch, powershell_exploiter, po
def test_build_monkey_execution_command():
host = VictimHost("127.0.0.1")
depth = 2
executable_path = "/tmp/test-monkey"
cmd = powershell.build_monkey_execution_command(host, depth, executable_path)
cmd = powershell.build_monkey_execution_command(bogus_servers, depth, executable_path)
assert f"-d {depth}" in cmd
assert executable_path in cmd

View File

@ -14,7 +14,7 @@ INTERVAL = 0.001
def test_terminate_without_start():
m = AutomatedMaster(None, None, None, None, MagicMock(), [], MagicMock())
m = AutomatedMaster(None, [], None, None, None, MagicMock(), [], MagicMock())
# Test that call to terminate does not raise exception
m.terminate()
@ -34,7 +34,7 @@ def test_stop_if_cant_get_config_from_island(monkeypatch):
monkeypatch.setattr(
"infection_monkey.master.automated_master.CHECK_FOR_TERMINATE_INTERVAL_SEC", INTERVAL
)
m = AutomatedMaster(None, None, None, None, cc, [], MagicMock())
m = AutomatedMaster(None, [], None, None, None, cc, [], MagicMock())
m.start()
assert cc.get_config.call_count == CHECK_FOR_CONFIG_COUNT
@ -73,7 +73,7 @@ def test_stop_if_cant_get_stop_signal_from_island(monkeypatch, sleep_and_return_
"infection_monkey.master.automated_master.CHECK_FOR_TERMINATE_INTERVAL_SEC", INTERVAL
)
m = AutomatedMaster(None, None, None, None, cc, [], MagicMock())
m = AutomatedMaster(None, [], None, None, None, cc, [], MagicMock())
m.start()
assert cc.should_agent_stop.call_count == CHECK_FOR_STOP_AGENT_COUNT

View File

@ -66,7 +66,7 @@ def hosts_to_exploit(hosts):
def enqueue_hosts(hosts: Iterable[VictimHost]):
q = Queue()
q: Queue = Queue()
for h in hosts:
q.put(h)
@ -85,6 +85,7 @@ def get_host_exploit_combos_from_call_args_list(call_args_list):
CREDENTIALS_FOR_PROPAGATION = {"usernames": ["m0nk3y", "user"], "passwords": ["1234", "pword"]}
SERVERS = ["127.0.0.1:5000", "10.10.10.10:5007"]
def get_credentials_for_propagation():
@ -98,7 +99,7 @@ def run_exploiters(exploiter_config, hosts_to_exploit, callback, scan_completed,
scan_completed.set()
e = Exploiter(puppet, num_workers, get_credentials_for_propagation)
e.exploit_hosts(exploiter_config, hosts, 1, callback, scan_completed, stop)
e.exploit_hosts(exploiter_config, hosts, 1, SERVERS, callback, scan_completed, stop)
return inner
@ -124,7 +125,7 @@ def test_credentials_passed_to_exploiter(run_exploiters):
run_exploiters(mock_puppet, 1)
for call_args in mock_puppet.exploit_host.call_args_list:
assert call_args[0][3].get("credentials") == CREDENTIALS_FOR_PROPAGATION
assert call_args[0][4].get("credentials") == CREDENTIALS_FOR_PROPAGATION
def test_stop_after_callback(exploiter_config, callback, scan_completed, stop, hosts_to_exploit):
@ -143,7 +144,9 @@ def test_stop_after_callback(exploiter_config, callback, scan_completed, stop, h
# Intentionally NOT setting scan_completed.set(); _callback() will set stop
e = Exploiter(MockPuppet(), callback_barrier_count + 2, get_credentials_for_propagation)
e.exploit_hosts(exploiter_config, hosts_to_exploit, 1, stoppable_callback, scan_completed, stop)
e.exploit_hosts(
exploiter_config, hosts_to_exploit, 1, SERVERS, stoppable_callback, scan_completed, stop
)
assert stoppable_callback.call_count == 2

View File

@ -110,6 +110,8 @@ os_windows = "windows"
os_linux = "linux"
SERVERS = ["127.0.0.1:5000", "10.10.10.10:5007"]
@pytest.fixture
def mock_ip_scanner():
@ -134,6 +136,7 @@ class StubExploiter:
exploiters_to_run,
hosts_to_exploit,
current_depth,
servers,
results_callback,
scan_completed,
stop,
@ -171,7 +174,7 @@ def test_scan_result_processing(
subnets=["10.0.0.1", "10.0.0.2", "10.0.0.3"],
)
propagation_config = get_propagation_config(default_agent_configuration, targets)
p.propagate(propagation_config, 1, Event())
p.propagate(propagation_config, 1, SERVERS, Event())
assert len(telemetry_messenger_spy.telemetries) == 3
@ -204,6 +207,7 @@ class MockExploiter:
exploiters_to_run,
hosts_to_exploit,
current_depth,
servers,
results_callback,
scan_completed,
stop,
@ -269,7 +273,7 @@ def test_exploiter_result_processing(
subnets=["10.0.0.1", "10.0.0.2", "10.0.0.3"],
)
propagation_config = get_propagation_config(default_agent_configuration, targets)
p.propagate(propagation_config, 1, Event())
p.propagate(propagation_config, 1, SERVERS, Event())
exploit_telems = [t for t in telemetry_messenger_spy.telemetries if isinstance(t, ExploitTelem)]
assert len(exploit_telems) == 4
@ -310,7 +314,7 @@ def test_scan_target_generation(
subnets=["10.0.0.0/29", "172.10.20.30"],
)
propagation_config = get_propagation_config(default_agent_configuration, targets)
p.propagate(propagation_config, 1, Event())
p.propagate(propagation_config, 1, SERVERS, Event())
expected_ip_scan_list = [
"10.0.0.0",