diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py
index 5e4d1cef7..5ed812b7d 100644
--- a/monkey/monkey_island/cc/models/__init__.py
+++ b/monkey/monkey_island/cc/models/__init__.py
@@ -7,6 +7,5 @@ from .monkey import Monkey
from .monkey_ttl import MonkeyTtl
from .pba_results import PbaResults
from monkey_island.cc.models.report.report import Report
-from .stolen_credentials import StolenCredentials
from .simulation import Simulation, SimulationSchema, IslandMode
from .user_credentials import UserCredentials
diff --git a/monkey/monkey_island/cc/models/stolen_credentials.py b/monkey/monkey_island/cc/models/stolen_credentials.py
deleted file mode 100644
index 968610165..000000000
--- a/monkey/monkey_island/cc/models/stolen_credentials.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from __future__ import annotations
-
-from mongoengine import Document, ListField, ReferenceField
-
-from monkey_island.cc.models import Monkey
-
-
-class StolenCredentials(Document):
- """
- This class has 2 main section:
- * The schema section defines the DB fields in the document. This is the
- data of the object.
- * The logic section defines complex questions we can ask about a single document
- which are asked multiple times, somewhat like an API.
-
- """
-
- # SCHEMA
- monkey = ReferenceField(Monkey)
- identities = ListField()
- secrets = ListField()
diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py
index 81cd7ad69..cb29f7c49 100644
--- a/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py
+++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1003.py
@@ -1,7 +1,7 @@
from common.utils.attack_utils import ScanStatus
-from monkey_island.cc.models import StolenCredentials
+from monkey_island.cc.repository import ICredentialsRepository
from monkey_island.cc.services.attack.technique_reports import AttackTechnique
-from monkey_island.cc.services.reporting.stolen_credentials import get_stolen_creds
+from monkey_island.cc.services.reporting import format_creds_for_reporting
class T1003(AttackTechnique):
@@ -16,8 +16,21 @@ class T1003(AttackTechnique):
@staticmethod
def get_report_data():
+ raise NotImplementedError
+
+
+class T1003GetReportData:
+ """
+ Class to patch the T1003 attack technique which
+ needs stolen credentials from db.
+ """
+
+ def __init__(self, credentials_repository: ICredentialsRepository):
+ self._credentials_repository = credentials_repository
+
+ def __call__(self):
def get_technique_status_and_data():
- if list(StolenCredentials.objects()):
+ if list(self._credentials_repository.get_stolen_credentials()):
status = ScanStatus.USED.value
else:
status = ScanStatus.UNSCANNED.value
@@ -28,5 +41,7 @@ class T1003(AttackTechnique):
data.update(T1003.get_message_and_status(status))
data.update(T1003.get_mitigation_by_status(status))
- data["stolen_creds"] = get_stolen_creds()
+ data["stolen_creds"] = format_creds_for_reporting(
+ self._credentials_repository.get_stolen_credentials()
+ )
return data
diff --git a/monkey/monkey_island/cc/services/initialize.py b/monkey/monkey_island/cc/services/initialize.py
index 26209ce6c..260fa168e 100644
--- a/monkey/monkey_island/cc/services/initialize.py
+++ b/monkey/monkey_island/cc/services/initialize.py
@@ -33,6 +33,7 @@ from monkey_island.cc.repository import (
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
from monkey_island.cc.server_utils.encryption import ILockableEncryptor, RepositoryEncryptor
from monkey_island.cc.services import AWSService, IslandModeService, RepositoryService
+from monkey_island.cc.services.attack.technique_reports.T1003 import T1003, T1003GetReportData
from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService
from monkey_island.cc.services.telemetry.processing.credentials.credentials_parser import (
CredentialsParser,
@@ -64,9 +65,7 @@ def initialize_services(data_dir: Path) -> DIContainer:
_register_repositories(container, data_dir)
_register_services(container)
- # Note: A hack to resolve credentials parser
- # It changes telemetry processing function, this will be refactored!
- _patch_credentials_parser(container)
+ _dirty_hacks(container)
# This is temporary until we get DI all worked out.
ReportService.initialize(
@@ -153,7 +152,16 @@ def _register_services(container: DIContainer):
container.register_instance(RepositoryService, container.resolve(RepositoryService))
-def _patch_credentials_parser(container: DIContainer):
+def _dirty_hacks(container: DIContainer):
+ # A dirty hacks function that patches some of the things that
+ # are needed at the current point
+
+ # Patches attack technique T1003 which is a static class
+ # but it needs stolen credentials from the database
+ T1003.get_report_data = container.resolve(T1003GetReportData)
+
+ # Note: A hack to resolve credentials parser
+ # It changes telemetry processing function, this will be refactored!
TELEMETRY_CATEGORY_TO_PROCESSING_FUNC[TelemCategoryEnum.CREDENTIALS] = container.resolve(
CredentialsParser
)
diff --git a/monkey/monkey_island/cc/services/reporting/__init__.py b/monkey/monkey_island/cc/services/reporting/__init__.py
index e69de29bb..75facb01b 100644
--- a/monkey/monkey_island/cc/services/reporting/__init__.py
+++ b/monkey/monkey_island/cc/services/reporting/__init__.py
@@ -0,0 +1 @@
+from .format_credentials import format_creds_for_reporting
diff --git a/monkey/monkey_island/cc/services/reporting/format_credentials.py b/monkey/monkey_island/cc/services/reporting/format_credentials.py
new file mode 100644
index 000000000..721868cdc
--- /dev/null
+++ b/monkey/monkey_island/cc/services/reporting/format_credentials.py
@@ -0,0 +1,38 @@
+import logging
+from typing import Mapping, Sequence
+
+from common.credentials import CredentialComponentType, Credentials
+
+logger = logging.getLogger(__name__)
+
+
+def format_creds_for_reporting(credentials: Sequence[Credentials]) -> Sequence[Mapping]:
+ logger.info("Stolen creds generated for reporting")
+
+ formatted_creds = []
+ cred_type_dict = {
+ CredentialComponentType.PASSWORD: "Clear Password",
+ CredentialComponentType.LM_HASH: "LM hash",
+ CredentialComponentType.NT_HASH: "NTLM hash",
+ CredentialComponentType.SSH_KEYPAIR: "Clear SSH private key",
+ }
+ for cred in credentials:
+ secret = cred.secret
+ if secret is None:
+ continue
+
+ if secret.credential_type not in cred_type_dict:
+ continue
+ username = _get_username(cred)
+ cred_row = {
+ "username": username,
+ "_type": secret.credential_type.name,
+ "type": cred_type_dict[secret.credential_type],
+ }
+ if cred_row not in formatted_creds:
+ formatted_creds.append(cred_row)
+ return formatted_creds
+
+
+def _get_username(credentials: Credentials) -> str:
+ return credentials.identity.username if credentials.identity else ""
diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py
index dd19f8750..1a46a6566 100644
--- a/monkey/monkey_island/cc/services/reporting/report.py
+++ b/monkey/monkey_island/cc/services/reporting/report.py
@@ -4,7 +4,6 @@ import logging
from itertools import chain, product
from typing import List
-from common.credentials import CredentialComponentType
from common.network.network_range import NetworkRange
from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst
from monkey_island.cc.database import mongo
@@ -20,16 +19,11 @@ from monkey_island.cc.services.reporting.pth_report import PTHReportService
from monkey_island.cc.services.reporting.report_generation_synchronisation import (
safe_generate_regular_report,
)
-from monkey_island.cc.services.reporting.stolen_credentials import (
- extract_ssh_keys,
- get_stolen_creds,
-)
from monkey_island.cc.services.utils.network_utils import get_subnets, local_ip_addresses
from .. import AWSService
from . import aws_exporter
from .issue_processing.exploit_processing.exploiter_descriptor_enum import ExploiterDescriptorEnum
-from .issue_processing.exploit_processing.processors.cred_exploit import CredentialType
from .issue_processing.exploit_processing.processors.exploit import ExploiterReportInfo
logger = logging.getLogger(__name__)
@@ -42,8 +36,6 @@ class ReportService:
_credentials_repository = None
class DerivedIssueEnum:
- WEAK_PASSWORD = "weak_password"
- STOLEN_CREDS = "stolen_creds"
ZEROLOGON_PASS_RESTORE_FAILED = "zerologon_pass_restore_failed"
@classmethod
@@ -382,37 +374,6 @@ class ReportService:
def get_manual_monkey_hostnames():
return [monkey["hostname"] for monkey in get_manual_monkeys()]
- @classmethod
- def get_config_users(cls):
- 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]
-
- @classmethod
- def get_config_passwords(cls):
- 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
- ),
- )
-
- return [p.password for p in passwords]
-
@classmethod
def get_config_exploits(cls):
agent_configuration = cls._agent_configuration_repository.get_configuration()
@@ -438,42 +399,18 @@ class ReportService:
return agent_configuration.propagation.network_scan.targets.local_network_scan
@staticmethod
- def get_issue_set(issues, config_users, config_passwords):
+ def get_issue_set(issues):
issue_set = set()
for machine in issues:
for issue in issues[machine]:
- if ReportService._is_weak_credential_issue(issue, config_users, config_passwords):
- issue_set.add(ReportService.DerivedIssueEnum.WEAK_PASSWORD)
- elif ReportService._is_stolen_credential_issue(issue):
- issue_set.add(ReportService.DerivedIssueEnum.STOLEN_CREDS)
- elif ReportService._is_zerologon_pass_restore_failed(issue):
+ if ReportService._is_zerologon_pass_restore_failed(issue):
issue_set.add(ReportService.DerivedIssueEnum.ZEROLOGON_PASS_RESTORE_FAILED)
issue_set.add(issue["type"])
return issue_set
- @staticmethod
- def _is_weak_credential_issue(
- issue: dict, config_usernames: List[str], config_passwords: List[str]
- ) -> bool:
- # Only credential exploiter issues have 'credential_type'
- return (
- "credential_type" in issue
- and issue["credential_type"] == CredentialType.PASSWORD.value
- and issue["password"] in config_passwords
- and issue["username"] in config_usernames
- )
-
- @staticmethod
- def _is_stolen_credential_issue(issue: dict) -> bool:
- # Only credential exploiter issues have 'credential_type'
- return "credential_type" in issue and (
- issue["credential_type"] == CredentialType.PASSWORD.value
- or issue["credential_type"] == CredentialType.HASH.value
- )
-
@staticmethod
def _is_zerologon_pass_restore_failed(issue: dict):
return (
@@ -490,12 +427,9 @@ class ReportService:
def generate_report():
domain_issues = ReportService.get_domain_issues()
issues = ReportService.get_issues()
- config_users = ReportService.get_config_users()
- config_passwords = ReportService.get_config_passwords()
- issue_set = ReportService.get_issue_set(issues, config_users, config_passwords)
+ issue_set = ReportService.get_issue_set(issues)
cross_segment_issues = ReportService.get_cross_segment_issues()
monkey_latest_modify_time = Monkey.get_latest_modifytime()
- stolen_creds = get_stolen_creds()
scanned_nodes = ReportService.get_scanned()
exploited_cnt = len(get_monkey_exploited())
@@ -515,8 +449,6 @@ class ReportService:
"glance": {
"scanned": scanned_nodes,
"exploited_cnt": exploited_cnt,
- "stolen_creds": stolen_creds,
- "ssh_keys": extract_ssh_keys(stolen_creds),
"strong_users": PTHReportService.get_strong_users_on_crit_details(),
},
"recommendations": {"issues": issues, "domain_issues": domain_issues},
diff --git a/monkey/monkey_island/cc/services/reporting/stolen_credentials.py b/monkey/monkey_island/cc/services/reporting/stolen_credentials.py
deleted file mode 100644
index 2a817ac75..000000000
--- a/monkey/monkey_island/cc/services/reporting/stolen_credentials.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import logging
-from typing import Mapping, Sequence
-
-from common.credentials import CredentialComponentType
-from monkey_island.cc.models import StolenCredentials
-
-logger = logging.getLogger(__name__)
-
-
-def get_stolen_creds() -> Sequence[Mapping]:
- stolen_creds = _fetch_from_db()
- stolen_creds = _format_creds_for_reporting(stolen_creds)
-
- logger.info("Stolen creds generated for reporting")
- return stolen_creds
-
-
-def extract_ssh_keys(credentials: Sequence[Mapping]) -> Sequence[Mapping]:
- return [c for c in credentials if c["_type"] == CredentialComponentType.SSH_KEYPAIR.name]
-
-
-def _fetch_from_db() -> Sequence[StolenCredentials]:
- return list(StolenCredentials.objects())
-
-
-def _format_creds_for_reporting(credentials: Sequence[StolenCredentials]):
- formatted_creds = []
- cred_type_dict = {
- CredentialComponentType.PASSWORD.name: "Clear Password",
- CredentialComponentType.LM_HASH.name: "LM hash",
- CredentialComponentType.NT_HASH.name: "NTLM hash",
- CredentialComponentType.SSH_KEYPAIR.name: "Clear SSH private key",
- }
-
- for cred in credentials:
- for secret_type in cred.secrets:
- if secret_type not in cred_type_dict:
- continue
- username = _get_username(cred)
- cred_row = {
- "username": username,
- "_type": secret_type,
- "type": cred_type_dict[secret_type],
- "origin": cred.monkey.hostname,
- }
- if cred_row not in formatted_creds:
- formatted_creds.append(cred_row)
- return formatted_creds
-
-
-def _get_username(credentials: StolenCredentials) -> str:
- return credentials.identities[0]["username"] if credentials.identities else ""
diff --git a/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials_parser.py b/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials_parser.py
index be14047be..4d675b42c 100644
--- a/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials_parser.py
+++ b/monkey/monkey_island/cc/services/telemetry/processing/credentials/credentials_parser.py
@@ -15,10 +15,10 @@ class CredentialsParser:
def __init__(self, credentials_repository: ICredentialsRepository):
self._credentials_repository = credentials_repository
- def __call__(self, telemetry_dict):
- self._parse_credentials(telemetry_dict)
+ def __call__(self, telemetry_dict, _agent_configuration):
+ self._parse_credentials(telemetry_dict, _agent_configuration)
- def _parse_credentials(self, telemetry_dict: Mapping):
+ def _parse_credentials(self, telemetry_dict: Mapping, _agent_configuration):
credentials = [
Credentials.from_mapping(credential) for credential in telemetry_dict["data"]
]
diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js
index 1a93c8e06..5d5cd72c8 100644
--- a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js
+++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1003.js
@@ -18,7 +18,9 @@ class T1003 extends React.Component {
The monkeys were run with the following configuration:
- Usernames used for brute-forcing:
-
- Passwords used for brute-forcing:
-
- Brute forcing uses stolen credentials only. No credentials were supplied during Monkey’s
- configuration.
-
- The Monkey uses the following exploit methods:
-
{this.props.data.status === ScanStatus.USED ?
- {this.state.report.overview.config_users.map(x =>
-
- {this.state.report.overview.config_passwords.map(x =>
- >
- :
-
- {this.state.report.overview.config_exploits.map(x =>
-
+ The Monkey attempted the following exploitation methods: +
- No exploits are used by the Monkey. + No exploiters were enabled.
} { @@ -379,6 +376,9 @@ class ReportPageComponent extends AuthComponent { getPotentialSecurityIssuesOverviews() { let overviews = []; let issues = this.state.report.overview.issues; + let state_issues = this.state.issues; + issues.push(...state_issues); + issues = [...new Set(issues)]; for(let i=0; i < issues.length; i++) { if (this.isIssuePotentialSecurityIssue(issues[i])) { @@ -420,6 +420,10 @@ class ReportPageComponent extends AuthComponent { getImmediateThreatCount() { let threatCount = 0; let issues = this.state.report.overview.issues; + let state_issues = this.state.issues; + + issues.push(...state_issues); + issues = [...new Set(issues)]; for(let i=0; i < issues.length; i++) { if(this.isIssueImmediateThreat(issues[i])) { @@ -439,6 +443,10 @@ class ReportPageComponent extends AuthComponent { getImmediateThreatsOverviews() { let overviews = []; let issues = this.state.report.overview.issues; + let state_issues = this.state.issues; + + issues.push(...state_issues); + issues = [...new Set(issues)]; for(let i=0; i < issues.length; i++) { if (this.isIssueImmediateThreat(issues[i])) { @@ -535,7 +543,10 @@ class ReportPageComponent extends AuthComponent {+ Usernames used for brute-forcing: +
++ Credentials used for brute-forcing: +
++ No credentials were used. +
+ ) + + } + +} + +export default UsedCredentials; diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WeakPasswordIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WeakPasswordIssue.js deleted file mode 100644 index ee3c6c04f..000000000 --- a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/WeakPasswordIssue.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; - -export function weakPasswordIssueOverview() { - return (