Merge pull request #1022 from guardicore/zerologon-bb-test

Zerologon BB test
This commit is contained in:
VakarisZ 2021-03-08 13:59:16 +02:00 committed by GitHub
commit a132881ccc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 141 additions and 13 deletions

View File

@ -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()

View File

@ -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)}")

View File

@ -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

View File

@ -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": []
})

View File

@ -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):
"""

View File

@ -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):

View File

@ -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))}

View File

@ -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):

View File

@ -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__)

View File

@ -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():

View File

@ -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

View File

@ -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]:

View File

@ -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