Merge pull request #1657 from guardicore/1597-implement-exploitation

1597 implement exploitation
This commit is contained in:
Mike Salvatore 2021-12-15 09:11:17 -05:00 committed by GitHub
commit ba5d755dfa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 493 additions and 35 deletions

View File

@ -16,7 +16,9 @@ class UnknownPluginError(Exception):
pass pass
ExploiterResultData = namedtuple("ExploiterResultData", ["result", "info", "attempts"]) ExploiterResultData = namedtuple(
"ExploiterResultData", ["success", "info", "attempts", "error_message"]
)
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"])

View File

@ -1,4 +1,5 @@
from .ip_scan_results import IPScanResults from .ip_scan_results import IPScanResults
from .ip_scanner import IPScanner from .ip_scanner import IPScanner
from .exploiter import Exploiter
from .propagator import Propagator from .propagator import Propagator
from .automated_master import AutomatedMaster from .automated_master import AutomatedMaster

View File

@ -6,18 +6,20 @@ from typing import Any, Callable, Dict, List, Tuple
from infection_monkey.i_control_channel import IControlChannel from infection_monkey.i_control_channel import IControlChannel
from infection_monkey.i_master import IMaster from infection_monkey.i_master import IMaster
from infection_monkey.i_puppet import IPuppet from infection_monkey.i_puppet import IPuppet
from infection_monkey.model import VictimHostFactory
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
from infection_monkey.telemetry.post_breach_telem import PostBreachTelem from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
from infection_monkey.telemetry.system_info_telem import SystemInfoTelem from infection_monkey.telemetry.system_info_telem import SystemInfoTelem
from infection_monkey.utils.timer import Timer from infection_monkey.utils.timer import Timer
from . import IPScanner, Propagator from . import Exploiter, IPScanner, Propagator
from .threading_utils import create_daemon_thread from .threading_utils import create_daemon_thread
CHECK_ISLAND_FOR_STOP_COMMAND_INTERVAL_SEC = 5 CHECK_ISLAND_FOR_STOP_COMMAND_INTERVAL_SEC = 5
CHECK_FOR_TERMINATE_INTERVAL_SEC = CHECK_ISLAND_FOR_STOP_COMMAND_INTERVAL_SEC / 5 CHECK_FOR_TERMINATE_INTERVAL_SEC = CHECK_ISLAND_FOR_STOP_COMMAND_INTERVAL_SEC / 5
SHUTDOWN_TIMEOUT = 5 SHUTDOWN_TIMEOUT = 5
NUM_SCAN_THREADS = 16 # TODO: Adjust this to the optimal number of scan threads NUM_SCAN_THREADS = 16 # TODO: Adjust this to the optimal number of scan threads
NUM_EXPLOIT_THREADS = 4 # TODO: Adjust this to the optimal number of exploit threads
logger = logging.getLogger() logger = logging.getLogger()
@ -27,6 +29,7 @@ class AutomatedMaster(IMaster):
self, self,
puppet: IPuppet, puppet: IPuppet,
telemetry_messenger: ITelemetryMessenger, telemetry_messenger: ITelemetryMessenger,
victim_host_factory: VictimHostFactory,
control_channel: IControlChannel, control_channel: IControlChannel,
): ):
self._puppet = puppet self._puppet = puppet
@ -34,7 +37,10 @@ class AutomatedMaster(IMaster):
self._control_channel = control_channel self._control_channel = control_channel
ip_scanner = IPScanner(self._puppet, NUM_SCAN_THREADS) ip_scanner = IPScanner(self._puppet, NUM_SCAN_THREADS)
self._propagator = Propagator(self._telemetry_messenger, ip_scanner) exploiter = Exploiter(self._puppet, NUM_EXPLOIT_THREADS)
self._propagator = Propagator(
self._telemetry_messenger, ip_scanner, exploiter, victim_host_factory
)
self._stop = threading.Event() self._stop = threading.Event()
self._master_thread = create_daemon_thread(target=self._run_master_thread) self._master_thread = create_daemon_thread(target=self._run_master_thread)

View File

@ -0,0 +1,97 @@
import logging
import queue
import threading
from queue import Queue
from threading import Event
from typing import Callable, Dict, List
from infection_monkey.i_puppet import ExploiterResultData, IPuppet
from infection_monkey.model import VictimHost
from .threading_utils import run_worker_threads
QUEUE_TIMEOUT = 2
logger = logging.getLogger()
ExploiterName = str
Callback = Callable[[ExploiterName, VictimHost, ExploiterResultData], None]
class Exploiter:
def __init__(self, puppet: IPuppet, num_workers: int):
self._puppet = puppet
self._num_workers = num_workers
def exploit_hosts(
self,
exploiter_config: Dict,
hosts_to_exploit: Queue,
results_callback: Callback,
scan_completed: Event,
stop: Event,
):
# Run vulnerability exploiters before brute force exploiters to minimize the effect of
# account lockout due to invalid credentials
exploiters_to_run = exploiter_config["vulnerability"] + exploiter_config["brute_force"]
logger.debug(
"Agent is configured to run the following exploiters in order: "
f"{','.join([e['name'] for e in exploiters_to_run])}"
)
exploit_args = (exploiters_to_run, hosts_to_exploit, results_callback, scan_completed, stop)
run_worker_threads(
target=self._exploit_hosts_on_queue, args=exploit_args, num_workers=self._num_workers
)
def _exploit_hosts_on_queue(
self,
exploiters_to_run: List[Dict],
hosts_to_exploit: Queue,
results_callback: Callback,
scan_completed: Event,
stop: Event,
):
logger.debug(f"Starting exploiter thread -- Thread ID: {threading.get_ident()}")
while not stop.is_set():
try:
victim_host = hosts_to_exploit.get(timeout=QUEUE_TIMEOUT)
self._run_all_exploiters(exploiters_to_run, victim_host, results_callback, stop)
except queue.Empty:
if _all_hosts_have_been_processed(scan_completed, hosts_to_exploit):
break
logger.debug(
f"Exiting exploiter thread -- Thread ID: {threading.get_ident()} -- "
f"stop.is_set(): {stop.is_set()} -- network_scan_completed: "
f"{scan_completed.is_set()}"
)
def _run_all_exploiters(
self,
exploiters_to_run: List[Dict],
victim_host: VictimHost,
results_callback: Callback,
stop: Event,
):
for exploiter in exploiters_to_run:
if stop.is_set():
break
exploiter_name = exploiter["name"]
exploiter_results = self._run_exploiter(exploiter_name, victim_host, stop)
results_callback(exploiter_name, victim_host, exploiter_results)
if exploiter["propagator"] and exploiter_results.success:
break
def _run_exploiter(
self, exploiter_name: str, victim_host: VictimHost, stop: Event
) -> ExploiterResultData:
logger.debug(f"Attempting to use {exploiter_name} on {victim_host}")
return self._puppet.exploit_host(exploiter_name, victim_host.ip_addr, {}, stop)
def _all_hosts_have_been_processed(scan_completed: Event, hosts_to_exploit: Queue):
return scan_completed.is_set() and hosts_to_exploit.empty()

View File

@ -14,7 +14,7 @@ from infection_monkey.i_puppet import (
) )
from . import IPScanResults from . import IPScanResults
from .threading_utils import create_daemon_thread from .threading_utils import run_worker_threads
logger = logging.getLogger() logger = logging.getLogger()
@ -35,14 +35,7 @@ class IPScanner:
ips.put(ip) ips.put(ip)
scan_ips_args = (ips, options, results_callback, stop) scan_ips_args = (ips, options, results_callback, stop)
scan_threads = [] run_worker_threads(target=self._scan_ips, args=scan_ips_args, num_workers=self._num_workers)
for i in range(0, self._num_workers):
t = create_daemon_thread(target=self._scan_ips, args=scan_ips_args)
t.start()
scan_threads.append(t)
for t in scan_threads:
t.join()
def _scan_ips(self, ips: Queue, options: Dict, results_callback: Callback, stop: Event): def _scan_ips(self, ips: Queue, options: Dict, results_callback: Callback, stop: Event):
logger.debug(f"Starting scan thread -- Thread ID: {threading.get_ident()}") logger.debug(f"Starting scan thread -- Thread ID: {threading.get_ident()}")

View File

@ -1,41 +1,60 @@
import logging import logging
from queue import Queue from queue import Queue
from threading import Event, Thread from threading import Event
from typing import Dict from typing import Dict
from infection_monkey.i_puppet import FingerprintData, PingScanData, PortScanData, PortStatus from infection_monkey.i_puppet import (
from infection_monkey.model.host import VictimHost ExploiterResultData,
FingerprintData,
PingScanData,
PortScanData,
PortStatus,
)
from infection_monkey.model import VictimHost, VictimHostFactory
from infection_monkey.telemetry.exploit_telem import ExploitTelem
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
from infection_monkey.telemetry.scan_telem import ScanTelem from infection_monkey.telemetry.scan_telem import ScanTelem
from . import IPScanner, IPScanResults from . import Exploiter, IPScanner, IPScanResults
from .threading_utils import create_daemon_thread from .threading_utils import create_daemon_thread
logger = logging.getLogger() logger = logging.getLogger()
class Propagator: class Propagator:
def __init__(self, telemetry_messenger: ITelemetryMessenger, ip_scanner: IPScanner): def __init__(
self,
telemetry_messenger: ITelemetryMessenger,
ip_scanner: IPScanner,
exploiter: Exploiter,
victim_host_factory: VictimHostFactory,
):
self._telemetry_messenger = telemetry_messenger self._telemetry_messenger = telemetry_messenger
self._ip_scanner = ip_scanner self._ip_scanner = ip_scanner
self._exploiter = exploiter
self._victim_host_factory = victim_host_factory
self._hosts_to_exploit = None self._hosts_to_exploit = None
def propagate(self, propagation_config: Dict, stop: Event): def propagate(self, propagation_config: Dict, stop: Event):
logger.info("Attempting to propagate") logger.info("Attempting to propagate")
network_scan_completed = Event()
self._hosts_to_exploit = Queue() self._hosts_to_exploit = Queue()
scan_thread = create_daemon_thread( scan_thread = create_daemon_thread(
target=self._scan_network, args=(propagation_config, stop) target=self._scan_network, args=(propagation_config, stop)
) )
exploit_thread = create_daemon_thread( exploit_thread = create_daemon_thread(
target=self._exploit_targets, args=(scan_thread, stop) target=self._exploit_hosts,
args=(propagation_config, network_scan_completed, stop),
) )
scan_thread.start() scan_thread.start()
exploit_thread.start() exploit_thread.start()
scan_thread.join() scan_thread.join()
network_scan_completed.set()
exploit_thread.join() exploit_thread.join()
logger.info("Finished attempting to propagate") logger.info("Finished attempting to propagate")
@ -52,7 +71,7 @@ class Propagator:
logger.info("Finished network scan") logger.info("Finished network scan")
def _process_scan_results(self, ip: str, scan_results: IPScanResults): def _process_scan_results(self, ip: str, scan_results: IPScanResults):
victim_host = VictimHost(ip) victim_host = self._victim_host_factory.build_victim_host(ip)
Propagator._process_ping_scan_results(victim_host, scan_results.ping_scan_data) Propagator._process_ping_scan_results(victim_host, scan_results.ping_scan_data)
Propagator._process_tcp_scan_results(victim_host, scan_results.port_scan_data) Propagator._process_tcp_scan_results(victim_host, scan_results.port_scan_data)
@ -95,5 +114,35 @@ class Propagator:
for service, details in fd.services.items(): for service, details in fd.services.items():
victim_host.services.setdefault(service, {}).update(details) victim_host.services.setdefault(service, {}).update(details)
def _exploit_targets(self, scan_thread: Thread, stop: Event): def _exploit_hosts(
pass self,
propagation_config: Dict,
network_scan_completed: Event,
stop: Event,
):
logger.info("Exploiting victims")
exploiter_config = propagation_config["exploiters"]
self._exploiter.exploit_hosts(
exploiter_config,
self._hosts_to_exploit,
self._process_exploit_attempts,
network_scan_completed,
stop,
)
logger.info("Finished exploiting victims")
def _process_exploit_attempts(
self, exploiter_name: str, host: VictimHost, result: ExploiterResultData
):
if result.success:
logger.info(f"Successfully propagated to {host} using {exploiter_name}")
else:
logger.info(
f"Failed to propagate to {host} using {exploiter_name}: {result.error_message}"
)
self._telemetry_messenger.send_telemetry(
ExploitTelem(exploiter_name, host, result.success, result.info, result.attempts)
)

View File

@ -2,5 +2,16 @@ from threading import Thread
from typing import Callable, Tuple from typing import Callable, Tuple
def run_worker_threads(target: Callable[..., None], args: Tuple = (), num_workers: int = 2):
worker_threads = []
for i in range(0, num_workers):
t = create_daemon_thread(target=target, args=args)
t.start()
worker_threads.append(t)
for t in worker_threads:
t.join()
def create_daemon_thread(target: Callable[..., None], args: Tuple = ()): def create_daemon_thread(target: Callable[..., None], args: Tuple = ()):
return Thread(target=target, args=args, daemon=True) return Thread(target=target, args=args, daemon=True)

View File

@ -1,4 +1,5 @@
from infection_monkey.model.host import VictimHost from infection_monkey.model.host import VictimHost
from infection_monkey.model.victim_host_factory import VictimHostFactory
MONKEY_ARG = "m0nk3y" MONKEY_ARG = "m0nk3y"
DROPPER_ARG = "dr0pp3r" DROPPER_ARG = "dr0pp3r"

View File

@ -0,0 +1,28 @@
from infection_monkey.model import VictimHost
class VictimHostFactory:
def __init__(self):
pass
def build_victim_host(self, ip: str):
victim_host = VictimHost(ip)
# TODO: Reimplement the below logic from the old monkey.py
"""
if self._monkey_tunnel:
self._monkey_tunnel.set_tunnel_for_host(machine)
if self._default_server:
if self._network.on_island(self._default_server):
machine.set_default_server(
get_interface_to_target(machine.ip_addr)
+ (":" + self._default_server_port if self._default_server_port else "")
)
else:
machine.set_default_server(self._default_server)
logger.debug(
f"Default server for machine: {machine} set to {machine.default_server}"
)
"""
return victim_host

View File

@ -280,11 +280,24 @@ class MockPuppet(IPuppet):
"executed_cmds": [], "executed_cmds": [],
} }
successful_exploiters = { successful_exploiters = {
DOT_1: {"PowerShellExploiter": ExploiterResultData(True, info_powershell, attempts)}, DOT_1: {
DOT_3: {"SSHExploiter": ExploiterResultData(False, info_ssh, attempts)}, "PowerShellExploiter": ExploiterResultData(True, info_powershell, attempts, None),
"ZerologonExploiter": ExploiterResultData(False, {}, [], "Zerologon failed"),
"SSHExploiter": ExploiterResultData(False, info_ssh, attempts, "Failed exploiting"),
},
DOT_3: {
"PowerShellExploiter": ExploiterResultData(
False, info_powershell, attempts, "PowerShell Exploiter Failed"
),
"SSHExploiter": ExploiterResultData(False, info_ssh, attempts, "Failed exploiting"),
"ZerologonExploiter": ExploiterResultData(True, {}, [], None),
},
} }
return successful_exploiters[host][name] try:
return successful_exploiters[host][name]
except KeyError:
return ExploiterResultData(False, {}, [], f"{name} failed for host {host}")
def run_payload( def run_payload(
self, name: str, options: Dict, interrupt: threading.Event self, name: str, options: Dict, interrupt: threading.Event

View File

@ -475,6 +475,9 @@ class ConfigService:
formatted_propagation_config["targets"] = ConfigService._format_targets_from_flat_config( formatted_propagation_config["targets"] = ConfigService._format_targets_from_flat_config(
config config
) )
formatted_propagation_config[
"exploiters"
] = ConfigService._format_exploiters_from_flat_config(config)
config["propagation"] = formatted_propagation_config config["propagation"] = formatted_propagation_config
@ -567,3 +570,33 @@ class ConfigService:
config.pop(flat_subnet_scan_list_field, None) config.pop(flat_subnet_scan_list_field, None)
return formatted_scan_targets_config return formatted_scan_targets_config
@staticmethod
def _format_exploiters_from_flat_config(config: Dict):
flat_config_exploiter_classes_field = "exploiter_classes"
brute_force_category = "brute_force"
vulnerability_category = "vulnerability"
brute_force_exploiters = {
"MSSQLExploiter",
"PowerShellExploiter",
"SSHExploiter",
"SmbExploiter",
"WmiExploiter",
}
formatted_exploiters_config = {"brute_force": [], "vulnerability": []}
for exploiter in sorted(config[flat_config_exploiter_classes_field]):
category = (
brute_force_category
if exploiter in brute_force_exploiters
else vulnerability_category
)
formatted_exploiters_config[category].append(
{"name": exploiter, "propagator": (exploiter != "ZerologonExploiter")}
)
config.pop(flat_config_exploiter_classes_field, None)
return formatted_exploiters_config

View File

@ -55,6 +55,7 @@
"ShellShockExploiter", "ShellShockExploiter",
"ElasticGroovyExploiter", "ElasticGroovyExploiter",
"Struts2Exploiter", "Struts2Exploiter",
"ZerologonExploiter",
"WebLogicExploiter", "WebLogicExploiter",
"HadoopExploiter", "HadoopExploiter",
"MSSQLExploiter", "MSSQLExploiter",

View File

@ -2,7 +2,7 @@ from infection_monkey.master import AutomatedMaster
def test_terminate_without_start(): def test_terminate_without_start():
m = AutomatedMaster(None, None, None) m = AutomatedMaster(None, None, None, None)
# Test that call to terminate does not raise exception # Test that call to terminate does not raise exception
m.terminate() m.terminate()

View File

@ -0,0 +1,102 @@
import logging
from queue import Queue
from threading import Barrier, Event
from unittest.mock import MagicMock
import pytest
from infection_monkey.master import Exploiter
from infection_monkey.model import VictimHost
from infection_monkey.puppet.mock_puppet import MockPuppet
logger = logging.getLogger()
@pytest.fixture(autouse=True)
def patch_queue_timeout(monkeypatch):
monkeypatch.setattr("infection_monkey.master.exploiter.QUEUE_TIMEOUT", 0.001)
@pytest.fixture
def scan_completed():
return Event()
@pytest.fixture
def stop():
return Event()
@pytest.fixture
def callback():
return MagicMock()
@pytest.fixture
def exploiter_config():
return {
"brute_force": [
{"name": "PowerShellExploiter", "propagator": True},
{"name": "SSHExploiter", "propagator": True},
],
"vulnerability": [
{"name": "ZerologonExploiter", "propagator": False},
],
}
@pytest.fixture
def hosts():
return [VictimHost("10.0.0.1"), VictimHost("10.0.0.3")]
@pytest.fixture
def hosts_to_exploit(hosts):
q = Queue()
q.put(hosts[0])
q.put(hosts[1])
return q
def test_exploiter(exploiter_config, callback, scan_completed, stop, hosts, hosts_to_exploit):
# Set this so that Exploiter() exits once it has processed all victims
scan_completed.set()
e = Exploiter(MockPuppet(), 2)
e.exploit_hosts(exploiter_config, hosts_to_exploit, callback, scan_completed, stop)
assert callback.call_count == 5
host_exploit_combos = set()
for i in range(0, 5):
victim_host = callback.call_args_list[i][0][0]
exploiter_name = callback.call_args_list[i][0][1]
host_exploit_combos.add((victim_host, exploiter_name))
assert ("ZerologonExploiter", hosts[0]) in host_exploit_combos
assert ("PowerShellExploiter", hosts[0]) in host_exploit_combos
assert ("ZerologonExploiter", hosts[1]) in host_exploit_combos
assert ("PowerShellExploiter", hosts[1]) in host_exploit_combos
assert ("SSHExploiter", hosts[1]) in host_exploit_combos
def test_stop_after_callback(exploiter_config, callback, scan_completed, stop, hosts_to_exploit):
callback_barrier_count = 2
def _callback(*_):
# Block all threads here until 2 threads reach this barrier, then set stop
# and test that neither thread continues to scan.
_callback.barrier.wait()
stop.set()
_callback.barrier = Barrier(callback_barrier_count)
stoppable_callback = MagicMock(side_effect=_callback)
# Intentionally NOT setting scan_completed.set(); _callback() will set stop
e = Exploiter(MockPuppet(), callback_barrier_count + 2)
e.exploit_hosts(exploiter_config, hosts_to_exploit, stoppable_callback, scan_completed, stop)
assert stoppable_callback.call_count == 2

View File

@ -1,11 +1,19 @@
from threading import Event from threading import Event
from infection_monkey.i_puppet import FingerprintData, PingScanData, PortScanData, PortStatus from infection_monkey.i_puppet import (
ExploiterResultData,
FingerprintData,
PingScanData,
PortScanData,
PortStatus,
)
from infection_monkey.master import IPScanResults, Propagator from infection_monkey.master import IPScanResults, Propagator
from infection_monkey.model import VictimHostFactory
from infection_monkey.telemetry.exploit_telem import ExploitTelem
empty_fingerprint_data = FingerprintData(None, None, {}) empty_fingerprint_data = FingerprintData(None, None, {})
dot_1_results = IPScanResults( dot_1_scan_results = IPScanResults(
PingScanData(True, "windows"), PingScanData(True, "windows"),
{ {
22: PortScanData(22, PortStatus.CLOSED, None, None), 22: PortScanData(22, PortStatus.CLOSED, None, None),
@ -19,7 +27,7 @@ dot_1_results = IPScanResults(
}, },
) )
dot_3_results = IPScanResults( dot_3_scan_results = IPScanResults(
PingScanData(True, "linux"), PingScanData(True, "linux"),
{ {
22: PortScanData(22, PortStatus.OPEN, "SSH BANNER", "tcp-22"), 22: PortScanData(22, PortStatus.OPEN, "SSH BANNER", "tcp-22"),
@ -42,7 +50,7 @@ dot_3_results = IPScanResults(
}, },
) )
dead_host_results = IPScanResults( dead_host_scan_results = IPScanResults(
PingScanData(False, None), PingScanData(False, None),
{ {
22: PortScanData(22, PortStatus.CLOSED, None, None), 22: PortScanData(22, PortStatus.CLOSED, None, None),
@ -79,19 +87,27 @@ class MockIPScanner:
def scan(self, ips_to_scan, _, results_callback, stop): def scan(self, ips_to_scan, _, results_callback, stop):
for ip in ips_to_scan: for ip in ips_to_scan:
if ip.endswith(".1"): if ip.endswith(".1"):
results_callback(ip, dot_1_results) results_callback(ip, dot_1_scan_results)
elif ip.endswith(".3"): elif ip.endswith(".3"):
results_callback(ip, dot_3_results) results_callback(ip, dot_3_scan_results)
else: else:
results_callback(ip, dead_host_results) results_callback(ip, dead_host_scan_results)
class StubExploiter:
def exploit_hosts(
self, hosts_to_exploit, exploiter_config, results_callback, scan_completed, stop
):
pass
def test_scan_result_processing(telemetry_messenger_spy): def test_scan_result_processing(telemetry_messenger_spy):
p = Propagator(telemetry_messenger_spy, MockIPScanner()) p = Propagator(telemetry_messenger_spy, MockIPScanner(), StubExploiter(), VictimHostFactory())
p.propagate( p.propagate(
{ {
"targets": {"subnet_scan_list": ["10.0.0.1", "10.0.0.2", "10.0.0.3"]}, "targets": {"subnet_scan_list": ["10.0.0.1", "10.0.0.2", "10.0.0.3"]},
"network_scan": {}, "network_scan": {}, # This is empty since MockIPscanner ignores it
"exploiters": {}, # This is empty since StubExploiter ignores it
}, },
Event(), Event(),
) )
@ -119,3 +135,79 @@ def test_scan_result_processing(telemetry_messenger_spy):
assert data["machine"]["os"] == {} assert data["machine"]["os"] == {}
assert data["machine"]["services"] == {} assert data["machine"]["services"] == {}
assert data["machine"]["icmp"] is False assert data["machine"]["icmp"] is False
class MockExploiter:
def exploit_hosts(
self, exploiter_config, hosts_to_exploit, results_callback, scan_completed, stop
):
hte = []
for _ in range(0, 2):
hte.append(hosts_to_exploit.get())
for host in hte:
if host.ip_addr.endswith(".1"):
results_callback(
"PowerShellExploiter",
host,
ExploiterResultData(True, {}, {}, None),
)
results_callback(
"SSHExploiter",
host,
ExploiterResultData(False, {}, {}, "SSH FAILED for .1"),
)
if host.ip_addr.endswith(".2"):
results_callback(
"PowerShellExploiter",
host,
ExploiterResultData(False, {}, {}, "POWERSHELL FAILED for .2"),
)
results_callback(
"SSHExploiter",
host,
ExploiterResultData(False, {}, {}, "SSH FAILED for .2"),
)
if host.ip_addr.endswith(".3"):
results_callback(
"PowerShellExploiter",
host,
ExploiterResultData(False, {}, {}, "POWERSHELL FAILED for .3"),
)
results_callback(
"SSHExploiter",
host,
ExploiterResultData(True, {}, {}, None),
)
def test_exploiter_result_processing(telemetry_messenger_spy):
p = Propagator(telemetry_messenger_spy, MockIPScanner(), MockExploiter(), VictimHostFactory())
p.propagate(
{
"targets": {"subnet_scan_list": ["10.0.0.1", "10.0.0.2", "10.0.0.3"]},
"network_scan": {}, # This is empty since MockIPscanner ignores it
"exploiters": {}, # This is empty since MockExploiter ignores it
},
Event(),
)
exploit_telems = [t for t in telemetry_messenger_spy.telemetries if isinstance(t, ExploitTelem)]
assert len(exploit_telems) == 4
for t in exploit_telems:
data = t.get_data()
ip = data["machine"]["ip_addr"]
assert ip.endswith(".1") or ip.endswith(".3")
if ip.endswith(".1"):
if data["exploiter"].startswith("PowerShell"):
assert data["result"]
else:
assert not data["result"]
elif ip.endswith(".3"):
if data["exploiter"].startswith("PowerShell"):
assert not data["result"]
else:
assert data["result"]

View File

@ -101,8 +101,9 @@ def test_format_config_for_agent__propagation(flat_monkey_config):
ConfigService.format_flat_config_for_agent(flat_monkey_config) ConfigService.format_flat_config_for_agent(flat_monkey_config)
assert "propagation" in flat_monkey_config assert "propagation" in flat_monkey_config
assert "network_scan" in flat_monkey_config["propagation"]
assert "targets" in flat_monkey_config["propagation"] assert "targets" in flat_monkey_config["propagation"]
assert "network_scan" in flat_monkey_config["propagation"]
assert "exploiters" in flat_monkey_config["propagation"]
def test_format_config_for_agent__propagation_targets(flat_monkey_config): def test_format_config_for_agent__propagation_targets(flat_monkey_config):
@ -163,3 +164,31 @@ def test_format_config_for_agent__network_scan(flat_monkey_config):
assert "tcp_target_ports" not in flat_monkey_config assert "tcp_target_ports" not in flat_monkey_config
assert "ping_scan_timeout" not in flat_monkey_config assert "ping_scan_timeout" not in flat_monkey_config
assert "finger_classes" not in flat_monkey_config assert "finger_classes" not in flat_monkey_config
def test_format_config_for_agent__exploiters(flat_monkey_config):
expected_exploiters_config = {
"brute_force": [
{"name": "MSSQLExploiter", "propagator": True},
{"name": "PowerShellExploiter", "propagator": True},
{"name": "SSHExploiter", "propagator": True},
{"name": "SmbExploiter", "propagator": True},
{"name": "WmiExploiter", "propagator": True},
],
"vulnerability": [
{"name": "DrupalExploiter", "propagator": True},
{"name": "ElasticGroovyExploiter", "propagator": True},
{"name": "HadoopExploiter", "propagator": True},
{"name": "ShellShockExploiter", "propagator": True},
{"name": "Struts2Exploiter", "propagator": True},
{"name": "WebLogicExploiter", "propagator": True},
{"name": "ZerologonExploiter", "propagator": False},
],
}
ConfigService.format_flat_config_for_agent(flat_monkey_config)
assert "propagation" in flat_monkey_config
assert "exploiters" in flat_monkey_config["propagation"]
assert flat_monkey_config["propagation"]["exploiters"] == expected_exploiters_config
assert "exploiter_classes" not in flat_monkey_config