forked from p15670423/monkey
Merge pull request #1022 from guardicore/zerologon-bb-test
Zerologon BB test
This commit is contained in:
commit
a132881ccc
|
@ -4,5 +4,5 @@ from abc import ABCMeta, abstractmethod
|
|||
class Analyzer(object, metaclass=ABCMeta):
|
||||
|
||||
@abstractmethod
|
||||
def analyze_test_results(self):
|
||||
def analyze_test_results(self) -> bool:
|
||||
raise NotImplementedError()
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
from typing import List
|
||||
from pprint import pformat
|
||||
|
||||
import dpath.util
|
||||
|
||||
from common.config_value_paths import USER_LIST_PATH, PASSWORD_LIST_PATH, NTLM_HASH_LIST_PATH, LM_HASH_LIST_PATH
|
||||
from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer
|
||||
from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog
|
||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||
|
||||
# Query for telemetry collection to see if password restoration was successful
|
||||
TELEM_QUERY = {'telem_category': 'exploit',
|
||||
'data.exploiter': 'ZerologonExploiter',
|
||||
'data.info.password_restored': True}
|
||||
|
||||
|
||||
class ZerologonAnalyzer(Analyzer):
|
||||
|
||||
def __init__(self, island_client: MonkeyIslandClient, expected_credentials: List[str]):
|
||||
self.island_client = island_client
|
||||
self.expected_credentials = expected_credentials
|
||||
self.log = AnalyzerLog(self.__class__.__name__)
|
||||
|
||||
def analyze_test_results(self):
|
||||
self.log.clear()
|
||||
is_creds_gathered = self._analyze_credential_gathering()
|
||||
is_creds_restored = self._analyze_credential_restore()
|
||||
return is_creds_gathered and is_creds_restored
|
||||
|
||||
def _analyze_credential_gathering(self) -> bool:
|
||||
config = self.island_client.get_config()
|
||||
credentials_on_island = ZerologonAnalyzer._get_relevant_credentials(config)
|
||||
return self._is_all_credentials_in_list(credentials_on_island)
|
||||
|
||||
@staticmethod
|
||||
def _get_relevant_credentials(config: dict):
|
||||
credentials_on_island = []
|
||||
credentials_on_island.extend(dpath.util.get(config['configuration'], USER_LIST_PATH))
|
||||
credentials_on_island.extend(dpath.util.get(config['configuration'], NTLM_HASH_LIST_PATH))
|
||||
credentials_on_island.extend(dpath.util.get(config['configuration'], LM_HASH_LIST_PATH))
|
||||
return credentials_on_island
|
||||
|
||||
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]
|
||||
self._log_creds_not_gathered(credentials_missing)
|
||||
return not credentials_missing
|
||||
|
||||
def _log_creds_not_gathered(self, missing_creds: List[str]):
|
||||
if not missing_creds:
|
||||
self.log.add_entry("Zerologon exploiter gathered all credentials expected.")
|
||||
else:
|
||||
for cred in missing_creds:
|
||||
self.log.add_entry(f"Credential Zerologon exploiter failed to gathered:{cred}.")
|
||||
|
||||
def _analyze_credential_restore(self) -> bool:
|
||||
cred_restore_telems = self.island_client.find_telems_in_db(TELEM_QUERY)
|
||||
self._log_credential_restore(cred_restore_telems)
|
||||
return bool(cred_restore_telems)
|
||||
|
||||
def _log_credential_restore(self, telem_list: List[dict]):
|
||||
if telem_list:
|
||||
self.log.add_entry("Zerologon exploiter telemetry contains indicators that credentials "
|
||||
"were successfully restored.")
|
||||
else:
|
||||
self.log.add_entry("Credential restore failed or credential restore "
|
||||
"telemetry not found on the Monkey Island.")
|
||||
self.log.add_entry(f"Query for credential restore telem: {pformat(TELEM_QUERY)}")
|
||||
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import json
|
||||
import logging
|
||||
from time import sleep
|
||||
from typing import Union
|
||||
|
||||
from bson import json_util
|
||||
|
||||
|
@ -8,6 +9,7 @@ from envs.monkey_zoo.blackbox.island_client.monkey_island_requests import Monkey
|
|||
|
||||
SLEEP_BETWEEN_REQUESTS_SECONDS = 0.5
|
||||
MONKEY_TEST_ENDPOINT = 'api/test/monkey'
|
||||
TELEMETRY_TEST_ENDPOINT = 'api/test/telemetry'
|
||||
LOG_TEST_ENDPOINT = 'api/test/log'
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -67,6 +69,13 @@ class MonkeyIslandClient(object):
|
|||
MonkeyIslandClient.form_find_query_for_request(query))
|
||||
return MonkeyIslandClient.get_test_query_results(response)
|
||||
|
||||
def find_telems_in_db(self, query: dict):
|
||||
if query is None:
|
||||
raise TypeError
|
||||
response = self.requests.get(TELEMETRY_TEST_ENDPOINT,
|
||||
MonkeyIslandClient.form_find_query_for_request(query))
|
||||
return MonkeyIslandClient.get_test_query_results(response)
|
||||
|
||||
def get_all_monkeys_from_db(self):
|
||||
response = self.requests.get(MONKEY_TEST_ENDPOINT,
|
||||
MonkeyIslandClient.form_find_query_for_request(None))
|
||||
|
@ -78,7 +87,7 @@ class MonkeyIslandClient(object):
|
|||
return MonkeyIslandClient.get_test_query_results(response)
|
||||
|
||||
@staticmethod
|
||||
def form_find_query_for_request(query):
|
||||
def form_find_query_for_request(query: Union[dict, None]) -> dict:
|
||||
return {'find_query': json_util.dumps(query)}
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
from copy import copy
|
||||
|
||||
from envs.monkey_zoo.blackbox.island_configs.base_template import BaseTemplate
|
||||
|
||||
|
||||
class Zerologon(BaseTemplate):
|
||||
|
||||
config_values = copy(BaseTemplate.config_values)
|
||||
|
||||
config_values.update({
|
||||
"basic.exploiters.exploiter_classes": ["ZerologonExploiter"],
|
||||
"basic_network.scope.subnet_scan_list": ["10.2.2.25"],
|
||||
# Empty list to make sure ZeroLogon adds "Administrator" username
|
||||
"basic.credentials.exploit_user_list": []
|
||||
})
|
|
@ -7,6 +7,7 @@ from typing_extensions import Type
|
|||
|
||||
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.island_client.island_config_parser import \
|
||||
IslandConfigParser
|
||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import \
|
||||
|
@ -25,6 +26,7 @@ from envs.monkey_zoo.blackbox.island_configs.tunneling import Tunneling
|
|||
from envs.monkey_zoo.blackbox.island_configs.weblogic import Weblogic
|
||||
from envs.monkey_zoo.blackbox.island_configs.wmi_mimikatz import WmiMimikatz
|
||||
from envs.monkey_zoo.blackbox.island_configs.wmi_pth import WmiPth
|
||||
from envs.monkey_zoo.blackbox.island_configs.zerologon import Zerologon
|
||||
from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import \
|
||||
TestLogsHandler
|
||||
from envs.monkey_zoo.blackbox.tests.exploitation import ExploitationTest
|
||||
|
@ -160,6 +162,22 @@ class TestMonkeyBlackbox:
|
|||
def test_wmi_pth(self, island_client):
|
||||
TestMonkeyBlackbox.run_exploitation_test(island_client, WmiPth, "WMI_PTH")
|
||||
|
||||
def test_zerologon_exploiter(self, island_client):
|
||||
test_name = "Zerologon_exploiter"
|
||||
expected_creds = ["Administrator",
|
||||
"aad3b435b51404eeaad3b435b51404ee",
|
||||
"2864b62ea4496934a5d6e86f50b834a5"]
|
||||
raw_config = IslandConfigParser.get_raw_config(Zerologon, island_client)
|
||||
analyzer = ZerologonAnalyzer(island_client, expected_creds)
|
||||
log_handler = TestLogsHandler(test_name, island_client, TestMonkeyBlackbox.get_log_dir_path())
|
||||
ExploitationTest(
|
||||
name=test_name,
|
||||
island_client=island_client,
|
||||
raw_config=raw_config,
|
||||
analyzers=[analyzer],
|
||||
timeout=DEFAULT_TIMEOUT_SECONDS,
|
||||
log_handler=log_handler).run()
|
||||
|
||||
@pytest.mark.skip(reason="Perfomance test that creates env from fake telemetries is faster, use that instead.")
|
||||
def test_report_generation_performance(self, island_client, quick_performance_tests):
|
||||
"""
|
||||
|
|
|
@ -7,6 +7,7 @@ from werkzeug.exceptions import NotFound
|
|||
|
||||
import monkey_island.cc.environment.environment_singleton as env_singleton
|
||||
from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH
|
||||
from monkey_island.cc.resources.test.telemetry_test import TelemetryTest
|
||||
from monkey_island.cc.resources.zero_trust.zero_trust_report import ZeroTrustReport
|
||||
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
|
||||
from monkey_island.cc.server_utils.custom_json_encoder import CustomJSONEncoder
|
||||
|
@ -145,9 +146,11 @@ def init_api_resources(api):
|
|||
api.add_resource(ScoutSuiteAuth, '/api/scoutsuite_auth/<string:provider>')
|
||||
api.add_resource(AWSKeys, '/api/aws_keys')
|
||||
|
||||
# Resources used by black box tests
|
||||
api.add_resource(MonkeyTest, '/api/test/monkey')
|
||||
api.add_resource(ClearCaches, '/api/test/clear_caches')
|
||||
api.add_resource(LogTest, '/api/test/log')
|
||||
api.add_resource(TelemetryTest, '/api/test/telemetry')
|
||||
|
||||
|
||||
def init_app(mongo_url):
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import flask_restful
|
||||
from bson import json_util
|
||||
from flask import request
|
||||
|
||||
from monkey_island.cc.database import mongo
|
||||
from monkey_island.cc.resources.auth.auth import jwt_required
|
||||
|
||||
|
||||
class TelemetryTest(flask_restful.Resource):
|
||||
@jwt_required
|
||||
def get(self, **kw):
|
||||
find_query = json_util.loads(request.args.get('find_query'))
|
||||
return {'results': list(mongo.db.telemetry.find(find_query))}
|
|
@ -4,7 +4,7 @@ from monkey_island.cc.services.config import ConfigService
|
|||
|
||||
__author__ = "VakarisZ"
|
||||
|
||||
from monkey_island.cc.services.config_schema.config_value_paths import CURRENT_SERVER_PATH
|
||||
from common.config_value_paths import CURRENT_SERVER_PATH
|
||||
|
||||
|
||||
class T1065(AttackTechnique):
|
||||
|
|
|
@ -14,10 +14,10 @@ from monkey_island.cc.services.config_schema.config_schema import SCHEMA
|
|||
|
||||
__author__ = "itay.mizeretz"
|
||||
|
||||
from monkey_island.cc.services.config_schema.config_value_paths import (AWS_KEYS_PATH, EXPORT_MONKEY_TELEMS_PATH,
|
||||
LM_HASH_LIST_PATH, NTLM_HASH_LIST_PATH,
|
||||
PASSWORD_LIST_PATH, SSH_KEYS_PATH,
|
||||
STARTED_ON_ISLAND_PATH, USER_LIST_PATH)
|
||||
from common.config_value_paths import (AWS_KEYS_PATH, EXPORT_MONKEY_TELEMS_PATH,
|
||||
LM_HASH_LIST_PATH, NTLM_HASH_LIST_PATH,
|
||||
PASSWORD_LIST_PATH, SSH_KEYS_PATH,
|
||||
STARTED_ON_ISLAND_PATH, USER_LIST_PATH)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from monkey_island.cc.services.config import ConfigService
|
||||
from monkey_island.cc.services.config_schema.config_value_paths import INACCESSIBLE_SUBNETS_PATH
|
||||
from common.config_value_paths import INACCESSIBLE_SUBNETS_PATH
|
||||
|
||||
|
||||
def get_config_network_segments_as_subnet_groups():
|
||||
|
|
|
@ -12,9 +12,9 @@ from monkey_island.cc.database import mongo
|
|||
from monkey_island.cc.models import Monkey
|
||||
from monkey_island.cc.services.utils.network_utils import get_subnets, local_ip_addresses
|
||||
from monkey_island.cc.services.config import ConfigService
|
||||
from monkey_island.cc.services.config_schema.config_value_paths import (EXPLOITER_CLASSES_PATH, LOCAL_NETWORK_SCAN_PATH,
|
||||
PASSWORD_LIST_PATH, SUBNET_SCAN_LIST_PATH,
|
||||
USER_LIST_PATH)
|
||||
from common.config_value_paths import (EXPLOITER_CLASSES_PATH, LOCAL_NETWORK_SCAN_PATH,
|
||||
PASSWORD_LIST_PATH, SUBNET_SCAN_LIST_PATH,
|
||||
USER_LIST_PATH)
|
||||
from monkey_island.cc.services.configuration.utils import get_config_network_segments_as_subnet_groups
|
||||
from monkey_island.cc.services.node import NodeService
|
||||
from monkey_island.cc.services.reporting.pth_report import PTHReportService
|
||||
|
|
|
@ -6,7 +6,7 @@ from common.cloud.scoutsuite_consts import CloudProviders
|
|||
from common.utils.exceptions import InvalidAWSKeys
|
||||
from monkey_island.cc.server_utils.encryptor import encryptor
|
||||
from monkey_island.cc.services.config import ConfigService
|
||||
from monkey_island.cc.services.config_schema.config_value_paths import AWS_KEYS_PATH
|
||||
from common.config_value_paths import AWS_KEYS_PATH
|
||||
|
||||
|
||||
def is_cloud_authentication_setup(provider: CloudProviders) -> Tuple[bool, str]:
|
||||
|
|
|
@ -6,7 +6,7 @@ import dpath.util
|
|||
from monkey_island.cc.database import mongo
|
||||
from monkey_island.cc.server_utils import encryptor
|
||||
from monkey_island.cc.services.config import ConfigService
|
||||
from monkey_island.cc.services.config_schema.config_value_paths import AWS_KEYS_PATH
|
||||
from common.config_value_paths import AWS_KEYS_PATH
|
||||
from monkey_island.cc.services.zero_trust.scoutsuite.scoutsuite_auth_service import is_aws_keys_setup
|
||||
from monkey_island.cc.test_common.fixtures import FixtureEnum
|
||||
|
||||
|
|
Loading…
Reference in New Issue