Merge pull request #2134 from guardicore/2092-bb-tests

2092 bb tests
This commit is contained in:
VakarisZ 2022-08-01 12:09:52 +03:00 committed by GitHub
commit 759de2a055
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 218 additions and 70 deletions

View File

@ -1,6 +1,7 @@
from pprint import pformat from pprint import pformat
from typing import List from typing import List
from common.credentials import CredentialComponentType, Credentials
from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer
from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
@ -26,19 +27,23 @@ class ZerologonAnalyzer(Analyzer):
return is_creds_gathered and is_creds_restored return is_creds_gathered and is_creds_restored
def _analyze_credential_gathering(self) -> bool: def _analyze_credential_gathering(self) -> bool:
config = self.island_client.get_config() propagation_credentials = self.island_client.get_propagation_credentials()
credentials_on_island = ZerologonAnalyzer._get_relevant_credentials(config) credentials_on_island = ZerologonAnalyzer._get_relevant_credentials(propagation_credentials)
return self._is_all_credentials_in_list(credentials_on_island) return self._is_all_credentials_in_list(credentials_on_island)
@staticmethod @staticmethod
def _get_relevant_credentials(config: dict): def _get_relevant_credentials(propagation_credentials: Credentials) -> List[str]:
credentials_on_island = [] credentials_on_island = set()
# TODO: Pull configured credentials and put usernames, nt and lm hashes into
# credentials_island for credentials in propagation_credentials:
# credentials_on_island.extend(dpath.util.get(config["configuration"], USER_LIST_PATH)) if credentials.identity.credential_type is CredentialComponentType.USERNAME:
# credentials_on_island.extend(dpath.util.get(config["configuration"], NTLM_HASH_LIST_PATH)) credentials_on_island.update([credentials.identity.username])
# credentials_on_island.extend(dpath.util.get(config["configuration"], LM_HASH_LIST_PATH)) if credentials.secret.credential_type is CredentialComponentType.NT_HASH:
return credentials_on_island credentials_on_island.update([credentials.secret.nt_hash])
if credentials.secret.credential_type is CredentialComponentType.LM_HASH:
credentials_on_island.update([credentials.secret.lm_hash])
return list(credentials_on_island)
def _is_all_credentials_in_list(self, all_creds: List[str]) -> bool: def _is_all_credentials_in_list(self, all_creds: List[str]) -> bool:
credentials_missing = [cred for cred in self.expected_credentials if cred not in all_creds] credentials_missing = [cred for cred in self.expected_credentials if cred not in all_creds]

View File

@ -32,6 +32,11 @@ def no_gcp(request):
return request.config.getoption("--no-gcp") return request.config.getoption("--no-gcp")
@pytest.fixture(scope="session")
def machines_to_start(request):
return request.config.getoption("-k")
def pytest_runtest_setup(item): def pytest_runtest_setup(item):
if "skip_powershell_reuse" in item.keywords and item.config.getoption( if "skip_powershell_reuse" in item.keywords and item.config.getoption(
"--skip-powershell-reuse" "--skip-powershell-reuse"

View File

@ -26,3 +26,70 @@ GCP_TEST_MACHINE_LIST = {
"log4j-tomcat-52", "log4j-tomcat-52",
], ],
} }
DEPTH_2_A = {
"europe-west3-a": [
"sshkeys-11",
"sshkeys-12",
]
}
DEPTH_1_A = {
"europe-west3-a": ["hadoop-2", "hadoop-3", "mssql-16", "mimikatz-14", "mimikatz-15"],
"europe-west1-b": [
"log4j-logstash-55",
"log4j-logstash-56",
"log4j-solr-49",
"log4j-solr-50",
"log4j-tomcat-51",
"log4j-tomcat-52",
],
}
DEPTH_3_A = {
"europe-west3-a": [
"tunneling-9",
"tunneling-10",
"tunneling-11",
"tunneling-12",
"mimikatz-15",
],
"europe-west1-b": [
"powershell-3-45",
"powershell-3-46",
"powershell-3-47",
"powershell-3-48",
],
}
POWERSHELL_EXPLOITER_REUSE = {
"europe-west1-b": [
"powershell-3-46",
]
}
ZEROLOGON = {
"europe-west3-a": [
"zerologon-25",
],
}
WMI_AND_MIMIKATZ = {
"europe-west3-a": [
"mimikatz-14",
"mimikatz-15",
]
}
SMB_PTH = {"europe-west3-a": ["mimikatz-15"]}
GCP_SINGLE_TEST_LIST = {
"test_depth_2_a": DEPTH_2_A,
"test_depth_1_a": DEPTH_1_A,
"test_depth_3_a": DEPTH_3_A,
"test_powershell_exploiter_credentials_reuse": POWERSHELL_EXPLOITER_REUSE,
"test_zerologon_exploiter": ZEROLOGON,
"test_wmi_and_mimikatz_exploiters": WMI_AND_MIMIKATZ,
"test_smb_pth": SMB_PTH,
}

View File

@ -1,7 +1,7 @@
import json import json
import logging import logging
import time import time
from typing import Union from typing import Sequence, Union
from bson import json_util from bson import json_util
@ -29,23 +29,41 @@ class MonkeyIslandClient(object):
def get_api_status(self): def get_api_status(self):
return self.requests.get("api") return self.requests.get("api")
def get_config(self): def get_propagation_credentials(self) -> Sequence[Credentials]:
return json.loads(self.requests.get("api/agent-configuration").content) response = self.requests.get("api/propagation-credentials")
return [Credentials.from_mapping(credentials) for credentials in response.json()]
@avoid_race_condition @avoid_race_condition
def import_config(self, test_configuration: TestConfiguration): def import_config(self, test_configuration: TestConfiguration):
self.requests.post_json( self._import_config(test_configuration)
self._import_credentials(test_configuration.propagation_credentials)
@avoid_race_condition
def _import_config(self, test_configuration: TestConfiguration):
response = self.requests.post_json(
"api/agent-configuration", "api/agent-configuration",
json=AgentConfiguration.to_mapping(test_configuration.agent_configuration), json=AgentConfiguration.to_mapping(test_configuration.agent_configuration),
) )
if response.ok:
LOGGER.info("Configuration is imported.")
else:
LOGGER.error(f"Failed to import config: {response}")
assert False
@avoid_race_condition
def _import_credentials(self, propagation_credentials: Credentials):
serialized_propagation_credentials = [ serialized_propagation_credentials = [
Credentials.to_mapping(credentials) Credentials.to_mapping(credentials) for credentials in propagation_credentials
for credentials in test_configuration.propagation_credentials
] ]
self.requests.post_json( response = self.requests.post_json(
"/api/propagation-credentials/configured-credentials", "/api/propagation-credentials/configured-credentials",
json=serialized_propagation_credentials, json=serialized_propagation_credentials,
) )
if response.ok:
LOGGER.info("Credentials are imported.")
else:
LOGGER.error(f"Failed to import credentials: {response}")
assert False
@avoid_race_condition @avoid_race_condition
def run_monkey_local(self): def run_monkey_local(self):

View File

@ -6,7 +6,10 @@ import pytest
from envs.monkey_zoo.blackbox.analyzers.communication_analyzer import CommunicationAnalyzer from envs.monkey_zoo.blackbox.analyzers.communication_analyzer import CommunicationAnalyzer
from envs.monkey_zoo.blackbox.analyzers.zerologon_analyzer import ZerologonAnalyzer from envs.monkey_zoo.blackbox.analyzers.zerologon_analyzer import ZerologonAnalyzer
from envs.monkey_zoo.blackbox.gcp_test_machine_list import GCP_TEST_MACHINE_LIST from envs.monkey_zoo.blackbox.gcp_test_machine_list import (
GCP_SINGLE_TEST_LIST,
GCP_TEST_MACHINE_LIST,
)
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
from envs.monkey_zoo.blackbox.island_client.test_configuration_parser import get_target_ips from envs.monkey_zoo.blackbox.island_client.test_configuration_parser import get_target_ips
from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler
@ -35,18 +38,21 @@ LOGGER = logging.getLogger(__name__)
@pytest.fixture(autouse=True, scope="session") @pytest.fixture(autouse=True, scope="session")
def GCPHandler(request, no_gcp): def GCPHandler(request, no_gcp, machines_to_start):
if not no_gcp: if not no_gcp:
list_machines = GCP_TEST_MACHINE_LIST
if machines_to_start:
list_machines = GCP_SINGLE_TEST_LIST[machines_to_start]
try: try:
initialize_gcp_client() initialize_gcp_client()
start_machines(GCP_TEST_MACHINE_LIST) start_machines(list_machines)
except Exception as e: except Exception as e:
LOGGER.error("GCP Handler failed to initialize: %s." % e) LOGGER.error("GCP Handler failed to initialize: %s." % e)
pytest.exit("Encountered an error while starting GCP machines. Stopping the tests.") pytest.exit("Encountered an error while starting GCP machines. Stopping the tests.")
wait_machine_bootup() wait_machine_bootup()
def fin(): def fin():
stop_machines(GCP_TEST_MACHINE_LIST) stop_machines(list_machines)
request.addfinalizer(fin) request.addfinalizer(fin)

View File

@ -5,6 +5,7 @@ from .noop import noop_test_configuration
from .utils import ( from .utils import (
add_credential_collectors, add_credential_collectors,
add_exploiters, add_exploiters,
add_fingerprinters,
add_http_ports, add_http_ports,
add_subnets, add_subnets,
add_tcp_ports, add_tcp_ports,
@ -34,6 +35,12 @@ def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfigurati
return add_exploiters(agent_configuration, brute_force=brute_force, vulnerability=vulnerability) return add_exploiters(agent_configuration, brute_force=brute_force, vulnerability=vulnerability)
def _add_fingerprinters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
fingerprinters = [PluginConfiguration(name="http", options={})]
return add_fingerprinters(agent_configuration, fingerprinters)
def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration: def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
subnets = [ subnets = [
"10.2.2.2", "10.2.2.2",
@ -69,18 +76,17 @@ def _add_http_ports(agent_configuration: AgentConfiguration) -> AgentConfigurati
return add_http_ports(agent_configuration, HTTP_PORTS) return add_http_ports(agent_configuration, HTTP_PORTS)
agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 1) test_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 1)
agent_configuration = _add_exploiters(agent_configuration) test_configuration = _add_exploiters(test_configuration)
agent_configuration = _add_subnets(agent_configuration) test_configuration = _add_fingerprinters(test_configuration)
agent_configuration = _add_tcp_ports(agent_configuration) test_configuration = _add_subnets(test_configuration)
agent_configuration = _add_credential_collectors(agent_configuration) test_configuration = _add_tcp_ports(test_configuration)
agent_configuration = _add_http_ports(agent_configuration) test_configuration = _add_credential_collectors(test_configuration)
test_configuration = _add_http_ports(test_configuration)
depth_1_a_test_configuration = replace_agent_configuration( depth_1_a_test_configuration = replace_agent_configuration(
noop_test_configuration, agent_configuration noop_test_configuration, test_configuration
) )
CREDENTIALS = ( CREDENTIALS = (
Credentials(Username("m0nk3y"), None), Credentials(Username("m0nk3y"), None),
Credentials(None, Password("Ivrrw5zEzs")), Credentials(None, Password("Ivrrw5zEzs")),

View File

@ -34,13 +34,13 @@ def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguratio
return add_tcp_ports(agent_configuration, ports) return add_tcp_ports(agent_configuration, ports)
agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 2) test_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 2)
agent_configuration = _add_exploiters(agent_configuration) test_configuration = _add_exploiters(test_configuration)
agent_configuration = _add_subnets(agent_configuration) test_configuration = _add_subnets(test_configuration)
agent_configuration = _add_tcp_ports(agent_configuration) test_configuration = _add_tcp_ports(test_configuration)
depth_2_a_test_configuration = replace_agent_configuration( depth_2_a_test_configuration = replace_agent_configuration(
noop_test_configuration, agent_configuration noop_test_configuration, test_configuration
) )

View File

@ -48,14 +48,14 @@ def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguratio
return add_tcp_ports(agent_configuration, ports) return add_tcp_ports(agent_configuration, ports)
agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 3) test_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 3)
agent_configuration = set_keep_tunnel_open_time(noop_test_configuration.agent_configuration, 20) test_configuration = set_keep_tunnel_open_time(test_configuration, 20)
agent_configuration = _add_exploiters(agent_configuration) test_configuration = _add_exploiters(test_configuration)
agent_configuration = _add_subnets(agent_configuration) test_configuration = _add_subnets(test_configuration)
agent_configuration = _add_tcp_ports(agent_configuration) test_configuration = _add_tcp_ports(test_configuration)
depth_3_a_test_configuration = replace_agent_configuration( depth_3_a_test_configuration = replace_agent_configuration(
noop_test_configuration, agent_configuration noop_test_configuration, test_configuration
) )

View File

@ -30,11 +30,11 @@ def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguratio
return add_tcp_ports(agent_configuration, ports) return add_tcp_ports(agent_configuration, ports)
agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 1) test_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 1)
agent_configuration = _add_exploiters(agent_configuration) test_configuration = _add_exploiters(test_configuration)
agent_configuration = _add_subnets(agent_configuration) test_configuration = _add_subnets(test_configuration)
agent_configuration = _add_tcp_ports(agent_configuration) test_configuration = _add_tcp_ports(test_configuration)
powershell_credentials_reuse_test_configuration = replace_agent_configuration( powershell_credentials_reuse_test_configuration = replace_agent_configuration(
noop_test_configuration, agent_configuration noop_test_configuration, test_configuration
) )

View File

@ -33,14 +33,14 @@ def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguratio
return add_tcp_ports(agent_configuration, ports) return add_tcp_ports(agent_configuration, ports)
agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 3) test_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 3)
agent_configuration = set_keep_tunnel_open_time(agent_configuration, 20) test_configuration = set_keep_tunnel_open_time(test_configuration, 20)
agent_configuration = _add_exploiters(agent_configuration) test_configuration = _add_exploiters(test_configuration)
agent_configuration = _add_subnets(agent_configuration) test_configuration = _add_subnets(test_configuration)
agent_configuration = _add_tcp_ports(agent_configuration) test_configuration = _add_tcp_ports(test_configuration)
smb_pth_test_configuration = replace_agent_configuration( smb_pth_test_configuration = replace_agent_configuration(
noop_test_configuration, agent_configuration noop_test_configuration, test_configuration
) )

View File

@ -28,6 +28,16 @@ def add_exploiters(
return replace_exploitation_configuration(agent_configuration, exploitation_configuration) return replace_exploitation_configuration(agent_configuration, exploitation_configuration)
def add_fingerprinters(
agent_configuration: AgentConfiguration, fingerprinters: Sequence[PluginConfiguration]
) -> AgentConfiguration:
network_scan_configuration = replace(
agent_configuration.propagation.network_scan, fingerprinters=fingerprinters
)
return replace_network_scan_configuration(agent_configuration, network_scan_configuration)
def add_tcp_ports( def add_tcp_ports(
agent_configuration: AgentConfiguration, tcp_ports: Sequence[int] agent_configuration: AgentConfiguration, tcp_ports: Sequence[int]
) -> AgentConfiguration: ) -> AgentConfiguration:

View File

@ -40,15 +40,15 @@ def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguratio
return add_tcp_ports(agent_configuration, ports) return add_tcp_ports(agent_configuration, ports)
agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 1) test_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 1)
agent_configuration = _add_exploiters(agent_configuration) test_configuration = _add_exploiters(test_configuration)
agent_configuration = _add_subnets(agent_configuration) test_configuration = _add_subnets(test_configuration)
agent_configuration = _add_credential_collectors(agent_configuration) test_configuration = _add_credential_collectors(test_configuration)
agent_configuration = _add_tcp_ports(agent_configuration) test_configuration = _add_tcp_ports(test_configuration)
agent_configuration = _add_credential_collectors(agent_configuration) test_configuration = _add_credential_collectors(test_configuration)
wmi_mimikatz_test_configuration = replace_agent_configuration( wmi_mimikatz_test_configuration = replace_agent_configuration(
noop_test_configuration, agent_configuration noop_test_configuration, test_configuration
) )

View File

@ -27,11 +27,11 @@ def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
return add_subnets(agent_configuration, subnets) return add_subnets(agent_configuration, subnets)
agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 1) test_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 1)
agent_configuration = _add_exploiters(agent_configuration) test_configuration = _add_exploiters(test_configuration)
agent_configuration = _add_tcp_ports(agent_configuration) test_configuration = _add_tcp_ports(test_configuration)
agent_configuration = _add_subnets(agent_configuration) test_configuration = _add_subnets(test_configuration)
zerologon_test_configuration = replace_agent_configuration( zerologon_test_configuration = replace_agent_configuration(
noop_test_configuration, agent_configuration noop_test_configuration, test_configuration
) )

View File

@ -285,7 +285,12 @@ class ZerologonExploiter(HostExploiter):
self, user: str, lmhash: str, nthash: str self, user: str, lmhash: str, nthash: str
) -> None: ) -> None:
self.telemetry_messenger.send_telemetry( self.telemetry_messenger.send_telemetry(
CredentialsTelem([Credentials([Username(user)], [LMHash(lmhash), NTHash(nthash)])]) CredentialsTelem(
[
Credentials(Username(user), LMHash(lmhash)),
Credentials(Username(user), NTHash(nthash)),
]
)
) )
def get_original_pwd_nthash(self, username: str, user_pwd_hashes: List[str]) -> str: def get_original_pwd_nthash(self, username: str, user_pwd_hashes: List[str]) -> str:

View File

@ -1,7 +1,8 @@
import logging import logging
from dataclasses import replace
from queue import Queue from queue import Queue
from threading import Event from threading import Event
from typing import List from typing import List, Sequence
from common.agent_configuration import ( from common.agent_configuration import (
ExploitationConfiguration, ExploitationConfiguration,
@ -53,10 +54,14 @@ class Propagator:
network_scan_completed = Event() network_scan_completed = Event()
self._hosts_to_exploit = Queue() self._hosts_to_exploit = Queue()
network_scan = self._add_http_ports_to_fingerprinters(
propagation_config.network_scan, propagation_config.exploitation.options.http_ports
)
scan_thread = create_daemon_thread( scan_thread = create_daemon_thread(
target=self._scan_network, target=self._scan_network,
name="PropagatorScanThread", name="PropagatorScanThread",
args=(propagation_config.network_scan, stop), args=(network_scan, stop),
) )
exploit_thread = create_daemon_thread( exploit_thread = create_daemon_thread(
target=self._exploit_hosts, target=self._exploit_hosts,
@ -74,6 +79,23 @@ class Propagator:
logger.info("Finished attempting to propagate") logger.info("Finished attempting to propagate")
@staticmethod
def _add_http_ports_to_fingerprinters(
network_scan: NetworkScanConfiguration, http_ports: Sequence[int]
) -> NetworkScanConfiguration:
# This is a hack to add http_ports to the options of fingerprinters
# It will be reworked. See https://github.com/guardicore/monkey/issues/2136
modified_fingerprinters = [*network_scan.fingerprinters]
for i, fingerprinter in enumerate(modified_fingerprinters):
if fingerprinter.name != "http":
continue
modified_options = fingerprinter.options.copy()
modified_options["http_ports"] = list(http_ports)
modified_fingerprinters[i] = replace(fingerprinter, options=modified_options)
return replace(network_scan, fingerprinters=modified_fingerprinters)
def _scan_network(self, scan_config: NetworkScanConfiguration, stop: Event): def _scan_network(self, scan_config: NetworkScanConfiguration, stop: Event):
logger.info("Starting network scan") logger.info("Starting network scan")

View File

@ -31,7 +31,7 @@ def censor_password(password, plain_chars=3, secret_chars=5):
""" """
if not password: if not password:
return "" return ""
password = get_datastore_encryptor().decrypt(password) password = get_datastore_encryptor().decrypt(password.encode()).decode()
return password[0:plain_chars] + "*" * secret_chars return password[0:plain_chars] + "*" * secret_chars
@ -45,5 +45,5 @@ def censor_hash(str_hash, plain_chars=5):
""" """
if not str_hash: if not str_hash:
return "" return ""
str_hash = get_datastore_encryptor().decrypt(str_hash) str_hash = get_datastore_encryptor().decrypt(str_hash.encode()).decode()
return str_hash[0:plain_chars] + " ..." return str_hash[0:plain_chars] + " ..."

View File

@ -22,6 +22,8 @@ class APIEncoder(JSONEncoder):
return value.name return value.name
if issubclass(type(value), IJSONSerializable): if issubclass(type(value), IJSONSerializable):
return loads(value.__class__.to_json(value)) return loads(value.__class__.to_json(value))
if issubclass(type(value), set):
return list(value)
try: try:
return JSONEncoder.default(self, value) return JSONEncoder.default(self, value)
except TypeError: except TypeError:

View File

@ -52,4 +52,6 @@ def encrypt_exploit_creds(telemetry_json):
credential = attempts[i][field] credential = attempts[i][field]
if credential: # PowerShell exploiter's telem may have `None` here if credential: # PowerShell exploiter's telem may have `None` here
if len(credential) > 0: if len(credential) > 0:
attempts[i][field] = get_datastore_encryptor().encrypt(credential) attempts[i][field] = (
get_datastore_encryptor().encrypt(credential.encode()).decode()
)