Skip to content

Commit 4fe3742

Browse files
authored
Merge pull request #363 from splunk/cleanup_mitre_actors_and_techniques
Cleanup mitre actors and techniques
2 parents 6c5cea3 + 570c9d2 commit 4fe3742

File tree

5 files changed

+84
-15
lines changed

5 files changed

+84
-15
lines changed
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
from pydantic import Field
21
from typing import Annotated
32

3+
from pydantic import Field
4+
45
CVE_TYPE = Annotated[str, Field(pattern=r"^CVE-[1|2]\d{3}-\d+$")]
5-
MITRE_ATTACK_ID_TYPE = Annotated[str, Field(pattern=r"^T\d{4}(.\d{3})?$")]
6+
MITRE_ATTACK_ID_TYPE_PARENT = Annotated[str, Field(pattern=r"^T\d{4}$")]
7+
MITRE_ATTACK_ID_TYPE_SUBTYPE = Annotated[str, Field(pattern=r"^T\d{4}(.\d{3})$")]
8+
MITRE_ATTACK_ID_TYPE = MITRE_ATTACK_ID_TYPE_PARENT | MITRE_ATTACK_ID_TYPE_SUBTYPE
69
APPID_TYPE = Annotated[str, Field(pattern="^[a-zA-Z0-9_-]+$")]

contentctl/objects/detection_tags.py

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@
3333
SecurityContentProductName,
3434
SecurityDomain,
3535
)
36-
from contentctl.objects.mitre_attack_enrichment import MitreAttackEnrichment
36+
from contentctl.objects.mitre_attack_enrichment import (
37+
MitreAttackEnrichment,
38+
MitreAttackGroup,
39+
)
3740

3841

3942
class DetectionTags(BaseModel):
@@ -44,7 +47,7 @@ class DetectionTags(BaseModel):
4447
asset_type: AssetType = Field(...)
4548
group: list[str] = []
4649

47-
mitre_attack_id: List[MITRE_ATTACK_ID_TYPE] = []
50+
mitre_attack_id: list[MITRE_ATTACK_ID_TYPE] = []
4851
nist: list[NistCategory] = []
4952

5053
product: list[SecurityContentProductName] = Field(..., min_length=1)
@@ -68,6 +71,15 @@ def kill_chain_phases(self) -> list[KillChainPhase]:
6871
phases.add(phase)
6972
return sorted(list(phases))
7073

74+
# We do not want this to be included in serialization. By default, @property
75+
# objects are not included in dumps
76+
@property
77+
def unique_mitre_attack_groups(self) -> list[MitreAttackGroup]:
78+
group_set: set[MitreAttackGroup] = set()
79+
for enrichment in self.mitre_attack_enrichments:
80+
group_set.update(set(enrichment.mitre_attack_group_objects))
81+
return sorted(group_set, key=lambda k: k.group)
82+
7183
# enum is intentionally Cis18 even though field is named cis20 for legacy reasons
7284
@computed_field
7385
@property
@@ -134,8 +146,8 @@ def addAttackEnrichment(self, info: ValidationInfo):
134146

135147
if len(missing_tactics) > 0:
136148
raise ValueError(f"Missing Mitre Attack IDs. {missing_tactics} not found.")
137-
else:
138-
self.mitre_attack_enrichments = mitre_enrichments
149+
150+
self.mitre_attack_enrichments = mitre_enrichments
139151

140152
return self
141153

@@ -159,6 +171,44 @@ def addAttackEnrichments(cls, v:list[MitreAttackEnrichment], info:ValidationInfo
159171
return enrichments
160172
"""
161173

174+
@field_validator("mitre_attack_id", mode="after")
175+
@classmethod
176+
def sameTypeAndSubtypeNotPresent(
177+
cls, techniques_and_subtechniques: list[MITRE_ATTACK_ID_TYPE]
178+
) -> list[MITRE_ATTACK_ID_TYPE]:
179+
techniques: list[str] = [
180+
f"{unknown_technique}."
181+
for unknown_technique in techniques_and_subtechniques
182+
if "." not in unknown_technique
183+
]
184+
subtechniques: list[MITRE_ATTACK_ID_TYPE] = [
185+
unknown_technique
186+
for unknown_technique in techniques_and_subtechniques
187+
if "." in unknown_technique
188+
]
189+
subtype_and_parent_exist_exceptions: list[ValueError] = []
190+
191+
for subtechnique in subtechniques:
192+
for technique in techniques:
193+
if subtechnique.startswith(technique):
194+
subtype_and_parent_exist_exceptions.append(
195+
ValueError(
196+
f" Technique : {technique.split('.')[0]}\n"
197+
f" SubTechnique: {subtechnique}\n"
198+
)
199+
)
200+
201+
if len(subtype_and_parent_exist_exceptions):
202+
error_string = "\n".join(
203+
str(e) for e in subtype_and_parent_exist_exceptions
204+
)
205+
raise ValueError(
206+
"Overlapping MITRE Attack ID Techniques and Subtechniques may not be defined. "
207+
f"Remove the Technique and keep the Subtechnique:\n{error_string}"
208+
)
209+
210+
return techniques_and_subtechniques
211+
162212
@field_validator("analytic_story", mode="before")
163213
@classmethod
164214
def mapStoryNamesToStoryObjects(
@@ -238,3 +288,6 @@ def mapAtomicGuidsToAtomicTests(
238288
return matched_tests + [
239289
AtomicTest.AtomicTestWhenTestIsMissing(test) for test in missing_tests
240290
]
291+
return matched_tests + [
292+
AtomicTest.AtomicTestWhenTestIsMissing(test) for test in missing_tests
293+
]

contentctl/objects/mitre_attack_enrichment.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
from __future__ import annotations
2-
from pydantic import BaseModel, Field, ConfigDict, HttpUrl, field_validator
3-
from typing import List
4-
from enum import StrEnum
2+
53
import datetime
4+
from enum import StrEnum
5+
from typing import List
6+
7+
from pydantic import BaseModel, ConfigDict, Field, HttpUrl, field_validator
8+
69
from contentctl.objects.annotated_types import MITRE_ATTACK_ID_TYPE
710

811

@@ -84,6 +87,16 @@ def standardize_contributors(cls, contributors: list[str] | None) -> list[str]:
8487
return []
8588
return contributors
8689

90+
def __lt__(self, other: MitreAttackGroup) -> bool:
91+
if not isinstance(object, MitreAttackGroup):
92+
raise Exception(
93+
f"Cannot compare object of type MitreAttackGroup to object of type [{type(object).__name__}]"
94+
)
95+
return self.group < other.group
96+
97+
def __hash__(self) -> int:
98+
return hash(self.group)
99+
87100

88101
class MitreAttackEnrichment(BaseModel):
89102
ConfigDict(extra="forbid")

contentctl/objects/story_tags.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
from __future__ import annotations
2-
from pydantic import BaseModel, Field, model_serializer, ConfigDict
3-
from typing import List, Set, Optional
42

53
from enum import Enum
4+
from typing import List, Optional, Set
65

7-
from contentctl.objects.mitre_attack_enrichment import MitreAttackEnrichment
6+
from pydantic import BaseModel, ConfigDict, Field, model_serializer
7+
8+
from contentctl.objects.annotated_types import CVE_TYPE, MITRE_ATTACK_ID_TYPE
89
from contentctl.objects.enums import (
9-
StoryCategory,
1010
DataModel,
1111
KillChainPhase,
1212
SecurityContentProductName,
13+
StoryCategory,
1314
)
14-
from contentctl.objects.annotated_types import CVE_TYPE, MITRE_ATTACK_ID_TYPE
15+
from contentctl.objects.mitre_attack_enrichment import MitreAttackEnrichment
1516

1617

1718
class StoryUseCase(str, Enum):

contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ tags:
6060
asset_type: Endpoint
6161
mitre_attack_id:
6262
- T1560.001
63-
- T1560
6463
product:
6564
- Splunk Enterprise
6665
- Splunk Enterprise Security

0 commit comments

Comments
 (0)