contentctl 5.5.3__tar.gz → 5.5.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {contentctl-5.5.3 → contentctl-5.5.4}/PKG-INFO +1 -1
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/enrichments/attack_enrichment.py +57 -29
- contentctl-5.5.4/contentctl/output/attack_nav_output.py +197 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/pyproject.toml +1 -1
- contentctl-5.5.3/contentctl/output/attack_nav_output.py +0 -53
- {contentctl-5.5.3 → contentctl-5.5.4}/LICENSE.md +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/README.md +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/__init__.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/build.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/deploy_acs.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/detection_testing/DetectionTestingManager.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/detection_testing/GitService.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/detection_testing/generate_detection_coverage_badge.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureServer.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/detection_testing/progress_bar.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/detection_testing/views/DetectionTestingView.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/doc_gen.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/initialize.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/inspect.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/new_content.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/release_notes.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/reporting.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/test.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/actions/validate.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/api.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/contentctl.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/enrichments/cve_enrichment.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/enrichments/splunk_app_enrichment.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/helper/link_validator.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/helper/logger.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/helper/splunk_app.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/helper/utils.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/input/director.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/input/new_content_questions.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/input/yml_reader.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/abstract_security_content_objects/detection_abstract.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/alert_action.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/annotated_types.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/atomic.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/base_security_event.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/base_test.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/base_test_result.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/baseline.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/baseline_tags.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/config.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/constants.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/content_versioning_service.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/correlation_search.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/dashboard.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/data_source.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/deployment.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/deployment_email.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/deployment_notable.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/deployment_phantom.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/deployment_rba.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/deployment_scheduling.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/deployment_slack.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/detection.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/detection_metadata.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/detection_stanza.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/detection_tags.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/drilldown.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/enums.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/errors.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/integration_test.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/integration_test_result.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/investigation.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/investigation_tags.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/lookup.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/macro.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/manual_test.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/manual_test_result.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/mitre_attack_enrichment.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/notable_action.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/notable_event.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/playbook.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/playbook_tags.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/rba.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/removed_security_content_object.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/risk_analysis_action.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/risk_event.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/risk_object.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/savedsearches_conf.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/security_content_object.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/story.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/story_tags.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/test_attack_data.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/test_group.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/threat_object.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/throttling.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/unit_test.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/unit_test_baseline.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/objects/unit_test_result.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/api_json_output.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/attack_nav_writer.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/conf_output.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/conf_writer.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/doc_md_output.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/jinja_writer.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/json_writer.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/runtime_csv_writer.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/svg_output.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/analyticstories_detections.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/analyticstories_investigations.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/analyticstories_stories.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/app.conf.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/app.manifest.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/collections.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/content-version.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/detection_count.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/detection_coverage.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/doc_detection_page.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/doc_detections.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/doc_navigation.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/doc_navigation_pages.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/doc_playbooks.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/doc_playbooks_page.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/doc_stories.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/doc_story_page.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/es_investigations_investigations.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/es_investigations_stories.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/header.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/macros.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/panel.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/savedsearches_baselines.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/savedsearches_detections.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/savedsearches_investigations.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/server.conf.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/transforms.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/workflow_actions.j2 +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/yml_writer.py +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/README.md +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_default.yml +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/README/essoc_story_detail.txt +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/README/essoc_summary.txt +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/README/essoc_usage_dashboard.txt +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/README.md +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/default/analytic_stories.conf +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/default/commands.conf +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/default/data/ui/nav/default.xml +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/default/data/ui/views/escu_summary.xml +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/default/data/ui/views/feedback.xml +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/default/use_case_library.conf +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/lookups/mitre_enrichment.csv +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/metadata/default.meta +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/static/appIcon.png +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/static/appIconAlt.png +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/static/appIconAlt_2x.png +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/static/appIcon_2x.png +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/data_sources/sysmon_eventid_1.yml +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/datamodels_cim.conf +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/datamodels_custom.conf +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/deployments/escu_default_configuration_anomaly.yml +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/deployments/escu_default_configuration_baseline.yml +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/deployments/escu_default_configuration_correlation.yml +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/deployments/escu_default_configuration_hunting.yml +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/deployments/escu_default_configuration_ttp.yml +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/detections/application/.gitkeep +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/detections/cloud/.gitkeep +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/detections/network/.gitkeep +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/detections/web/.gitkeep +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/macros/security_content_ctime.yml +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/macros/security_content_summariesonly.yml +0 -0
- {contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/stories/cobalt_strike.yml +0 -0
|
@@ -1,18 +1,40 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import logging
|
|
4
|
-
from pydantic import BaseModel
|
|
5
4
|
from dataclasses import field
|
|
6
|
-
from typing import Any
|
|
7
5
|
from pathlib import Path
|
|
6
|
+
from typing import Any, TypedDict, cast
|
|
7
|
+
|
|
8
|
+
from attackcti import attack_client # type: ignore[reportMissingTypeStubs]
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
from contentctl.objects.annotated_types import MITRE_ATTACK_ID_TYPE
|
|
12
|
+
from contentctl.objects.config import validate
|
|
8
13
|
from contentctl.objects.mitre_attack_enrichment import (
|
|
9
14
|
MitreAttackEnrichment,
|
|
10
15
|
MitreTactics,
|
|
11
16
|
)
|
|
12
|
-
from contentctl.objects.config import validate
|
|
13
|
-
from contentctl.objects.annotated_types import MITRE_ATTACK_ID_TYPE
|
|
14
17
|
|
|
18
|
+
# Suppress attackcti logging
|
|
15
19
|
logging.getLogger("taxii2client").setLevel(logging.CRITICAL)
|
|
20
|
+
logging.getLogger("stix2").setLevel(logging.CRITICAL)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AttackPattern(TypedDict):
|
|
24
|
+
id: str
|
|
25
|
+
technique_id: str
|
|
26
|
+
technique: str
|
|
27
|
+
tactic: list[str]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class IntrusionSet(TypedDict):
|
|
31
|
+
id: str
|
|
32
|
+
group: str
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Relationship(TypedDict):
|
|
36
|
+
target_object: str
|
|
37
|
+
source_object: str
|
|
16
38
|
|
|
17
39
|
|
|
18
40
|
class AttackEnrichment(BaseModel):
|
|
@@ -98,11 +120,6 @@ class AttackEnrichment(BaseModel):
|
|
|
98
120
|
end="",
|
|
99
121
|
flush=True,
|
|
100
122
|
)
|
|
101
|
-
# The existence of the input_path is validated during cli argument validation, but it is
|
|
102
|
-
# possible that the repo is in the wrong format. If the following directories do not
|
|
103
|
-
# exist, then attack_client will fall back to resolving via REST API. We do not
|
|
104
|
-
# want this as it is slow and error prone, so we will force an exception to
|
|
105
|
-
# be generated.
|
|
106
123
|
enterprise_path = input_path / "enterprise-attack"
|
|
107
124
|
mobile_path = input_path / "ics-attack"
|
|
108
125
|
ics_path = input_path / "mobile-attack"
|
|
@@ -123,36 +140,47 @@ class AttackEnrichment(BaseModel):
|
|
|
123
140
|
}
|
|
124
141
|
)
|
|
125
142
|
|
|
126
|
-
all_enterprise_techniques =
|
|
127
|
-
stix_format=False
|
|
143
|
+
all_enterprise_techniques = cast(
|
|
144
|
+
list[AttackPattern], lift.get_enterprise_techniques(stix_format=False)
|
|
128
145
|
)
|
|
129
|
-
enterprise_relationships =
|
|
130
|
-
stix_format=False
|
|
146
|
+
enterprise_relationships = cast(
|
|
147
|
+
list[Relationship], lift.get_enterprise_relationships(stix_format=False)
|
|
148
|
+
)
|
|
149
|
+
enterprise_groups = cast(
|
|
150
|
+
list[IntrusionSet], lift.get_enterprise_groups(stix_format=False)
|
|
131
151
|
)
|
|
132
|
-
enterprise_groups = lift.get_enterprise_groups(stix_format=False)
|
|
133
152
|
|
|
134
153
|
for technique in all_enterprise_techniques:
|
|
135
154
|
apt_groups: list[dict[str, Any]] = []
|
|
136
155
|
for relationship in enterprise_relationships:
|
|
137
|
-
if
|
|
138
|
-
|
|
139
|
-
|
|
156
|
+
if relationship["target_object"] == technique[
|
|
157
|
+
"id"
|
|
158
|
+
] and relationship["source_object"].startswith("intrusion-set"):
|
|
140
159
|
for group in enterprise_groups:
|
|
141
160
|
if relationship["source_object"] == group["id"]:
|
|
142
|
-
apt_groups.append(group)
|
|
143
|
-
# apt_groups.append(group['group'])
|
|
161
|
+
apt_groups.append(dict(group))
|
|
144
162
|
|
|
145
|
-
tactics = []
|
|
163
|
+
tactics: list[MitreTactics] = []
|
|
146
164
|
if "tactic" in technique:
|
|
147
165
|
for tactic in technique["tactic"]:
|
|
148
|
-
tactics.append(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
166
|
+
tactics.append(
|
|
167
|
+
cast(MitreTactics, tactic.replace("-", " ").title())
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
self.addMitreIDViaGroupObjects(dict(technique), tactics, apt_groups)
|
|
171
|
+
attack_lookup[technique["technique_id"]] = (
|
|
172
|
+
MitreAttackEnrichment.model_validate(
|
|
173
|
+
{
|
|
174
|
+
"mitre_attack_id": technique["technique_id"],
|
|
175
|
+
"mitre_attack_technique": technique["technique"],
|
|
176
|
+
"mitre_attack_tactics": tactics,
|
|
177
|
+
"mitre_attack_groups": [
|
|
178
|
+
group["group"] for group in apt_groups
|
|
179
|
+
],
|
|
180
|
+
"mitre_attack_group_objects": apt_groups,
|
|
181
|
+
}
|
|
182
|
+
)
|
|
183
|
+
)
|
|
156
184
|
|
|
157
185
|
except Exception as err:
|
|
158
186
|
raise Exception(f"Error getting MITRE Enrichment: {str(err)}")
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Standard library imports
|
|
2
|
+
import json
|
|
3
|
+
import pathlib
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Any, TypedDict
|
|
6
|
+
|
|
7
|
+
# Third-party imports
|
|
8
|
+
from contentctl.objects.detection import Detection
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TechniqueData(TypedDict):
|
|
12
|
+
score: int
|
|
13
|
+
file_paths: list[str]
|
|
14
|
+
links: list[dict[str, str]]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LayerData(TypedDict):
|
|
18
|
+
name: str
|
|
19
|
+
versions: dict[str, str]
|
|
20
|
+
domain: str
|
|
21
|
+
description: str
|
|
22
|
+
filters: dict[str, list[str]]
|
|
23
|
+
sorting: int
|
|
24
|
+
layout: dict[str, str | bool]
|
|
25
|
+
hideDisabled: bool
|
|
26
|
+
techniques: list[dict[str, Any]]
|
|
27
|
+
gradient: dict[str, list[str] | int]
|
|
28
|
+
legendItems: list[dict[str, str]]
|
|
29
|
+
showTacticRowBackground: bool
|
|
30
|
+
tacticRowBackground: str
|
|
31
|
+
selectTechniquesAcrossTactics: bool
|
|
32
|
+
selectSubtechniquesWithParent: bool
|
|
33
|
+
selectVisibleTechniques: bool
|
|
34
|
+
metadata: list[dict[str, str]]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AttackNavOutput:
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
layer_name: str = "Splunk Detection Coverage",
|
|
41
|
+
layer_description: str = "MITRE ATT&CK coverage for Splunk detections",
|
|
42
|
+
layer_domain: str = "enterprise-attack",
|
|
43
|
+
):
|
|
44
|
+
self.layer_name = layer_name
|
|
45
|
+
self.layer_description = layer_description
|
|
46
|
+
self.layer_domain = layer_domain
|
|
47
|
+
|
|
48
|
+
def writeObjects(
|
|
49
|
+
self, detections: list[Detection], output_path: pathlib.Path
|
|
50
|
+
) -> None:
|
|
51
|
+
"""
|
|
52
|
+
Generate MITRE ATT&CK Navigator layer file from detections
|
|
53
|
+
Args:
|
|
54
|
+
detections: List of Detection objects
|
|
55
|
+
output_path: Path to write the layer file
|
|
56
|
+
"""
|
|
57
|
+
techniques: dict[str, TechniqueData] = {}
|
|
58
|
+
tactic_coverage: dict[str, set[str]] = {}
|
|
59
|
+
|
|
60
|
+
# Process each detection
|
|
61
|
+
for detection in detections:
|
|
62
|
+
if not hasattr(detection.tags, "mitre_attack_id"):
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
for tactic in detection.tags.mitre_attack_id:
|
|
66
|
+
if tactic not in techniques:
|
|
67
|
+
techniques[tactic] = {"score": 0, "file_paths": [], "links": []}
|
|
68
|
+
tactic_coverage[tactic] = set()
|
|
69
|
+
|
|
70
|
+
detection_type = detection.source
|
|
71
|
+
detection_id = str(detection.id) # Convert UUID to string
|
|
72
|
+
detection_url = (
|
|
73
|
+
f"https://research.splunk.com/{detection_type}/{detection_id}/"
|
|
74
|
+
)
|
|
75
|
+
detection_name = detection.name.replace(
|
|
76
|
+
"_", " "
|
|
77
|
+
).title() # Convert to Title Case
|
|
78
|
+
detection_info = f"{detection_name}"
|
|
79
|
+
|
|
80
|
+
techniques[tactic]["score"] += 1
|
|
81
|
+
techniques[tactic]["file_paths"].append(detection_info)
|
|
82
|
+
techniques[tactic]["links"].append(
|
|
83
|
+
{"label": detection_name, "url": detection_url}
|
|
84
|
+
)
|
|
85
|
+
tactic_coverage[tactic].add(detection_id)
|
|
86
|
+
|
|
87
|
+
# Create the layer file
|
|
88
|
+
layer: LayerData = {
|
|
89
|
+
"name": self.layer_name,
|
|
90
|
+
"versions": {
|
|
91
|
+
"attack": "14", # Update as needed
|
|
92
|
+
"navigator": "5.1.0",
|
|
93
|
+
"layer": "4.5",
|
|
94
|
+
},
|
|
95
|
+
"domain": self.layer_domain,
|
|
96
|
+
"description": self.layer_description,
|
|
97
|
+
"filters": {
|
|
98
|
+
"platforms": [
|
|
99
|
+
"Windows",
|
|
100
|
+
"Linux",
|
|
101
|
+
"macOS",
|
|
102
|
+
"AWS",
|
|
103
|
+
"GCP",
|
|
104
|
+
"Azure",
|
|
105
|
+
"Office 365",
|
|
106
|
+
"SaaS",
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
"sorting": 0,
|
|
110
|
+
"layout": {
|
|
111
|
+
"layout": "flat",
|
|
112
|
+
"showName": True,
|
|
113
|
+
"showID": False,
|
|
114
|
+
"showAggregateScores": True,
|
|
115
|
+
"countUnscored": True,
|
|
116
|
+
"aggregateFunction": "average",
|
|
117
|
+
"expandedSubtechniques": "none",
|
|
118
|
+
},
|
|
119
|
+
"hideDisabled": False,
|
|
120
|
+
"techniques": [
|
|
121
|
+
{
|
|
122
|
+
"techniqueID": tid,
|
|
123
|
+
"score": data["score"],
|
|
124
|
+
"metadata": [
|
|
125
|
+
{"name": "Detection", "value": name, "divider": False}
|
|
126
|
+
for name in data["file_paths"]
|
|
127
|
+
]
|
|
128
|
+
+ [
|
|
129
|
+
{
|
|
130
|
+
"name": "Link",
|
|
131
|
+
"value": f"[View Detection]({link['url']})",
|
|
132
|
+
"divider": False,
|
|
133
|
+
}
|
|
134
|
+
for link in data["links"]
|
|
135
|
+
],
|
|
136
|
+
"links": [
|
|
137
|
+
{"label": link["label"], "url": link["url"]}
|
|
138
|
+
for link in data["links"]
|
|
139
|
+
],
|
|
140
|
+
}
|
|
141
|
+
for tid, data in techniques.items()
|
|
142
|
+
],
|
|
143
|
+
"gradient": {
|
|
144
|
+
"colors": [
|
|
145
|
+
"#1a365d", # Dark blue
|
|
146
|
+
"#2c5282", # Medium blue
|
|
147
|
+
"#4299e1", # Light blue
|
|
148
|
+
"#48bb78", # Light green
|
|
149
|
+
"#38a169", # Medium green
|
|
150
|
+
"#276749", # Dark green
|
|
151
|
+
],
|
|
152
|
+
"minValue": 0,
|
|
153
|
+
"maxValue": 5, # Adjust based on your max detections per technique
|
|
154
|
+
},
|
|
155
|
+
"legendItems": [
|
|
156
|
+
{"label": "1 Detection", "color": "#1a365d"},
|
|
157
|
+
{"label": "2 Detections", "color": "#4299e1"},
|
|
158
|
+
{"label": "3 Detections", "color": "#48bb78"},
|
|
159
|
+
{"label": "4+ Detections", "color": "#276749"},
|
|
160
|
+
],
|
|
161
|
+
"showTacticRowBackground": True,
|
|
162
|
+
"tacticRowBackground": "#dddddd",
|
|
163
|
+
"selectTechniquesAcrossTactics": True,
|
|
164
|
+
"selectSubtechniquesWithParent": True,
|
|
165
|
+
"selectVisibleTechniques": False,
|
|
166
|
+
"metadata": [
|
|
167
|
+
{"name": "Generated", "value": datetime.now().isoformat()},
|
|
168
|
+
{"name": "Total Detections", "value": str(len(detections))},
|
|
169
|
+
{"name": "Covered Techniques", "value": str(len(techniques))},
|
|
170
|
+
],
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# Write the layer file
|
|
174
|
+
output_file = output_path / "coverage.json"
|
|
175
|
+
with open(output_file, "w") as f:
|
|
176
|
+
json.dump(layer, f, indent=2)
|
|
177
|
+
|
|
178
|
+
print(f"\n✅ MITRE ATT&CK Navigator layer file written to: {output_file}")
|
|
179
|
+
print("📊 Coverage Summary:")
|
|
180
|
+
print(f" Total Detections: {len(detections)}")
|
|
181
|
+
print(f" Covered Techniques: {len(techniques)}")
|
|
182
|
+
print(f" Tactics with Coverage: {len(tactic_coverage)}")
|
|
183
|
+
print("\n🗺️ To view the layer:")
|
|
184
|
+
print(" 1. Go to https://mitre-attack.github.io/attack-navigator/")
|
|
185
|
+
print(" 2. Click 'Open Existing Layer'")
|
|
186
|
+
print(f" 3. Select the file: {output_file}")
|
|
187
|
+
|
|
188
|
+
def convertNameToFileName(self, name: str) -> str:
|
|
189
|
+
"""Convert a detection name to a valid filename"""
|
|
190
|
+
file_name = (
|
|
191
|
+
name.replace(" ", "_")
|
|
192
|
+
.replace("-", "_")
|
|
193
|
+
.replace(".", "_")
|
|
194
|
+
.replace("/", "_")
|
|
195
|
+
.lower()
|
|
196
|
+
)
|
|
197
|
+
return f"{file_name}.yml"
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import pathlib
|
|
2
|
-
from typing import List, Union
|
|
3
|
-
|
|
4
|
-
from contentctl.objects.detection import Detection
|
|
5
|
-
from contentctl.output.attack_nav_writer import AttackNavWriter
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class AttackNavOutput:
|
|
9
|
-
def writeObjects(
|
|
10
|
-
self, detections: List[Detection], output_path: pathlib.Path
|
|
11
|
-
) -> None:
|
|
12
|
-
techniques: dict[str, dict[str, Union[List[str], int]]] = {}
|
|
13
|
-
|
|
14
|
-
for detection in detections:
|
|
15
|
-
for tactic in detection.tags.mitre_attack_id:
|
|
16
|
-
if tactic not in techniques:
|
|
17
|
-
techniques[tactic] = {"score": 0, "file_paths": []}
|
|
18
|
-
|
|
19
|
-
detection_type = detection.source
|
|
20
|
-
detection_id = detection.id
|
|
21
|
-
|
|
22
|
-
# Store all three pieces of information separately
|
|
23
|
-
detection_info = f"{detection_type}|{detection_id}|{detection.name}"
|
|
24
|
-
|
|
25
|
-
techniques[tactic]["score"] = techniques[tactic].get("score", 0) + 1
|
|
26
|
-
if isinstance(techniques[tactic]["file_paths"], list):
|
|
27
|
-
techniques[tactic]["file_paths"].append(detection_info)
|
|
28
|
-
|
|
29
|
-
"""
|
|
30
|
-
for detection in objects:
|
|
31
|
-
if detection.tags.mitre_attack_enrichments:
|
|
32
|
-
for mitre_attack_enrichment in detection.tags.mitre_attack_enrichments:
|
|
33
|
-
if not mitre_attack_enrichment.mitre_attack_id in techniques:
|
|
34
|
-
techniques[mitre_attack_enrichment.mitre_attack_id] = {
|
|
35
|
-
'score': 1,
|
|
36
|
-
'file_paths': ['https://github.com/splunk/security_content/blob/develop/detections/' + detection.getSource() + '/' + self.convertNameToFileName(detection.name)]
|
|
37
|
-
}
|
|
38
|
-
else:
|
|
39
|
-
techniques[mitre_attack_enrichment.mitre_attack_id]['score'] = techniques[mitre_attack_enrichment.mitre_attack_id]['score'] + 1
|
|
40
|
-
techniques[mitre_attack_enrichment.mitre_attack_id]['file_paths'].append('https://github.com/splunk/security_content/blob/develop/detections/' + detection.getSource() + '/' + self.convertNameToFileName(detection.name))
|
|
41
|
-
"""
|
|
42
|
-
AttackNavWriter.writeAttackNavFile(techniques, output_path / "coverage.json")
|
|
43
|
-
|
|
44
|
-
def convertNameToFileName(self, name: str):
|
|
45
|
-
file_name = (
|
|
46
|
-
name.replace(" ", "_")
|
|
47
|
-
.replace("-", "_")
|
|
48
|
-
.replace(".", "_")
|
|
49
|
-
.replace("/", "_")
|
|
50
|
-
.lower()
|
|
51
|
-
)
|
|
52
|
-
file_name = file_name + ".yml"
|
|
53
|
-
return file_name
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/analyticstories_detections.j2
RENAMED
|
File without changes
|
{contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/analyticstories_investigations.j2
RENAMED
|
File without changes
|
{contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/analyticstories_stories.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/es_investigations_stories.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/savedsearches_baselines.j2
RENAMED
|
File without changes
|
{contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/savedsearches_detections.j2
RENAMED
|
File without changes
|
{contentctl-5.5.3 → contentctl-5.5.4}/contentctl/output/templates/savedsearches_investigations.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/README/essoc_summary.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/default/commands.conf
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/lookups/mitre_enrichment.csv
RENAMED
|
File without changes
|
{contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/metadata/default.meta
RENAMED
|
File without changes
|
|
File without changes
|
{contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/static/appIconAlt.png
RENAMED
|
File without changes
|
{contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/static/appIconAlt_2x.png
RENAMED
|
File without changes
|
{contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/app_template/static/appIcon_2x.png
RENAMED
|
File without changes
|
{contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/data_sources/sysmon_eventid_1.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/macros/security_content_ctime.yml
RENAMED
|
File without changes
|
{contentctl-5.5.3 → contentctl-5.5.4}/contentctl/templates/macros/security_content_summariesonly.yml
RENAMED
|
File without changes
|
|
File without changes
|