forked from p15670423/monkey
Merge pull request #2088 from guardicore/1662-remove-configservice-from-reportservice
1662 remove configservice from reportservice
This commit is contained in:
commit
753970f644
|
@ -2,9 +2,6 @@ SSH_KEYS_PATH = ["internal", "exploits", "exploit_ssh_keys"]
|
||||||
INACCESSIBLE_SUBNETS_PATH = ["basic_network", "network_analysis", "inaccessible_subnets"]
|
INACCESSIBLE_SUBNETS_PATH = ["basic_network", "network_analysis", "inaccessible_subnets"]
|
||||||
USER_LIST_PATH = ["basic", "credentials", "exploit_user_list"]
|
USER_LIST_PATH = ["basic", "credentials", "exploit_user_list"]
|
||||||
PASSWORD_LIST_PATH = ["basic", "credentials", "exploit_password_list"]
|
PASSWORD_LIST_PATH = ["basic", "credentials", "exploit_password_list"]
|
||||||
EXPLOITER_CLASSES_PATH = ["basic", "exploiters", "exploiter_classes"]
|
|
||||||
SUBNET_SCAN_LIST_PATH = ["basic_network", "scope", "subnet_scan_list"]
|
|
||||||
LOCAL_NETWORK_SCAN_PATH = ["basic_network", "scope", "local_network_scan"]
|
|
||||||
LM_HASH_LIST_PATH = ["internal", "exploits", "exploit_lm_hash_list"]
|
LM_HASH_LIST_PATH = ["internal", "exploits", "exploit_lm_hash_list"]
|
||||||
NTLM_HASH_LIST_PATH = ["internal", "exploits", "exploit_ntlm_hash_list"]
|
NTLM_HASH_LIST_PATH = ["internal", "exploits", "exploit_ntlm_hash_list"]
|
||||||
|
|
||||||
|
|
|
@ -3,21 +3,10 @@ from __future__ import annotations
|
||||||
from bson import json_util
|
from bson import json_util
|
||||||
|
|
||||||
from monkey_island.cc.models.report.report import Report
|
from monkey_island.cc.models.report.report import Report
|
||||||
from monkey_island.cc.server_utils.encryption import (
|
|
||||||
SensitiveField,
|
|
||||||
StringListEncryptor,
|
|
||||||
decrypt_dict,
|
|
||||||
encrypt_dict,
|
|
||||||
)
|
|
||||||
|
|
||||||
sensitive_fields = [
|
|
||||||
SensitiveField(path="overview.config_passwords", field_encryptor=StringListEncryptor)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def save_report(report_dict: dict):
|
def save_report(report_dict: dict):
|
||||||
report_dict = _encode_dot_char_before_mongo_insert(report_dict)
|
report_dict = _encode_dot_char_before_mongo_insert(report_dict)
|
||||||
report_dict = encrypt_dict(sensitive_fields, report_dict)
|
|
||||||
Report.objects.delete()
|
Report.objects.delete()
|
||||||
Report(
|
Report(
|
||||||
overview=report_dict["overview"],
|
overview=report_dict["overview"],
|
||||||
|
@ -29,7 +18,7 @@ def save_report(report_dict: dict):
|
||||||
|
|
||||||
def get_report() -> dict:
|
def get_report() -> dict:
|
||||||
report_dict = Report.objects.first().to_mongo()
|
report_dict = Report.objects.first().to_mongo()
|
||||||
return _decode_dot_char_before_mongo_insert(decrypt_dict(sensitive_fields, report_dict))
|
return _decode_dot_char_before_mongo_insert(report_dict)
|
||||||
|
|
||||||
|
|
||||||
# TODO remove this unnecessary encoding. I think these are legacy methods from back in the day
|
# TODO remove this unnecessary encoding. I think these are legacy methods from back in the day
|
||||||
|
|
|
@ -21,5 +21,4 @@ from .dict_encryptor import (
|
||||||
FieldNotFoundError,
|
FieldNotFoundError,
|
||||||
)
|
)
|
||||||
from .field_encryptors.i_field_encryptor import IFieldEncryptor
|
from .field_encryptors.i_field_encryptor import IFieldEncryptor
|
||||||
from .field_encryptors.string_list_encryptor import StringListEncryptor
|
|
||||||
from .field_encryptors.string_encryptor import StringEncryptor
|
from .field_encryptors.string_encryptor import StringEncryptor
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
from .i_field_encryptor import IFieldEncryptor
|
from .i_field_encryptor import IFieldEncryptor
|
||||||
from .string_list_encryptor import StringListEncryptor
|
|
||||||
from .string_encryptor import StringEncryptor
|
from .string_encryptor import StringEncryptor
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from ..data_store_encryptor import get_datastore_encryptor
|
|
||||||
from . import IFieldEncryptor
|
|
||||||
|
|
||||||
|
|
||||||
class StringListEncryptor(IFieldEncryptor):
|
|
||||||
@staticmethod
|
|
||||||
def encrypt(value: List[str]):
|
|
||||||
return [get_datastore_encryptor().encrypt(string.encode()) for string in value]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def decrypt(value: List[bytes]):
|
|
||||||
return [get_datastore_encryptor().decrypt(bytes_).decode() for bytes_ in value]
|
|
|
@ -69,7 +69,11 @@ def initialize_services(data_dir: Path) -> DIContainer:
|
||||||
_patch_credentials_parser(container)
|
_patch_credentials_parser(container)
|
||||||
|
|
||||||
# This is temporary until we get DI all worked out.
|
# This is temporary until we get DI all worked out.
|
||||||
ReportService.initialize(container.resolve(AWSService))
|
ReportService.initialize(
|
||||||
|
container.resolve(AWSService),
|
||||||
|
container.resolve(IAgentConfigurationRepository),
|
||||||
|
container.resolve(ICredentialsRepository),
|
||||||
|
)
|
||||||
|
|
||||||
return container
|
return container
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,16 @@
|
||||||
import functools
|
import functools
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import itertools
|
|
||||||
import logging
|
import logging
|
||||||
|
from itertools import chain, product
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from common.config_value_paths import (
|
from common.credentials import CredentialComponentType
|
||||||
EXPLOITER_CLASSES_PATH,
|
|
||||||
LOCAL_NETWORK_SCAN_PATH,
|
|
||||||
PASSWORD_LIST_PATH,
|
|
||||||
SUBNET_SCAN_LIST_PATH,
|
|
||||||
USER_LIST_PATH,
|
|
||||||
)
|
|
||||||
from common.network.network_range import NetworkRange
|
from common.network.network_range import NetworkRange
|
||||||
from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst
|
from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst
|
||||||
from monkey_island.cc.database import mongo
|
from monkey_island.cc.database import mongo
|
||||||
from monkey_island.cc.models import Monkey
|
from monkey_island.cc.models import Monkey
|
||||||
from monkey_island.cc.models.report import get_report, save_report
|
from monkey_island.cc.models.report import get_report, save_report
|
||||||
from monkey_island.cc.services.config import ConfigService
|
from monkey_island.cc.repository import IAgentConfigurationRepository, ICredentialsRepository
|
||||||
from monkey_island.cc.services.configuration.utils import (
|
from monkey_island.cc.services.configuration.utils import (
|
||||||
get_config_network_segments_as_subnet_groups,
|
get_config_network_segments_as_subnet_groups,
|
||||||
)
|
)
|
||||||
|
@ -47,6 +41,8 @@ logger = logging.getLogger(__name__)
|
||||||
class ReportService:
|
class ReportService:
|
||||||
|
|
||||||
_aws_service = None
|
_aws_service = None
|
||||||
|
_agent_configuration_repository = None
|
||||||
|
_credentials_repository = None
|
||||||
|
|
||||||
class DerivedIssueEnum:
|
class DerivedIssueEnum:
|
||||||
WEAK_PASSWORD = "weak_password"
|
WEAK_PASSWORD = "weak_password"
|
||||||
|
@ -54,8 +50,15 @@ class ReportService:
|
||||||
ZEROLOGON_PASS_RESTORE_FAILED = "zerologon_pass_restore_failed"
|
ZEROLOGON_PASS_RESTORE_FAILED = "zerologon_pass_restore_failed"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def initialize(cls, aws_service: AWSService):
|
def initialize(
|
||||||
|
cls,
|
||||||
|
aws_service: AWSService,
|
||||||
|
agent_configuration_repository: IAgentConfigurationRepository,
|
||||||
|
credentials_repository: ICredentialsRepository,
|
||||||
|
):
|
||||||
cls._aws_service = aws_service
|
cls._aws_service = aws_service
|
||||||
|
cls._agent_configuration_repository = agent_configuration_repository
|
||||||
|
cls._credentials_repository = credentials_repository
|
||||||
|
|
||||||
# This should pull from Simulation entity
|
# This should pull from Simulation entity
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -305,7 +308,7 @@ class ReportService:
|
||||||
"""
|
"""
|
||||||
cross_segment_issues = []
|
cross_segment_issues = []
|
||||||
|
|
||||||
for subnet_pair in itertools.product(subnet_group, subnet_group):
|
for subnet_pair in product(subnet_group, subnet_group):
|
||||||
source_subnet = subnet_pair[0]
|
source_subnet = subnet_pair[0]
|
||||||
target_subnet = subnet_pair[1]
|
target_subnet = subnet_pair[1]
|
||||||
pair_issues = ReportService.get_cross_segment_issues_per_subnet_pair(
|
pair_issues = ReportService.get_cross_segment_issues_per_subnet_pair(
|
||||||
|
@ -381,37 +384,60 @@ class ReportService:
|
||||||
def get_manual_monkey_hostnames():
|
def get_manual_monkey_hostnames():
|
||||||
return [monkey["hostname"] for monkey in get_manual_monkeys()]
|
return [monkey["hostname"] for monkey in get_manual_monkeys()]
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def get_config_users():
|
def get_config_users(cls):
|
||||||
return ConfigService.get_config_value(USER_LIST_PATH, True)
|
usernames = []
|
||||||
|
configured_credentials = cls._credentials_repository.get_configured_credentials()
|
||||||
|
for credentials in configured_credentials:
|
||||||
|
usernames = chain(
|
||||||
|
usernames,
|
||||||
|
(
|
||||||
|
identity
|
||||||
|
for identity in credentials.identities
|
||||||
|
if identity.credential_type == CredentialComponentType.USERNAME
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return [u.username for u in usernames]
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def get_config_passwords():
|
def get_config_passwords(cls):
|
||||||
return ConfigService.get_config_value(PASSWORD_LIST_PATH, True)
|
passwords = []
|
||||||
|
configured_credentials = cls._credentials_repository.get_configured_credentials()
|
||||||
|
for credentials in configured_credentials:
|
||||||
|
passwords = chain(
|
||||||
|
passwords,
|
||||||
|
(
|
||||||
|
secret
|
||||||
|
for secret in credentials.secrets
|
||||||
|
if secret.credential_type == CredentialComponentType.PASSWORD
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
return [p.password for p in passwords]
|
||||||
def get_config_exploits():
|
|
||||||
exploits_config_value = EXPLOITER_CLASSES_PATH
|
|
||||||
# TODO: Return default config here
|
|
||||||
default_exploits = ConfigService.get_default_config(False)
|
|
||||||
for namespace in exploits_config_value:
|
|
||||||
default_exploits = default_exploits[namespace]
|
|
||||||
exploits = ConfigService.get_config_value(exploits_config_value, True)
|
|
||||||
|
|
||||||
if exploits == default_exploits:
|
@classmethod
|
||||||
return ["default"]
|
def get_config_exploits(cls):
|
||||||
|
agent_configuration = cls._agent_configuration_repository.get_configuration()
|
||||||
|
exploitation_configuration = agent_configuration.propagation.exploitation
|
||||||
|
|
||||||
|
enabled_exploiters = chain(
|
||||||
|
exploitation_configuration.brute_force, exploitation_configuration.vulnerability
|
||||||
|
)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
ExploiterDescriptorEnum.get_by_class_name(exploit).display_name for exploit in exploits
|
ExploiterDescriptorEnum.get_by_class_name(exploiter.name).display_name
|
||||||
|
for exploiter in enabled_exploiters
|
||||||
]
|
]
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def get_config_ips():
|
def get_config_ips(cls):
|
||||||
return ConfigService.get_config_value(SUBNET_SCAN_LIST_PATH, True)
|
agent_configuration = cls._agent_configuration_repository.get_configuration()
|
||||||
|
return agent_configuration.propagation.network_scan.targets.subnets
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def get_config_scan():
|
def get_config_scan(cls):
|
||||||
return ConfigService.get_config_value(LOCAL_NETWORK_SCAN_PATH, True)
|
agent_configuration = cls._agent_configuration_repository.get_configuration()
|
||||||
|
return agent_configuration.propagation.network_scan.targets.local_network_scan
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_issue_set(issues, config_users, config_passwords):
|
def get_issue_set(issues, config_users, config_passwords):
|
||||||
|
@ -478,8 +504,6 @@ class ReportService:
|
||||||
report = {
|
report = {
|
||||||
"overview": {
|
"overview": {
|
||||||
"manual_monkeys": ReportService.get_manual_monkey_hostnames(),
|
"manual_monkeys": ReportService.get_manual_monkey_hostnames(),
|
||||||
"config_users": config_users,
|
|
||||||
"config_passwords": config_passwords,
|
|
||||||
"config_exploits": ReportService.get_config_exploits(),
|
"config_exploits": ReportService.get_config_exploits(),
|
||||||
"config_ips": ReportService.get_config_ips(),
|
"config_ips": ReportService.get_config_ips(),
|
||||||
"config_scan": ReportService.get_config_scan(),
|
"config_scan": ReportService.get_config_scan(),
|
||||||
|
|
|
@ -297,17 +297,12 @@ class ReportPageComponent extends AuthComponent {
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
this.state.report.overview.config_exploits.length > 0 ?
|
this.state.report.overview.config_exploits.length > 0 ?
|
||||||
(
|
<p>
|
||||||
this.state.report.overview.config_exploits[0] === 'default' ?
|
The Monkey uses the following exploit methods:
|
||||||
''
|
<ul>
|
||||||
:
|
{this.state.report.overview.config_exploits.map(x => <li key={x}>{x}</li>)}
|
||||||
<p>
|
</ul>
|
||||||
The Monkey uses the following exploit methods:
|
</p>
|
||||||
<ul>
|
|
||||||
{this.state.report.overview.config_exploits.map(x => <li key={x}>{x}</li>)}
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
)
|
|
||||||
:
|
:
|
||||||
<p>
|
<p>
|
||||||
No exploits are used by the Monkey.
|
No exploits are used by the Monkey.
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
import copy
|
import copy
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from monkey_island.cc.models import Report
|
from monkey_island.cc.models import Report
|
||||||
from monkey_island.cc.models.report import get_report, save_report
|
from monkey_island.cc.models.report import get_report, save_report
|
||||||
from monkey_island.cc.server_utils.encryption import IFieldEncryptor, SensitiveField
|
|
||||||
|
|
||||||
MOCK_SENSITIVE_FIELD_CONTENTS = ["the_string", "the_string2"]
|
MOCK_SENSITIVE_FIELD_CONTENTS = ["the_string", "the_string2"]
|
||||||
MOCK_REPORT_DICT = {
|
MOCK_REPORT_DICT = {
|
||||||
"overview": {
|
"overview": {
|
||||||
"foo": {"the_key": MOCK_SENSITIVE_FIELD_CONTENTS, "other_key": "other_value"},
|
"foo": {"the_key": ["the_string", "the_string2"], "other_key": "other_value"},
|
||||||
"bar": {"the_key": []},
|
"bar": {"the_key": []},
|
||||||
},
|
},
|
||||||
"glance": {"foo": "bar"},
|
"glance": {"foo": "bar"},
|
||||||
|
@ -19,43 +17,6 @@ MOCK_REPORT_DICT = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class MockStringListEncryptor(IFieldEncryptor):
|
|
||||||
plaintext = []
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def encrypt(value: List[str]) -> List[str]:
|
|
||||||
return [MockStringListEncryptor._encrypt(v) for v in value]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _encrypt(value: str) -> str:
|
|
||||||
MockStringListEncryptor.plaintext.append(value)
|
|
||||||
return f"ENCRYPTED_{str(len(MockStringListEncryptor.plaintext) - 1)}"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def decrypt(value: List[str]) -> List[str]:
|
|
||||||
return MockStringListEncryptor.plaintext
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
def patch_sensitive_fields(monkeypatch):
|
|
||||||
mock_sensitive_fields = [
|
|
||||||
SensitiveField("overview.foo.the_key", MockStringListEncryptor),
|
|
||||||
SensitiveField("overview.bar.the_key", MockStringListEncryptor),
|
|
||||||
]
|
|
||||||
monkeypatch.setattr(
|
|
||||||
"monkey_island.cc.models.report.report_dal.sensitive_fields", mock_sensitive_fields
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("uses_database")
|
|
||||||
def test_report_encryption():
|
|
||||||
save_report(MOCK_REPORT_DICT)
|
|
||||||
|
|
||||||
assert Report.objects.first()["overview"]["foo"]["the_key"] == ["ENCRYPTED_0", "ENCRYPTED_1"]
|
|
||||||
assert Report.objects.first()["overview"]["bar"]["the_key"] == []
|
|
||||||
assert get_report()["overview"]["foo"]["the_key"] == MOCK_SENSITIVE_FIELD_CONTENTS
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("uses_database")
|
@pytest.mark.usefixtures("uses_database")
|
||||||
def test_report_dot_encoding():
|
def test_report_dot_encoding():
|
||||||
mrd = copy.deepcopy(MOCK_REPORT_DICT)
|
mrd = copy.deepcopy(MOCK_REPORT_DICT)
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
from monkey_island.cc.server_utils.encryption import StringListEncryptor
|
|
||||||
|
|
||||||
MOCK_STRING_LIST = ["test_1", "test_2"]
|
|
||||||
EMPTY_LIST = []
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow
|
|
||||||
def test_encryption_and_decryption(uses_encryptor):
|
|
||||||
encrypted_list = StringListEncryptor.encrypt(MOCK_STRING_LIST)
|
|
||||||
assert not encrypted_list == MOCK_STRING_LIST
|
|
||||||
decrypted_list = StringListEncryptor.decrypt(encrypted_list)
|
|
||||||
assert decrypted_list == MOCK_STRING_LIST
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow
|
|
||||||
def test_empty_list(uses_encryptor):
|
|
||||||
# Tests that no errors are raised
|
|
||||||
encrypted_list = StringListEncryptor.encrypt(EMPTY_LIST)
|
|
||||||
StringListEncryptor.decrypt(encrypted_list)
|
|
Loading…
Reference in New Issue