Skip to content

Commit 758924d

Browse files
committed
Bump some versions in pyproject.toml.
Restructure the Pydantic Object for building MitreAttack API Groups based on the newer version of attackcti library.
1 parent 98f6b7a commit 758924d

File tree

3 files changed

+59
-34
lines changed

3 files changed

+59
-34
lines changed

contentctl/enrichments/attack_enrichment.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,27 +33,33 @@ def getEnrichmentByMitreID(self, mitre_id:Annotated[str, Field(pattern=r"^T\d{4}
3333
else:
3434
raise Exception(f"Error, Unable to find Mitre Enrichment for MitreID {mitre_id}")
3535

36-
37-
def addMitreID(self, technique:dict, tactics:list[str], groups:list[dict[str,Any]])->None:
36+
def addMitreIDViaGroupNames(self, technique:dict, tactics:list[str], groupNames:list[str])->None:
37+
technique_id = technique['technique_id']
38+
technique_obj = technique['technique']
39+
tactics.sort()
3840

41+
if technique_id in self.data:
42+
raise Exception(f"Error, trying to redefine MITRE ID '{technique_id}'")
43+
self.data[technique_id] = MitreAttackEnrichment(mitre_attack_id=technique_id,
44+
mitre_attack_technique=technique_obj,
45+
mitre_attack_tactics=tactics,
46+
mitre_attack_groups=groupNames,
47+
mitre_attack_group_objects=[])
48+
49+
def addMitreIDViaGroupObjects(self, technique:dict, tactics:list[str], groupObjects:list[dict[str,Any]])->None:
3950
technique_id = technique['technique_id']
4051
technique_obj = technique['technique']
4152
tactics.sort()
42-
group_names_only:list[str] = sorted([group['group'] for group in groups])
4353

44-
import pprint
45-
print(technique_id)
46-
print(technique_obj)
47-
print(tactics)
48-
print(group_names_only)
49-
pprint.pprint(groups)
54+
groupNames:list[str] = sorted([group['group'] for group in groupObjects])
55+
5056
if technique_id in self.data:
5157
raise Exception(f"Error, trying to redefine MITRE ID '{technique_id}'")
5258
self.data[technique_id] = MitreAttackEnrichment(mitre_attack_id=technique_id,
5359
mitre_attack_technique=technique_obj,
5460
mitre_attack_tactics=tactics,
55-
mitre_attack_groups=group_names_only,
56-
mitre_attack_group_objects=groups)
61+
mitre_attack_groups=groupNames,
62+
mitre_attack_group_objects=groupObjects)
5763

5864

5965
def get_attack_lookup(self, input_path: str, store_csv: bool = False, force_cached_or_offline: bool = False, skip_enrichment:bool = False) -> dict:
@@ -105,7 +111,7 @@ def get_attack_lookup(self, input_path: str, store_csv: bool = False, force_cach
105111
for tactic in technique['tactic']:
106112
tactics.append(tactic.replace('-',' ').title())
107113

108-
self.addMitreID(technique, tactics, apt_groups)
114+
self.addMitreIDViaGroupObjects(technique, tactics, apt_groups)
109115
attack_lookup[technique['technique_id']] = {'technique': technique['technique'], 'tactics': tactics, 'groups': apt_groups}
110116

111117
if store_csv:
@@ -138,7 +144,7 @@ def get_attack_lookup(self, input_path: str, store_csv: bool = False, force_cach
138144
technique_input = {'technique_id': key , 'technique': attack_lookup[key]['technique'] }
139145
tactics_input = attack_lookup[key]['tactics']
140146
groups_input = attack_lookup[key]['groups']
141-
self.addMitreID(technique=technique_input, tactics=tactics_input, groups=groups_input)
147+
self.addMitreIDViaGroupNames(technique=technique_input, tactics=tactics_input, groups=groups_input)
142148

143149

144150

contentctl/objects/mitre_attack_enrichment.py

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from __future__ import annotations
2-
from pydantic import BaseModel, Field, ConfigDict, HttpUrl
2+
from pydantic import BaseModel, Field, ConfigDict, HttpUrl, field_validator
33
from typing import List, Annotated
44
from enum import StrEnum
55
import datetime
@@ -22,17 +22,14 @@ class MitreTactics(StrEnum):
2222

2323

2424
class AttackGroupMatrix(StrEnum):
25-
mitre_attack = "mitre-attack"
25+
enterprise_attack = "enterprise-attack"
26+
ics_attack = "ics-attack"
27+
mobile_attack = "mobile-attack"
2628

2729

2830
class AttackGroupType(StrEnum):
2931
intrusion_set = "intrusion-set"
3032

31-
class MitreDomain(StrEnum):
32-
intrusion_set = "enterprise-attack"
33-
mobile_attack = "mobile-attack"
34-
ics_attack = "ics-attack"
35-
3633
class MitreExternalReference(BaseModel):
3734
model_config = ConfigDict(extra='forbid')
3835
source_name: str
@@ -43,25 +40,47 @@ class MitreExternalReference(BaseModel):
4340

4441
class MitreAttackGroup(BaseModel):
4542
model_config = ConfigDict(extra='forbid')
43+
contributors: list[str] = []
4644
created: datetime.datetime
4745
created_by_ref: str
4846
external_references: list[MitreExternalReference]
4947
group: str
5048
group_aliases: list[str]
5149
group_description: str
50+
group_id: str
5251
id: str
53-
matrix: AttackGroupMatrix
52+
matrix: list[AttackGroupMatrix]
53+
mitre_attack_spec_version: None | str
54+
mitre_version: str
55+
#assume that if the deprecated field is not present, then the group is not deprecated
56+
mitre_deprecated: bool
5457
modified: datetime.datetime
58+
modified_by_ref: str
5559
object_marking_refs: list[str]
5660
type: AttackGroupType
5761
url: HttpUrl
58-
x_mitre_attack_spec_version: None | str = None
59-
x_mitre_deprecated: None | bool = None
60-
x_mitre_domains: list[MitreDomain]
61-
x_mitre_modified_by_ref: str
62-
x_mitre_version: str
63-
contributors: list[str] = []
62+
63+
64+
@field_validator("mitre_deprecated", mode="before")
65+
def standardize_mitre_deprecated(cls, mitre_deprecated:bool | None) -> bool:
66+
'''
67+
For some reason, the API will return either a bool for mitre_deprecated OR
68+
None. We simplify our typing by converting None to False, and assuming that
69+
if deprecated is None, then the group is not deprecated.
70+
'''
71+
if mitre_deprecated is None:
72+
return False
73+
return mitre_deprecated
6474

75+
@field_validator("contributors", mode="before")
76+
def standardize_contributors(cls, contributors:list[str] | None) -> list[str]:
77+
'''
78+
For some reason, the API will return either a list of strings for contributors OR
79+
None. We simplify our typing by converting None to an empty list.
80+
'''
81+
if contributors is None:
82+
return []
83+
return contributors
6584

6685
class MitreAttackEnrichment(BaseModel):
6786
ConfigDict(use_enum_values=True)

pyproject.toml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,20 @@ contentctl = 'contentctl.contentctl:main'
1111

1212
[tool.poetry.dependencies]
1313
python = "^3.11"
14-
pydantic = "^2.7.1"
15-
PyYAML = "^6.0.1"
16-
requests = "~2.32.2"
14+
pydantic = "^2.8.2"
15+
PyYAML = "^6.0.2"
16+
requests = "~2.32.3"
1717
pycvesearch = "^1.2"
1818
xmltodict = "^0.13.0"
19-
attackcti = ">=0.3.7,<0.5.0"
19+
attackcti = "^0.4.0"
2020
Jinja2 = "^3.1.4"
2121
questionary = "^2.0.1"
2222
docker = "^7.1.0"
23-
splunk-sdk = "^2.0.1"
23+
splunk-sdk = "^2.0.2"
2424
semantic-version = "^2.10.0"
2525
bottle = "^0.12.25"
26-
tqdm = "^4.66.4"
27-
pygit2 = "^1.14.1"
26+
tqdm = "^4.66.5"
27+
pygit2 = "^1.15.1"
2828
tyro = "^0.8.3"
2929
gitpython = "^3.1.43"
3030
setuptools = ">=69.5.1,<74.0.0"

0 commit comments

Comments
 (0)