contentctl 4.3.0__py3-none-any.whl → 4.3.2__py3-none-any.whl

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.
@@ -1060,7 +1060,9 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1060
1060
  results = JSONResultsReader(job.results(output_mode="json"))
1061
1061
 
1062
1062
  # Consolidate a set of the distinct observable field names
1063
- observable_fields_set = set([o.name for o in detection.tags.observable])
1063
+ observable_fields_set = set([o.name for o in detection.tags.observable]) # keeping this around for later
1064
+ risk_object_fields_set = set([o.name for o in detection.tags.observable if "Victim" in o.role ]) # just the "Risk Objects"
1065
+ threat_object_fields_set = set([o.name for o in detection.tags.observable if "Attacker" in o.role]) # just the "threat objects"
1064
1066
 
1065
1067
  # Ensure the search had at least one result
1066
1068
  if int(job.content.get("resultCount", "0")) > 0:
@@ -1068,7 +1070,10 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1068
1070
  test.result = UnitTestResult()
1069
1071
 
1070
1072
  # Initialize the collection of fields that are empty that shouldn't be
1073
+ present_threat_objects: set[str] = set()
1071
1074
  empty_fields: set[str] = set()
1075
+
1076
+
1072
1077
 
1073
1078
  # Filter out any messages in the results
1074
1079
  for result in results:
@@ -1077,30 +1082,50 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1077
1082
 
1078
1083
  # If not a message, it is a dict and we will process it
1079
1084
  results_fields_set = set(result.keys())
1085
+ # Guard against first events (relevant later)
1080
1086
 
1081
- # Identify any observable fields that are not available in the results
1082
- missing_fields = observable_fields_set - results_fields_set
1083
- if len(missing_fields) > 0:
1087
+ # Identify any risk object fields that are not available in the results
1088
+ missing_risk_objects = risk_object_fields_set - results_fields_set
1089
+ if len(missing_risk_objects) > 0:
1084
1090
  # Report a failure in such cases
1085
- e = Exception(f"The observable field(s) {missing_fields} are missing in the detection results")
1091
+ e = Exception(f"The observable field(s) {missing_risk_objects} are missing in the detection results")
1086
1092
  test.result.set_job_content(
1087
1093
  job.content,
1088
1094
  self.infrastructure,
1089
- TestResultStatus.ERROR,
1095
+ TestResultStatus.FAIL,
1090
1096
  exception=e,
1091
1097
  duration=time.time() - search_start_time,
1092
1098
  )
1093
1099
 
1094
- return
1100
+ return
1095
1101
 
1096
- # If we find one or more fields that contain the string "null" then they were
1102
+ # If we find one or more risk object fields that contain the string "null" then they were
1097
1103
  # not populated and we should throw an error. This can happen if there is a typo
1098
1104
  # on a field. In this case, the field will appear but will not contain any values
1099
- current_empty_fields = set()
1105
+ current_empty_fields: set[str] = set()
1106
+
1100
1107
  for field in observable_fields_set:
1101
1108
  if result.get(field, 'null') == 'null':
1102
- current_empty_fields.add(field)
1103
-
1109
+ if field in risk_object_fields_set:
1110
+ e = Exception(f"The risk object field {field} is missing in at least one result.")
1111
+ test.result.set_job_content(
1112
+ job.content,
1113
+ self.infrastructure,
1114
+ TestResultStatus.FAIL,
1115
+ exception=e,
1116
+ duration=time.time() - search_start_time,
1117
+ )
1118
+ return
1119
+ else:
1120
+ if field in threat_object_fields_set:
1121
+ current_empty_fields.add(field)
1122
+ else:
1123
+ if field in threat_object_fields_set:
1124
+ present_threat_objects.add(field)
1125
+ continue
1126
+
1127
+
1128
+
1104
1129
  # If everything succeeded up until now, and no empty fields are found in the
1105
1130
  # current result, then the search was a success
1106
1131
  if len(current_empty_fields) == 0:
@@ -1114,21 +1139,32 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1114
1139
 
1115
1140
  else:
1116
1141
  empty_fields = empty_fields.union(current_empty_fields)
1117
-
1118
- # Report a failure if there were empty fields in all results
1119
- e = Exception(
1120
- f"One or more required observable fields {empty_fields} contained 'null' values. Is the "
1121
- "data being parsed correctly or is there an error in the naming of a field?"
1142
+
1143
+
1144
+ missing_threat_objects = threat_object_fields_set - present_threat_objects
1145
+ # Report a failure if there were empty fields in a threat object in all results
1146
+ if len(missing_threat_objects) > 0:
1147
+ e = Exception(
1148
+ f"One or more required threat object fields {missing_threat_objects} contained 'null' values in all events. "
1149
+ "Is the data being parsed correctly or is there an error in the naming of a field?"
1122
1150
  )
1123
- test.result.set_job_content(
1124
- job.content,
1125
- self.infrastructure,
1126
- TestResultStatus.ERROR,
1127
- exception=e,
1128
- duration=time.time() - search_start_time,
1129
- )
1151
+ test.result.set_job_content(
1152
+ job.content,
1153
+ self.infrastructure,
1154
+ TestResultStatus.FAIL,
1155
+ exception=e,
1156
+ duration=time.time() - search_start_time,
1157
+ )
1158
+ return
1159
+
1130
1160
 
1131
- return
1161
+ test.result.set_job_content(
1162
+ job.content,
1163
+ self.infrastructure,
1164
+ TestResultStatus.PASS,
1165
+ duration=time.time() - search_start_time,
1166
+ )
1167
+ return
1132
1168
 
1133
1169
  else:
1134
1170
  # Report a failure if there were no results at all
@@ -7,7 +7,7 @@ from attackcti import attack_client
7
7
  import logging
8
8
  from pydantic import BaseModel, Field
9
9
  from dataclasses import field
10
- from typing import Annotated
10
+ from typing import Annotated,Any
11
11
  from contentctl.objects.mitre_attack_enrichment import MitreAttackEnrichment
12
12
  from contentctl.objects.config import validate
13
13
  logging.getLogger('taxii2client').setLevel(logging.CRITICAL)
@@ -33,21 +33,33 @@ class AttackEnrichment(BaseModel):
33
33
  else:
34
34
  raise Exception(f"Error, Unable to find Mitre Enrichment for MitreID {mitre_id}")
35
35
 
36
-
37
- def addMitreID(self, technique:dict, tactics:list[str], groups:list[str])->None:
38
-
36
+ def addMitreIDViaGroupNames(self, technique:dict, tactics:list[str], groupNames:list[str])->None:
39
37
  technique_id = technique['technique_id']
40
38
  technique_obj = technique['technique']
41
39
  tactics.sort()
42
- groups.sort()
43
-
40
+
44
41
  if technique_id in self.data:
45
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:
50
+ technique_id = technique['technique_id']
51
+ technique_obj = technique['technique']
52
+ tactics.sort()
46
53
 
54
+ groupNames:list[str] = sorted([group['group'] for group in groupObjects])
55
+
56
+ if technique_id in self.data:
57
+ raise Exception(f"Error, trying to redefine MITRE ID '{technique_id}'")
47
58
  self.data[technique_id] = MitreAttackEnrichment(mitre_attack_id=technique_id,
48
59
  mitre_attack_technique=technique_obj,
49
60
  mitre_attack_tactics=tactics,
50
- mitre_attack_groups=groups)
61
+ mitre_attack_groups=groupNames,
62
+ mitre_attack_group_objects=groupObjects)
51
63
 
52
64
 
53
65
  def get_attack_lookup(self, input_path: str, store_csv: bool = False, force_cached_or_offline: bool = False, skip_enrichment:bool = False) -> dict:
@@ -86,19 +98,20 @@ class AttackEnrichment(BaseModel):
86
98
  progress_percent = ((index+1)/len(all_enterprise_techniques)) * 100
87
99
  if (sys.stdout.isatty() and sys.stdin.isatty() and sys.stderr.isatty()):
88
100
  print(f"\r\t{'MITRE Technique Progress'.rjust(23)}: [{progress_percent:3.0f}%]...", end="", flush=True)
89
- apt_groups = []
101
+ apt_groups:list[dict[str,Any]] = []
90
102
  for relationship in enterprise_relationships:
91
103
  if (relationship['target_object'] == technique['id']) and relationship['source_object'].startswith('intrusion-set'):
92
104
  for group in enterprise_groups:
93
105
  if relationship['source_object'] == group['id']:
94
- apt_groups.append(group['group'])
106
+ apt_groups.append(group)
107
+ #apt_groups.append(group['group'])
95
108
 
96
109
  tactics = []
97
110
  if ('tactic' in technique):
98
111
  for tactic in technique['tactic']:
99
112
  tactics.append(tactic.replace('-',' ').title())
100
113
 
101
- self.addMitreID(technique, tactics, apt_groups)
114
+ self.addMitreIDViaGroupObjects(technique, tactics, apt_groups)
102
115
  attack_lookup[technique['technique_id']] = {'technique': technique['technique'], 'tactics': tactics, 'groups': apt_groups}
103
116
 
104
117
  if store_csv:
@@ -131,7 +144,7 @@ class AttackEnrichment(BaseModel):
131
144
  technique_input = {'technique_id': key , 'technique': attack_lookup[key]['technique'] }
132
145
  tactics_input = attack_lookup[key]['tactics']
133
146
  groups_input = attack_lookup[key]['groups']
134
- self.addMitreID(technique=technique_input, tactics=tactics_input, groups=groups_input)
147
+ self.addMitreIDViaGroupNames(technique=technique_input, tactics=tactics_input, groups=groups_input)
135
148
 
136
149
 
137
150
 
@@ -290,6 +290,11 @@ class Detection_Abstract(SecurityContentObject):
290
290
  risk_object['threat_object_field'] = entity.name
291
291
  risk_object['threat_object_type'] = "url"
292
292
  risk_objects.append(risk_object)
293
+
294
+ elif 'Attacker' in entity.role:
295
+ risk_object['threat_object_field'] = entity.name
296
+ risk_object['threat_object_type'] = entity.type.lower()
297
+ risk_objects.append(risk_object)
293
298
 
294
299
  else:
295
300
  risk_object['risk_object_type'] = 'other'
@@ -132,3 +132,8 @@ SES_ATTACK_TACTICS_ID_MAPPING = {
132
132
  "Exfiltration": "TA0010",
133
133
  "Impact": "TA0040"
134
134
  }
135
+
136
+ RBA_OBSERVABLE_ROLE_MAPPING = {
137
+ "Attacker": 0,
138
+ "Victim": 1
139
+ }
@@ -1,8 +1,8 @@
1
1
  from __future__ import annotations
2
- from pydantic import BaseModel, Field, ConfigDict
2
+ from pydantic import BaseModel, Field, ConfigDict, HttpUrl, field_validator
3
3
  from typing import List, Annotated
4
4
  from enum import StrEnum
5
-
5
+ import datetime
6
6
 
7
7
  class MitreTactics(StrEnum):
8
8
  RECONNAISSANCE = "Reconnaissance"
@@ -21,12 +21,75 @@ class MitreTactics(StrEnum):
21
21
  IMPACT = "Impact"
22
22
 
23
23
 
24
+ class AttackGroupMatrix(StrEnum):
25
+ enterprise_attack = "enterprise-attack"
26
+ ics_attack = "ics-attack"
27
+ mobile_attack = "mobile-attack"
28
+
29
+
30
+ class AttackGroupType(StrEnum):
31
+ intrusion_set = "intrusion-set"
32
+
33
+ class MitreExternalReference(BaseModel):
34
+ model_config = ConfigDict(extra='forbid')
35
+ source_name: str
36
+ external_id: None | str = None
37
+ url: None | HttpUrl = None
38
+ description: None | str = None
39
+
40
+
41
+ class MitreAttackGroup(BaseModel):
42
+ model_config = ConfigDict(extra='forbid')
43
+ contributors: list[str] = []
44
+ created: datetime.datetime
45
+ created_by_ref: str
46
+ external_references: list[MitreExternalReference]
47
+ group: str
48
+ group_aliases: list[str]
49
+ group_description: str
50
+ group_id: str
51
+ id: str
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
57
+ modified: datetime.datetime
58
+ modified_by_ref: str
59
+ object_marking_refs: list[str]
60
+ type: AttackGroupType
61
+ url: HttpUrl
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
74
+
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
84
+
24
85
  class MitreAttackEnrichment(BaseModel):
25
86
  ConfigDict(use_enum_values=True)
26
87
  mitre_attack_id: Annotated[str, Field(pattern=r"^T\d{4}(.\d{3})?$")] = Field(...)
27
88
  mitre_attack_technique: str = Field(...)
28
89
  mitre_attack_tactics: List[MitreTactics] = Field(...)
29
90
  mitre_attack_groups: List[str] = Field(...)
30
-
91
+ #Exclude this field from serialization - it is very large and not useful in JSON objects
92
+ mitre_attack_group_objects: list[MitreAttackGroup] = Field(..., exclude=True)
31
93
  def __hash__(self) -> int:
32
94
  return id(self)
95
+
@@ -1,5 +1,5 @@
1
1
  from pydantic import BaseModel, field_validator
2
- from contentctl.objects.constants import SES_OBSERVABLE_TYPE_MAPPING, SES_OBSERVABLE_ROLE_MAPPING
2
+ from contentctl.objects.constants import SES_OBSERVABLE_TYPE_MAPPING, RBA_OBSERVABLE_ROLE_MAPPING
3
3
 
4
4
 
5
5
  class Observable(BaseModel):
@@ -26,10 +26,12 @@ class Observable(BaseModel):
26
26
  def check_roles(cls, v: list[str]):
27
27
  if len(v) == 0:
28
28
  raise ValueError("Error, at least 1 role must be listed for Observable.")
29
+ if len(v) > 1:
30
+ raise ValueError("Error, each Observable can only have one role.")
29
31
  for role in v:
30
- if role not in SES_OBSERVABLE_ROLE_MAPPING.keys():
32
+ if role not in RBA_OBSERVABLE_ROLE_MAPPING.keys():
31
33
  raise ValueError(
32
34
  f"Invalid role '{role}' provided for observable. Valid observable types are "
33
- f"{SES_OBSERVABLE_ROLE_MAPPING.keys()}"
35
+ f"{RBA_OBSERVABLE_ROLE_MAPPING.keys()}"
34
36
  )
35
37
  return v
@@ -53,11 +53,11 @@ tags:
53
53
  - name: parent_process_name
54
54
  type: Process
55
55
  role:
56
- - Parent Process
56
+ - Attacker
57
57
  - name: process_name
58
58
  type: Process
59
59
  role:
60
- - Child Process
60
+ - Attacker
61
61
  product:
62
62
  - Splunk Enterprise
63
63
  - Splunk Enterprise Security
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: contentctl
3
- Version: 4.3.0
3
+ Version: 4.3.2
4
4
  Summary: Splunk Content Control Tool
5
5
  License: Apache 2.0
6
6
  Author: STRT
@@ -11,20 +11,20 @@ Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
13
  Requires-Dist: Jinja2 (>=3.1.4,<4.0.0)
14
- Requires-Dist: PyYAML (>=6.0.1,<7.0.0)
15
- Requires-Dist: attackcti (>=0.3.7,<0.5.0)
14
+ Requires-Dist: PyYAML (>=6.0.2,<7.0.0)
15
+ Requires-Dist: attackcti (>=0.4.0,<0.5.0)
16
16
  Requires-Dist: bottle (>=0.12.25,<0.13.0)
17
17
  Requires-Dist: docker (>=7.1.0,<8.0.0)
18
18
  Requires-Dist: gitpython (>=3.1.43,<4.0.0)
19
19
  Requires-Dist: pycvesearch (>=1.2,<2.0)
20
- Requires-Dist: pydantic (>=2.7.1,<3.0.0)
21
- Requires-Dist: pygit2 (>=1.14.1,<2.0.0)
20
+ Requires-Dist: pydantic (>=2.8.2,<3.0.0)
21
+ Requires-Dist: pygit2 (>=1.15.1,<2.0.0)
22
22
  Requires-Dist: questionary (>=2.0.1,<3.0.0)
23
- Requires-Dist: requests (>=2.32.2,<2.33.0)
23
+ Requires-Dist: requests (>=2.32.3,<2.33.0)
24
24
  Requires-Dist: semantic-version (>=2.10.0,<3.0.0)
25
- Requires-Dist: setuptools (>=69.5.1,<73.0.0)
26
- Requires-Dist: splunk-sdk (>=2.0.1,<3.0.0)
27
- Requires-Dist: tqdm (>=4.66.4,<5.0.0)
25
+ Requires-Dist: setuptools (>=69.5.1,<74.0.0)
26
+ Requires-Dist: splunk-sdk (>=2.0.2,<3.0.0)
27
+ Requires-Dist: tqdm (>=4.66.5,<5.0.0)
28
28
  Requires-Dist: tyro (>=0.8.3,<0.9.0)
29
29
  Requires-Dist: xmltodict (>=0.13.0,<0.14.0)
30
30
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ contentctl/actions/deploy_acs.py,sha256=mf3uk495H1EU_LNN-TiOsYCo18HMGoEBMb6ojeTr
4
4
  contentctl/actions/detection_testing/DetectionTestingManager.py,sha256=zg8JasDjCpSC-yhseEyUwO8qbDJIUJbhlus9Li9ZAnA,8818
5
5
  contentctl/actions/detection_testing/GitService.py,sha256=W1vnDDt8JvIL7Z1Lve3D3RS7h8qwMxrW0BMXVGuDZDM,9007
6
6
  contentctl/actions/detection_testing/generate_detection_coverage_badge.py,sha256=N5mznaeErVak3mOBwsd0RDBFJO3bku0EZvpayCyU-uk,2259
7
- contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py,sha256=fDiyntUFXGi3OKNCL02Pr-4PLzX3dKWcD5UiTYoOkYA,53002
7
+ contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py,sha256=1PxEnhWSFgiOtIlqRD10gRShjB65i9vLiFEnwHSGf4o,55139
8
8
  contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py,sha256=REM3WB-DQAczeknGAKMzJhnvHgnt-u9yDG2UKGVj2vM,6854
9
9
  contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureServer.py,sha256=Q1ZfCYOp54O39bgTScZMInkmZiU-bGAM9Hiwr2mq5ms,370
10
10
  contentctl/actions/detection_testing/progress_bar.py,sha256=OK9oRnPlzPAswt9KZNYID-YLHxqaYPY821kIE4-rCeA,3244
@@ -23,7 +23,7 @@ contentctl/actions/test.py,sha256=dx7f750_MrlvysxOmOdIro1bH0iVKF4K54TSwhvU2MU,51
23
23
  contentctl/actions/validate.py,sha256=2MQ8yumCKj7zD8iUuA5gfFEMcE-GPRzYqkvuOexn0JA,5633
24
24
  contentctl/api.py,sha256=FBOpRhbBCBdjORmwe_8MPQ3PRZ6T0KrrFcfKovVFkug,6343
25
25
  contentctl/contentctl.py,sha256=SxWFMYquSYQAATrTBpvfj4j5DRedsOF2xO96ASs74wA,10505
26
- contentctl/enrichments/attack_enrichment.py,sha256=dVwXcULSeZJuQbeTlPpKDyEB9Y6uCy0UGWI83gPLTI0,6735
26
+ contentctl/enrichments/attack_enrichment.py,sha256=HsfHfcrRmsHT6pILN457jmCGOCdAhOlRBGfAP8aZY78,7834
27
27
  contentctl/enrichments/cve_enrichment.py,sha256=SjiytaZktVNbfICXcZ2vZzBiQpOkug5taPtiJK-S1OE,2313
28
28
  contentctl/enrichments/splunk_app_enrichment.py,sha256=zDNHFLZTi2dJ1gdnh0sHkD6F1VtkblqFnhacFcCMBfc,3418
29
29
  contentctl/helper/link_validator.py,sha256=-XorhxfGtjLynEL1X4hcpRMiyemogf2JEnvLwhHq80c,7139
@@ -33,7 +33,7 @@ contentctl/helper/utils.py,sha256=8ICRvE7DUiNL9BK4Hw71hCLFbd3R2u86OwKeDOdaBTY,19
33
33
  contentctl/input/director.py,sha256=kTqdN_rCzRMn4dR32hPaVyx2llhAxyhJgoGjowhsHzs,10887
34
34
  contentctl/input/new_content_questions.py,sha256=o4prlBoUhEMxqpZukquI9WKbzfFJfYhEF7a8m2q_BEE,5565
35
35
  contentctl/input/yml_reader.py,sha256=hyVUYhx4Ka8C618kP2D_E3sDUKEQGC6ty_QZQArHKd4,1489
36
- contentctl/objects/abstract_security_content_objects/detection_abstract.py,sha256=gra5kZkLFATBQJ-ZR4SNzkZ86MBX3pnOz2RdIl5oOVs,34502
36
+ contentctl/objects/abstract_security_content_objects/detection_abstract.py,sha256=V3pglFS5HYdPURIQFdNlHQfXYYr7-xLClrXiMUsb9rw,34745
37
37
  contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py,sha256=7tv-WEiUUOvZkao272J9l1IvL0y12kJ6SWLsMeWv9VE,9820
38
38
  contentctl/objects/alert_action.py,sha256=E9gjCn5C31h0sN7k90KNe4agRxFFSnMW_Z-Ri_3YQss,1335
39
39
  contentctl/objects/atomic.py,sha256=BP27gP8KHeODp6UazhVFxwDQ64wuJCARGsLfIH34h7U,8768
@@ -42,7 +42,7 @@ contentctl/objects/base_test_result.py,sha256=ZEAC2IUwUrW_-zHoaS7zp-uBBKIVTS8TcM
42
42
  contentctl/objects/baseline.py,sha256=Lb1vJKtDdlDrzWgrdkC9oQao_TnRrOxSwOWHf4trtaU,2150
43
43
  contentctl/objects/baseline_tags.py,sha256=fVhLF-NmisavybB_idu3N0Con0Ymj8clKfRMkWzBB-k,1762
44
44
  contentctl/objects/config.py,sha256=XpCjYIoU4XTM6RL4Nt-YjMX342FJz4R-ATDXJWexHNs,43615
45
- contentctl/objects/constants.py,sha256=1LjiK9A7t0aHHkJz2mrW-DImdW1P98GPssTwmwNNI_M,3468
45
+ contentctl/objects/constants.py,sha256=lfCcr1DsTZvANHj4Ee1_sEV-SebHwAn41-5EvmoEX2E,3537
46
46
  contentctl/objects/correlation_search.py,sha256=QZp1u-dwTZl9hkUOlJdHQ9h4Hp2bDHWWCKtrp3mvIUY,48310
47
47
  contentctl/objects/data_source.py,sha256=aRr6lHu-EtGmi6J2nXKD7i2ozUPtp7X-vDkQiutvD3I,1545
48
48
  contentctl/objects/deployment.py,sha256=Qc6M4yeOvxjqFKR8sfjd4CG06AbVheTOqP1mwqo4t8s,2651
@@ -63,10 +63,10 @@ contentctl/objects/investigation.py,sha256=JRoZxc_qi1fu_VFTRaxOc3B7zzSzCfEURsNzW
63
63
  contentctl/objects/investigation_tags.py,sha256=nFpMRKBVBsW21YW_vy2G1lXaSARX-kfFyrPoCyE77Q8,1280
64
64
  contentctl/objects/lookup.py,sha256=oZwBiHfRRrv2ZXdGyWIJWSWZMpuUbsXydaDDfpenk-4,7219
65
65
  contentctl/objects/macro.py,sha256=9nE-bxkFhtaltHOUCr0luU8jCCthmglHjhKs6Q2YzLU,2684
66
- contentctl/objects/mitre_attack_enrichment.py,sha256=JqSDnKF0-ZTaxUgvhdYNzIAt-7kNaEBvGr_5Bbfdwr8,1072
66
+ contentctl/objects/mitre_attack_enrichment.py,sha256=vhoB0oHlmPs-aDcEYT19BWrylO_hiDxOo5IWP4LBlNk,3293
67
67
  contentctl/objects/notable_action.py,sha256=ValkblBaG-60TF19y_vSnNzoNZ3eg48wIfr0qZxyKTA,1605
68
68
  contentctl/objects/notable_event.py,sha256=ITcwLzeatSGpe8267PYN-EhgqOSoWTfciCBVu8zjOXE,682
69
- contentctl/objects/observable.py,sha256=loEkmo7RPl383Jq-i5BmSnAqpTeh80d6ai7PDeWuxF0,1211
69
+ contentctl/objects/observable.py,sha256=pw0Ehi_KMb7nXzw2kuw1FnCknpD8zDkCAqBTa-M_F28,1313
70
70
  contentctl/objects/playbook.py,sha256=hSYYpdMhctgpp7uwaPciFqu1yuFI4M1NHy1WBBLyvzM,2469
71
71
  contentctl/objects/playbook_tags.py,sha256=NrhTGcgoYSGEZggrfebko0GBOXN9x05IadRUUL_CVfQ,1436
72
72
  contentctl/objects/risk_analysis_action.py,sha256=Glzcq99DAqqOJ2eZYCkUI3R5hA5cZGU0ZuCSinFf2R8,4278
@@ -157,14 +157,14 @@ contentctl/templates/deployments/escu_default_configuration_hunting.yml,sha256=h
157
157
  contentctl/templates/deployments/escu_default_configuration_ttp.yml,sha256=1D-pvzaH1v3_yCZXaY6njmdvV4S2_Ak8uzzCOsnj9XY,548
158
158
  contentctl/templates/detections/application/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
159
159
  contentctl/templates/detections/cloud/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
160
- contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml,sha256=-jp7CC3shnA9Te_0Zw6jLbLT8JnrVQvOfEUkNCQbCNo,3322
160
+ contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml,sha256=tw5_HVqMyx6itht6v2fz6Uqoy3EoIJ_lzVlrRABrMhY,3311
161
161
  contentctl/templates/detections/network/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
162
162
  contentctl/templates/detections/web/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
163
163
  contentctl/templates/macros/security_content_ctime.yml,sha256=Gg1YNllHVsX_YB716H1SJLWzxXZEfuJlnsgB2fuyoHU,159
164
164
  contentctl/templates/macros/security_content_summariesonly.yml,sha256=9BYUxAl2E4Nwh8K19F3AJS8Ka7ceO6ZDBjFiO3l3LY0,162
165
165
  contentctl/templates/stories/cobalt_strike.yml,sha256=rlaXxMN-5k8LnKBLPafBoksyMtlmsPMHPJOjTiMiZ-M,3063
166
- contentctl-4.3.0.dist-info/LICENSE.md,sha256=hQWUayRk-pAiOZbZnuy8djmoZkjKBx8MrCFpW-JiOgo,11344
167
- contentctl-4.3.0.dist-info/METADATA,sha256=YpQM8untSb4LvHmv0IHCjgJP7qM5X_c13n17lJGK_kg,20939
168
- contentctl-4.3.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
169
- contentctl-4.3.0.dist-info/entry_points.txt,sha256=5bjZ2NkbQfSwK47uOnA77yCtjgXhvgxnmCQiynRF_-U,57
170
- contentctl-4.3.0.dist-info/RECORD,,
166
+ contentctl-4.3.2.dist-info/LICENSE.md,sha256=hQWUayRk-pAiOZbZnuy8djmoZkjKBx8MrCFpW-JiOgo,11344
167
+ contentctl-4.3.2.dist-info/METADATA,sha256=AkxYjJ2zP-wRhoyOVMq5cGNJDmA8QFRkRy6FQaIKCbY,20939
168
+ contentctl-4.3.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
169
+ contentctl-4.3.2.dist-info/entry_points.txt,sha256=5bjZ2NkbQfSwK47uOnA77yCtjgXhvgxnmCQiynRF_-U,57
170
+ contentctl-4.3.2.dist-info/RECORD,,