Merge pull request #1825 from guardicore/check-supported-os-for-exploiters

Check supported os for exploiters
This commit is contained in:
Mike Salvatore 2022-03-29 09:57:24 -04:00 committed by GitHub
commit 6937b1a5c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 151 additions and 66 deletions

View File

@ -15,8 +15,6 @@ logger = logging.getLogger(__name__)
class HostExploiter: class HostExploiter:
_TARGET_OS_TYPE = []
@property @property
@abstractmethod @abstractmethod
def _EXPLOITED_SERVICE(self): def _EXPLOITED_SERVICE(self):
@ -44,9 +42,6 @@ class HostExploiter:
def set_finish_time(self): def set_finish_time(self):
self.exploit_info["finished"] = datetime.now().isoformat() self.exploit_info["finished"] = datetime.now().isoformat()
def is_os_supported(self):
return self.host.os.get("type") in self._TARGET_OS_TYPE
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(
{ {

View File

@ -19,7 +19,6 @@ logger = logging.getLogger(__name__)
class DrupalExploiter(WebRCE): class DrupalExploiter(WebRCE):
_TARGET_OS_TYPE = ["linux", "windows"]
_EXPLOITED_SERVICE = "Drupal Server" _EXPLOITED_SERVICE = "Drupal Server"
def __init__(self, host): def __init__(self, host):

View File

@ -25,7 +25,6 @@ from infection_monkey.utils.commands import build_monkey_commandline
class HadoopExploiter(WebRCE): class HadoopExploiter(WebRCE):
_TARGET_OS_TYPE = ["linux", "windows"]
_EXPLOITED_SERVICE = "Hadoop" _EXPLOITED_SERVICE = "Hadoop"
HADOOP_PORTS = [("8088", False)] HADOOP_PORTS = [("8088", False)]
# How long we have our http server open for downloads in seconds # How long we have our http server open for downloads in seconds

View File

@ -27,7 +27,6 @@ logger = logging.getLogger(__name__)
class Log4ShellExploiter(WebRCE): class Log4ShellExploiter(WebRCE):
_TARGET_OS_TYPE = ["linux", "windows"]
_EXPLOITED_SERVICE = "Log4j" _EXPLOITED_SERVICE = "Log4j"
SERVER_SHUTDOWN_TIMEOUT = LONG_REQUEST_TIMEOUT SERVER_SHUTDOWN_TIMEOUT = LONG_REQUEST_TIMEOUT
REQUEST_TO_VICTIM_TIMEOUT = MEDIUM_REQUEST_TIMEOUT REQUEST_TO_VICTIM_TIMEOUT = MEDIUM_REQUEST_TIMEOUT

View File

@ -23,7 +23,6 @@ logger = logging.getLogger(__name__)
class MSSQLExploiter(HostExploiter): class MSSQLExploiter(HostExploiter):
_EXPLOITED_SERVICE = "MSSQL" _EXPLOITED_SERVICE = "MSSQL"
_TARGET_OS_TYPE = ["windows"]
LOGIN_TIMEOUT = LONG_REQUEST_TIMEOUT LOGIN_TIMEOUT = LONG_REQUEST_TIMEOUT
QUERY_TIMEOUT = LONG_REQUEST_TIMEOUT QUERY_TIMEOUT = LONG_REQUEST_TIMEOUT
# Time in seconds to wait between MSSQL queries. # Time in seconds to wait between MSSQL queries.

View File

@ -31,7 +31,6 @@ class RemoteAgentExecutionError(Exception):
class PowerShellExploiter(HostExploiter): class PowerShellExploiter(HostExploiter):
_TARGET_OS_TYPE = ["windows"]
_EXPLOITED_SERVICE = "PowerShell Remoting (WinRM)" _EXPLOITED_SERVICE = "PowerShell Remoting (WinRM)"
def __init__(self): def __init__(self):

View File

@ -21,7 +21,6 @@ logger = getLogger(__name__)
class SMBExploiter(HostExploiter): class SMBExploiter(HostExploiter):
_TARGET_OS_TYPE = ["windows"]
_EXPLOITED_SERVICE = "SMB" _EXPLOITED_SERVICE = "SMB"
KNOWN_PROTOCOLS = { KNOWN_PROTOCOLS = {
"139/SMB": (r"ncacn_np:%s[\pipe\svcctl]", 139), "139/SMB": (r"ncacn_np:%s[\pipe\svcctl]", 139),

View File

@ -31,7 +31,6 @@ TRANSFER_UPDATE_RATE = 15
class SSHExploiter(HostExploiter): class SSHExploiter(HostExploiter):
_TARGET_OS_TYPE = ["linux", None]
_EXPLOITED_SERVICE = "SSH" _EXPLOITED_SERVICE = "SSH"
def __init__(self): def __init__(self):

View File

@ -20,7 +20,6 @@ DOWNLOAD_TIMEOUT = 300
class Struts2Exploiter(WebRCE): class Struts2Exploiter(WebRCE):
_TARGET_OS_TYPE = ["linux", "windows"]
_EXPLOITED_SERVICE = "Struts2" _EXPLOITED_SERVICE = "Struts2"
def __init__(self, host): def __init__(self, host):

View File

@ -29,7 +29,6 @@ HEADERS = {
class WebLogicExploiter(HostExploiter): class WebLogicExploiter(HostExploiter):
_TARGET_OS_TYPE = ["linux", "windows"]
_EXPLOITED_SERVICE = "Weblogic" _EXPLOITED_SERVICE = "Weblogic"
def _exploit_host(self): def _exploit_host(self):
@ -58,7 +57,6 @@ class WebLogic201710271(WebRCE):
"/wls-wsat/RegistrationRequesterPortType11", "/wls-wsat/RegistrationRequesterPortType11",
] ]
_TARGET_OS_TYPE = WebLogicExploiter._TARGET_OS_TYPE
_EXPLOITED_SERVICE = WebLogicExploiter._EXPLOITED_SERVICE _EXPLOITED_SERVICE = WebLogicExploiter._EXPLOITED_SERVICE
def __init__(self, host): def __init__(self, host):
@ -257,7 +255,6 @@ class WebLogic20192725(WebRCE):
URLS = ["_async/AsyncResponseServiceHttps"] URLS = ["_async/AsyncResponseServiceHttps"]
DELAY_BEFORE_EXPLOITING_SECONDS = 5 DELAY_BEFORE_EXPLOITING_SECONDS = 5
_TARGET_OS_TYPE = WebLogicExploiter._TARGET_OS_TYPE
_EXPLOITED_SERVICE = WebLogicExploiter._EXPLOITED_SERVICE _EXPLOITED_SERVICE = WebLogicExploiter._EXPLOITED_SERVICE
def __init__(self, host): def __init__(self, host):

View File

@ -22,7 +22,6 @@ logger = logging.getLogger(__name__)
class WmiExploiter(HostExploiter): class WmiExploiter(HostExploiter):
_TARGET_OS_TYPE = ["windows"]
_EXPLOITED_SERVICE = "WMI (Windows Management Instrumentation)" _EXPLOITED_SERVICE = "WMI (Windows Management Instrumentation)"
@WmiTools.impacket_user @WmiTools.impacket_user

View File

@ -33,7 +33,6 @@ logger = logging.getLogger(__name__)
class ZerologonExploiter(HostExploiter): class ZerologonExploiter(HostExploiter):
_TARGET_OS_TYPE = ["windows"]
_EXPLOITED_SERVICE = "Netlogon" _EXPLOITED_SERVICE = "Netlogon"
MAX_ATTEMPTS = 2000 # For 2000, expected average number of attempts needed: 256. MAX_ATTEMPTS = 2000 # For 2000, expected average number of attempts needed: 256.
ERROR_CODE_ACCESS_DENIED = 0xC0000022 ERROR_CODE_ACCESS_DENIED = 0xC0000022

View File

@ -114,6 +114,16 @@ class Exploiter:
for exploiter in interruptible_iter(exploiters_to_run, stop): for exploiter in interruptible_iter(exploiters_to_run, stop):
exploiter_name = exploiter["name"] exploiter_name = exploiter["name"]
victim_os = victim_host.os.get("type")
# We want to try all exploiters if the victim's OS is unknown
if victim_os is not None and victim_os not in exploiter["supported_os"]:
logger.debug(
f"Skipping {exploiter_name} because it does not support "
f"the victim's OS ({victim_os})"
)
continue
exploiter_results = self._run_exploiter( exploiter_results = self._run_exploiter(
exploiter_name, exploiter["options"], victim_host, current_depth, stop exploiter_name, exploiter["options"], victim_host, current_depth, stop
) )

View File

@ -163,8 +163,8 @@ class MockPuppet(IPuppet):
"ssh_key": host, "ssh_key": host,
}, },
] ]
info_powershell = { info_wmi = {
"display_name": "PowerShell", "display_name": "WMI",
"started": "2021-11-25T15:57:06.307696", "started": "2021-11-25T15:57:06.307696",
"finished": "2021-11-25T15:58:33.788238", "finished": "2021-11-25T15:58:33.788238",
"vulnerable_urls": [], "vulnerable_urls": [],
@ -189,15 +189,15 @@ class MockPuppet(IPuppet):
successful_exploiters = { successful_exploiters = {
DOT_1: { DOT_1: {
"PowerShellExploiter": ExploiterResultData(
True, True, False, os_windows, info_powershell, attempts, None
),
"ZerologonExploiter": ExploiterResultData( "ZerologonExploiter": ExploiterResultData(
False, False, False, os_windows, {}, [], "Zerologon failed" False, False, False, os_windows, {}, [], "Zerologon failed"
), ),
"SSHExploiter": ExploiterResultData( "SSHExploiter": ExploiterResultData(
False, False, False, os_linux, info_ssh, attempts, "Failed exploiting" False, False, False, os_linux, info_ssh, attempts, "Failed exploiting"
), ),
"WmiExploiter": ExploiterResultData(
True, True, False, os_windows, info_wmi, attempts, None
),
}, },
DOT_3: { DOT_3: {
"PowerShellExploiter": ExploiterResultData( "PowerShellExploiter": ExploiterResultData(
@ -205,7 +205,7 @@ class MockPuppet(IPuppet):
False, False,
False, False,
os_windows, os_windows,
info_powershell, info_wmi,
attempts, attempts,
"PowerShell Exploiter Failed", "PowerShell Exploiter Failed",
), ),

View File

@ -3,6 +3,7 @@ import copy
import functools import functools
import logging import logging
import re import re
from itertools import chain
from typing import Any, Dict, List from typing import Any, Dict, List
from jsonschema import Draft4Validator, validators from jsonschema import Draft4Validator, validators
@ -629,9 +630,10 @@ class ConfigService:
config.pop(flat_config_exploiter_classes_field, None) config.pop(flat_config_exploiter_classes_field, None)
return ConfigService._add_smb_download_timeout_to_exploiters( formatted_exploiters_config = ConfigService._add_smb_download_timeout_to_exploiters(
config, formatted_exploiters_config config, formatted_exploiters_config
) )
return ConfigService._add_supported_os_to_exploiters(formatted_exploiters_config)
@staticmethod @staticmethod
def _add_smb_download_timeout_to_exploiters( def _add_smb_download_timeout_to_exploiters(
@ -644,3 +646,23 @@ class ConfigService:
exploiter["options"]["smb_download_timeout"] = flat_config["smb_download_timeout"] exploiter["options"]["smb_download_timeout"] = flat_config["smb_download_timeout"]
return new_config return new_config
@staticmethod
def _add_supported_os_to_exploiters(
formatted_config: Dict,
) -> Dict[str, List[Dict[str, Any]]]:
supported_os = {
"HadoopExploiter": ["linux", "windows"],
"Log4ShellExploiter": ["linux", "windows"],
"MSSQLExploiter": ["windows"],
"PowerShellExploiter": ["windows"],
"SSHExploiter": ["linux"],
"SmbExploiter": ["windows"],
"WmiExploiter": ["windows"],
"ZerologonExploiter": ["windows"],
}
new_config = copy.deepcopy(formatted_config)
for exploiter in chain(new_config["brute_force"], new_config["vulnerability"]):
exploiter["supported_os"] = supported_os.get(exploiter["name"], [])
return new_config

View File

@ -45,20 +45,21 @@
] ]
}, },
"exploiters": { "exploiters": {
"options": {},
"brute_force": [ "brute_force": [
{"name": "MSSQLExploiter"}, {"name": "MSSQLExploiter", "supported_os": ["windows"], "options": {}},
{"name": "PowerShellExploiter"}, {"name": "PowerShellExploiter", "supported_os": ["windows"], "options": {}},
{"name": "SmbExploiter"}, {"name": "SmbExploiter", "supported_os": ["windows"], "options": {}},
{"name": "SSHExploiter"}, {"name": "SSHExploiter", "supported_os": ["linux"], "options": {}},
{"name": "WmiExploiter"} {"name": "WmiExploiter", "supported_os": ["windows"], "options": {}}
], ],
"vulnerability": [ "vulnerability": [
{"name": "DrupalExploiter"}, {"name": "DrupalExploiter", "supported_os": ["linux", "windows"], "options": {}},
{"name": "HadoopExploiter"}, {"name": "HadoopExploiter", "supported_os": ["linux", "windows"], "options": {}},
{"name": "ShellShockExploiter"}, {"name": "ShellShockExploiter", "supported_os": ["linux"], "options": {}},
{"name": "Struts2Exploiter"}, {"name": "Struts2Exploiter", "supported_os": ["linux", "windows"], "options": {}},
{"name": "WebLogicExploiter"}, {"name": "WebLogicExploiter", "supported_os": ["linux", "windows"], "options": {}},
{"name": "ZerologonExploiter"} {"name": "ZerologonExploiter", "supported_os": ["windows"], "options": {}}
] ]
} }
}, },
@ -102,7 +103,7 @@
"other_behaviors": {"readme": true} "other_behaviors": {"readme": true}
} }
}, },
"system_info_collector_classes": [ "credential_collector_classes": [
"MimikatzCollector", "MimikatzCollector",
"SSHCollector" "SSHCollector"
] ]

View File

@ -55,7 +55,8 @@
"HadoopExploiter", "HadoopExploiter",
"MSSQLExploiter", "MSSQLExploiter",
"DrupalExploiter", "DrupalExploiter",
"PowerShellExploiter" "PowerShellExploiter",
"Log4ShellExploiter"
], ],
"export_monkey_telems": false, "export_monkey_telems": false,
"finger_classes": [ "finger_classes": [

View File

@ -1,6 +1,7 @@
import logging import logging
from queue import Queue from queue import Queue
from threading import Barrier, Event from threading import Barrier, Event
from typing import Iterable
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest import pytest
@ -37,29 +38,47 @@ def exploiter_config():
return { return {
"options": {"dropper_path_linux": "/tmp/monkey"}, "options": {"dropper_path_linux": "/tmp/monkey"},
"brute_force": [ "brute_force": [
{"name": "PowerShellExploiter", "options": {"timeout": 10}}, {"name": "HadoopExploiter", "supported_os": ["windows"], "options": {"timeout": 10}},
{"name": "SSHExploiter", "options": {}}, {"name": "SSHExploiter", "supported_os": ["linux"], "options": {}},
{"name": "WmiExploiter", "supported_os": ["windows"], "options": {"timeout": 10}},
], ],
"vulnerability": [ "vulnerability": [
{"name": "ZerologonExploiter", "options": {}}, {"name": "ZerologonExploiter", "supported_os": ["windows"], "options": {}},
], ],
} }
@pytest.fixture @pytest.fixture
def hosts(): def hosts():
return [VictimHost("10.0.0.1"), VictimHost("10.0.0.3")] host_1 = VictimHost("10.0.0.1")
host_2 = VictimHost("10.0.0.3")
return [host_1, host_2]
@pytest.fixture @pytest.fixture
def hosts_to_exploit(hosts): def hosts_to_exploit(hosts):
return enqueue_hosts(hosts)
def enqueue_hosts(hosts: Iterable[VictimHost]):
q = Queue() q = Queue()
q.put(hosts[0]) for h in hosts:
q.put(hosts[1]) q.put(h)
return q return q
def get_host_exploit_combos_from_call_args_list(call_args_list):
host_exploit_combos = set()
for call_args in call_args_list:
victim_host = call_args[0][0]
exploiter_name = call_args[0][1]
host_exploit_combos.add((victim_host, exploiter_name))
return host_exploit_combos
CREDENTIALS_FOR_PROPAGATION = {"usernames": ["m0nk3y", "user"], "passwords": ["1234", "pword"]} CREDENTIALS_FOR_PROPAGATION = {"usernames": ["m0nk3y", "user"], "passwords": ["1234", "pword"]}
@ -69,12 +88,12 @@ def get_credentials_for_propagation():
@pytest.fixture @pytest.fixture
def run_exploiters(exploiter_config, hosts_to_exploit, callback, scan_completed, stop): def run_exploiters(exploiter_config, hosts_to_exploit, callback, scan_completed, stop):
def inner(puppet, num_workers): def inner(puppet, num_workers, hosts=hosts_to_exploit):
# Set this so that Exploiter() exits once it has processed all victims # Set this so that Exploiter() exits once it has processed all victims
scan_completed.set() scan_completed.set()
e = Exploiter(puppet, num_workers, get_credentials_for_propagation) e = Exploiter(puppet, num_workers, get_credentials_for_propagation)
e.exploit_hosts(exploiter_config, hosts_to_exploit, 1, callback, scan_completed, stop) e.exploit_hosts(exploiter_config, hosts, 1, callback, scan_completed, stop)
return inner return inner
@ -82,18 +101,16 @@ def run_exploiters(exploiter_config, hosts_to_exploit, callback, scan_completed,
def test_exploiter(callback, hosts, hosts_to_exploit, run_exploiters): def test_exploiter(callback, hosts, hosts_to_exploit, run_exploiters):
run_exploiters(MockPuppet(), 2) run_exploiters(MockPuppet(), 2)
assert callback.call_count == 5 assert callback.call_count == 8
host_exploit_combos = set() host_exploit_combos = get_host_exploit_combos_from_call_args_list(callback.call_args_list)
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 ("ZerologonExploiter", hosts[0]) in host_exploit_combos
assert ("PowerShellExploiter", hosts[0]) in host_exploit_combos assert ("HadoopExploiter", hosts[0]) in host_exploit_combos
assert ("SSHExploiter", hosts[0]) in host_exploit_combos
assert ("WmiExploiter", hosts[0]) in host_exploit_combos
assert ("ZerologonExploiter", hosts[1]) in host_exploit_combos assert ("ZerologonExploiter", hosts[1]) in host_exploit_combos
assert ("PowerShellExploiter", hosts[1]) in host_exploit_combos assert ("HadoopExploiter", hosts[1]) in host_exploit_combos
assert ("WmiExploiter", hosts[1]) in host_exploit_combos
assert ("SSHExploiter", hosts[1]) in host_exploit_combos assert ("SSHExploiter", hosts[1]) in host_exploit_combos
@ -132,10 +149,53 @@ def test_exploiter_raises_exception(callback, hosts, hosts_to_exploit, run_explo
mock_puppet.exploit_host = MagicMock(side_effect=Exception(error_message)) mock_puppet.exploit_host = MagicMock(side_effect=Exception(error_message))
run_exploiters(mock_puppet, 3) run_exploiters(mock_puppet, 3)
assert callback.call_count == 6 assert callback.call_count == 8
for i in range(0, 6): for i in range(0, 6):
exploit_result_data = callback.call_args_list[i][0][2] exploit_result_data = callback.call_args_list[i][0][2]
assert exploit_result_data.exploitation_success is False assert exploit_result_data.exploitation_success is False
assert exploit_result_data.propagation_success is False assert exploit_result_data.propagation_success is False
assert error_message in exploit_result_data.error_message assert error_message in exploit_result_data.error_message
def test_windows_exploiters_run_on_windows_host(callback, hosts, hosts_to_exploit, run_exploiters):
host = VictimHost("10.0.0.1")
host.os["type"] = "windows"
q = enqueue_hosts([host])
run_exploiters(MockPuppet(), 1, q)
assert callback.call_count == 3
host_exploit_combos = get_host_exploit_combos_from_call_args_list(callback.call_args_list)
assert ("SSHExploiter", host) not in host_exploit_combos
def test_linux_exploiters_run_on_linux_host(callback, hosts, hosts_to_exploit, run_exploiters):
host = VictimHost("10.0.0.1")
host.os["type"] = "linux"
q = enqueue_hosts([host])
run_exploiters(MockPuppet(), 1, q)
assert callback.call_count == 1
host_exploit_combos = get_host_exploit_combos_from_call_args_list(callback.call_args_list)
assert ("SSHExploiter", host) in host_exploit_combos
def test_all_exploiters_run_on_unknown_host(callback, hosts, hosts_to_exploit, run_exploiters):
host = VictimHost("10.0.0.1")
try:
del host.os["type"]
except KeyError:
pass
q = enqueue_hosts([host])
run_exploiters(MockPuppet(), 1, q)
assert callback.call_count == 4
host_exploit_combos = get_host_exploit_combos_from_call_args_list(callback.call_args_list)
assert ("ZerologonExploiter", hosts[0]) in host_exploit_combos
assert ("HadoopExploiter", hosts[0]) in host_exploit_combos
assert ("SSHExploiter", host) in host_exploit_combos
assert ("WmiExploiter", hosts[0]) in host_exploit_combos

View File

@ -177,18 +177,27 @@ def test_format_config_for_agent__exploiters(flat_monkey_config):
"http_ports": [80, 443, 7001, 8008, 8080, 9200], "http_ports": [80, 443, 7001, 8008, 8080, 9200],
}, },
"brute_force": [ "brute_force": [
{"name": "MSSQLExploiter", "options": {}}, {"name": "MSSQLExploiter", "supported_os": ["windows"], "options": {}},
{"name": "PowerShellExploiter", "options": {}}, {"name": "PowerShellExploiter", "supported_os": ["windows"], "options": {}},
{"name": "SSHExploiter", "options": {}}, {"name": "SSHExploiter", "supported_os": ["linux"], "options": {}},
{"name": "SmbExploiter", "options": {"smb_download_timeout": 300}}, {
{"name": "WmiExploiter", "options": {"smb_download_timeout": 300}}, "name": "SmbExploiter",
"supported_os": ["windows"],
"options": {"smb_download_timeout": 300},
},
{
"name": "WmiExploiter",
"supported_os": ["windows"],
"options": {"smb_download_timeout": 300},
},
], ],
"vulnerability": [ "vulnerability": [
{"name": "DrupalExploiter", "options": {}}, {"name": "DrupalExploiter", "supported_os": [], "options": {}},
{"name": "HadoopExploiter", "options": {}}, {"name": "HadoopExploiter", "supported_os": ["linux", "windows"], "options": {}},
{"name": "Struts2Exploiter", "options": {}}, {"name": "Log4ShellExploiter", "supported_os": ["linux", "windows"], "options": {}},
{"name": "WebLogicExploiter", "options": {}}, {"name": "Struts2Exploiter", "supported_os": [], "options": {}},
{"name": "ZerologonExploiter", "options": {}}, {"name": "WebLogicExploiter", "supported_os": [], "options": {}},
{"name": "ZerologonExploiter", "supported_os": ["windows"], "options": {}},
], ],
} }
ConfigService.format_flat_config_for_agent(flat_monkey_config) ConfigService.format_flat_config_for_agent(flat_monkey_config)