-
Notifications
You must be signed in to change notification settings - Fork 795
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
1597 implement exploitation #1657
Changes from all commits
93d0bb6
09305bc
eb7612d
1e02286
3394629
bda192e
b466a17
da61451
6c1caa1
4b3984d
fc767e2
f1b55b7
a6bb81e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
from .ip_scan_results import IPScanResults | ||
from .ip_scanner import IPScanner | ||
from .exploiter import Exploiter | ||
from .propagator import Propagator | ||
from .automated_master import AutomatedMaster |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
mssalvatore marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe it's worth considering creating the stop event in the agent. Then, puppet and master will be created with it. This way we wouldn't need to pass stop through the parameters everywhere. This would de-couple stopping logic from execution logic a bit, for better or worse. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would leak too much information into the agent, give the agent the ability to stop parts of the master that it shouldn't, and limit how the master is able to control functionality. The stop signal is something that the master can control per-run of the Propagator. The master controls the Propagator, the Propagator controls the scanner and exploiter. Giving the higher-level abstractions the ability to skip layers and control the lower-level abstractions increases complexity and coupling. |
||
|
||
|
||
def _all_hosts_have_been_processed(scan_completed: Event, hosts_to_exploit: Queue): | ||
return scan_completed.is_set() and hosts_to_exploit.empty() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will get done when we integrate the automated master with |
||
""" | ||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same consideration as before: each layer of logic contains the same
This logic is not specific to the
Exploiter
, it's an exception logic. Maybe there's a way to raise an exception whenstop.is_set()
instead. Maybe this can be solved with an interface/re-usable decoratorThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A decorator won't work because the
Event
needs to be available on import, but we could potentially add a utility function that handles this. We're going to need it for the plugins as well.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the moment I'm going to leave it. Each loop is using it in a slightly different way, or has slightly different requirements. As we accumulate more, similar loops, we can find the common patterns and make them reusable. At the moment I think it may be too early to do so.