contentctl 3.6.0__py3-none-any.whl → 4.0.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.
- contentctl/actions/build.py +89 -0
- contentctl/actions/detection_testing/DetectionTestingManager.py +48 -49
- contentctl/actions/detection_testing/GitService.py +148 -230
- contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +14 -24
- contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +43 -17
- contentctl/actions/detection_testing/views/DetectionTestingView.py +3 -2
- contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +0 -8
- contentctl/actions/doc_gen.py +1 -1
- contentctl/actions/initialize.py +28 -65
- contentctl/actions/inspect.py +260 -0
- contentctl/actions/new_content.py +106 -13
- contentctl/actions/release_notes.py +168 -144
- contentctl/actions/reporting.py +24 -13
- contentctl/actions/test.py +39 -20
- contentctl/actions/validate.py +25 -48
- contentctl/contentctl.py +196 -754
- contentctl/enrichments/attack_enrichment.py +69 -19
- contentctl/enrichments/cve_enrichment.py +28 -13
- contentctl/helper/link_validator.py +24 -26
- contentctl/helper/utils.py +7 -3
- contentctl/input/director.py +139 -201
- contentctl/input/new_content_questions.py +63 -61
- contentctl/input/sigma_converter.py +1 -2
- contentctl/input/ssa_detection_builder.py +16 -7
- contentctl/input/yml_reader.py +4 -3
- contentctl/objects/abstract_security_content_objects/detection_abstract.py +487 -154
- contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +155 -51
- contentctl/objects/alert_action.py +40 -0
- contentctl/objects/atomic.py +212 -0
- contentctl/objects/baseline.py +44 -43
- contentctl/objects/baseline_tags.py +69 -20
- contentctl/objects/config.py +857 -125
- contentctl/objects/constants.py +0 -1
- contentctl/objects/correlation_search.py +1 -1
- contentctl/objects/data_source.py +2 -4
- contentctl/objects/deployment.py +61 -21
- contentctl/objects/deployment_email.py +2 -2
- contentctl/objects/deployment_notable.py +4 -4
- contentctl/objects/deployment_phantom.py +2 -2
- contentctl/objects/deployment_rba.py +3 -4
- contentctl/objects/deployment_scheduling.py +2 -3
- contentctl/objects/deployment_slack.py +2 -2
- contentctl/objects/detection.py +1 -5
- contentctl/objects/detection_tags.py +210 -119
- contentctl/objects/enums.py +312 -24
- contentctl/objects/integration_test.py +1 -1
- contentctl/objects/integration_test_result.py +0 -2
- contentctl/objects/investigation.py +62 -53
- contentctl/objects/investigation_tags.py +30 -6
- contentctl/objects/lookup.py +80 -31
- contentctl/objects/macro.py +29 -45
- contentctl/objects/mitre_attack_enrichment.py +29 -5
- contentctl/objects/observable.py +3 -7
- contentctl/objects/playbook.py +60 -30
- contentctl/objects/playbook_tags.py +45 -8
- contentctl/objects/security_content_object.py +1 -5
- contentctl/objects/ssa_detection.py +8 -4
- contentctl/objects/ssa_detection_tags.py +19 -26
- contentctl/objects/story.py +142 -44
- contentctl/objects/story_tags.py +46 -33
- contentctl/objects/unit_test.py +7 -2
- contentctl/objects/unit_test_attack_data.py +10 -19
- contentctl/objects/unit_test_baseline.py +1 -1
- contentctl/objects/unit_test_old.py +4 -3
- contentctl/objects/unit_test_result.py +5 -3
- contentctl/objects/unit_test_ssa.py +31 -0
- contentctl/output/api_json_output.py +202 -130
- contentctl/output/attack_nav_output.py +20 -9
- contentctl/output/attack_nav_writer.py +3 -3
- contentctl/output/ba_yml_output.py +3 -3
- contentctl/output/conf_output.py +125 -391
- contentctl/output/conf_writer.py +169 -31
- contentctl/output/jinja_writer.py +2 -2
- contentctl/output/json_writer.py +17 -5
- contentctl/output/new_content_yml_output.py +8 -7
- contentctl/output/svg_output.py +17 -27
- contentctl/output/templates/analyticstories_detections.j2 +8 -4
- contentctl/output/templates/analyticstories_investigations.j2 +1 -1
- contentctl/output/templates/analyticstories_stories.j2 +6 -6
- contentctl/output/templates/app.conf.j2 +2 -2
- contentctl/output/templates/app.manifest.j2 +2 -2
- contentctl/output/templates/detection_coverage.j2 +6 -8
- contentctl/output/templates/doc_detection_page.j2 +2 -2
- contentctl/output/templates/doc_detections.j2 +2 -2
- contentctl/output/templates/doc_stories.j2 +1 -1
- contentctl/output/templates/es_investigations_investigations.j2 +1 -1
- contentctl/output/templates/es_investigations_stories.j2 +1 -1
- contentctl/output/templates/header.j2 +2 -1
- contentctl/output/templates/macros.j2 +6 -10
- contentctl/output/templates/savedsearches_baselines.j2 +5 -5
- contentctl/output/templates/savedsearches_detections.j2 +36 -33
- contentctl/output/templates/savedsearches_investigations.j2 +4 -4
- contentctl/output/templates/transforms.j2 +4 -4
- contentctl/output/yml_writer.py +2 -2
- contentctl/templates/app_template/README.md +7 -0
- contentctl/{output/templates/splunk_app → templates/app_template}/default/data/ui/nav/default.xml +1 -0
- contentctl/templates/app_template/lookups/mitre_enrichment.csv +638 -0
- contentctl/templates/deployments/{00_default_anomaly.yml → escu_default_configuration_anomaly.yml} +1 -2
- contentctl/templates/deployments/{00_default_baseline.yml → escu_default_configuration_baseline.yml} +1 -2
- contentctl/templates/deployments/{00_default_correlation.yml → escu_default_configuration_correlation.yml} +2 -2
- contentctl/templates/deployments/{00_default_hunting.yml → escu_default_configuration_hunting.yml} +2 -2
- contentctl/templates/deployments/{00_default_ttp.yml → escu_default_configuration_ttp.yml} +1 -2
- contentctl/templates/detections/anomalous_usage_of_7zip.yml +0 -1
- contentctl/templates/stories/cobalt_strike.yml +0 -1
- {contentctl-3.6.0.dist-info → contentctl-4.0.2.dist-info}/METADATA +36 -15
- contentctl-4.0.2.dist-info/RECORD +168 -0
- contentctl/actions/detection_testing/DataManipulation.py +0 -149
- contentctl/actions/generate.py +0 -91
- contentctl/helper/config_handler.py +0 -75
- contentctl/input/baseline_builder.py +0 -66
- contentctl/input/basic_builder.py +0 -58
- contentctl/input/detection_builder.py +0 -370
- contentctl/input/investigation_builder.py +0 -42
- contentctl/input/new_content_generator.py +0 -95
- contentctl/input/playbook_builder.py +0 -68
- contentctl/input/story_builder.py +0 -106
- contentctl/objects/app.py +0 -214
- contentctl/objects/repo_config.py +0 -163
- contentctl/objects/test_config.py +0 -630
- contentctl/output/templates/macros_detections.j2 +0 -7
- contentctl/output/templates/splunk_app/README.md +0 -7
- contentctl-3.6.0.dist-info/RECORD +0 -176
- /contentctl/{output/templates/splunk_app → templates/app_template}/README/essoc_story_detail.txt +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/README/essoc_summary.txt +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/README/essoc_usage_dashboard.txt +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/default/analytic_stories.conf +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/default/app.conf +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/default/commands.conf +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/default/content-version.conf +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/default/data/ui/views/escu_summary.xml +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/default/data/ui/views/feedback.xml +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/default/distsearch.conf +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/default/usage_searches.conf +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/default/use_case_library.conf +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/metadata/default.meta +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/static/appIcon.png +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/static/appIconAlt.png +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/static/appIconAlt_2x.png +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/static/appIcon_2x.png +0 -0
- {contentctl-3.6.0.dist-info → contentctl-4.0.2.dist-info}/LICENSE.md +0 -0
- {contentctl-3.6.0.dist-info → contentctl-4.0.2.dist-info}/WHEEL +0 -0
- {contentctl-3.6.0.dist-info → contentctl-4.0.2.dist-info}/entry_points.txt +0 -0
|
@@ -1,174 +1,246 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import json
|
|
3
|
-
|
|
3
|
+
import pathlib
|
|
4
4
|
|
|
5
5
|
from contentctl.output.json_writer import JsonWriter
|
|
6
6
|
from contentctl.objects.enums import SecurityContentType
|
|
7
|
+
from contentctl.objects.abstract_security_content_objects.security_content_object_abstract import (
|
|
8
|
+
SecurityContentObject_Abstract,
|
|
9
|
+
)
|
|
7
10
|
|
|
8
|
-
# Maximum Lambda Request Response Limit is 6MB
|
|
9
|
-
# https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html
|
|
10
|
-
# Note that if you are not using AWS Lambda, this file size may be increased.
|
|
11
|
-
AWS_LAMBDA_LIMIT = 1024 * 1024 * 6 - 1
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
class ApiJsonOutput:
|
|
14
|
+
|
|
15
15
|
def writeObjects(
|
|
16
|
-
self,
|
|
16
|
+
self,
|
|
17
|
+
objects: list[SecurityContentObject_Abstract],
|
|
18
|
+
output_path: pathlib.Path,
|
|
19
|
+
app_label:str = "ESCU",
|
|
20
|
+
contentType: SecurityContentType = None
|
|
17
21
|
) -> None:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
for
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
},
|
|
22
|
+
"""#Serialize all objects
|
|
23
|
+
try:
|
|
24
|
+
for obj in objects:
|
|
25
|
+
|
|
26
|
+
serialized_objects.append(obj.model_dump())
|
|
27
|
+
except Exception as e:
|
|
28
|
+
raise Exception(f"Error serializing object with name '{obj.name}' and type '{type(obj).__name__}': '{str(e)}'")
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
if contentType == SecurityContentType.detections:
|
|
32
|
+
detections = [
|
|
33
|
+
detection.model_dump(
|
|
34
|
+
include=set(
|
|
35
|
+
[
|
|
36
|
+
"name",
|
|
37
|
+
"author",
|
|
38
|
+
"date",
|
|
39
|
+
"version",
|
|
40
|
+
"id",
|
|
41
|
+
"description",
|
|
42
|
+
"tags",
|
|
43
|
+
"search",
|
|
44
|
+
"how_to_implement",
|
|
45
|
+
"known_false_positives",
|
|
46
|
+
"references",
|
|
47
|
+
"datamodel",
|
|
48
|
+
"macros",
|
|
49
|
+
"lookups",
|
|
50
|
+
"source",
|
|
51
|
+
"nes_fields",
|
|
52
|
+
]
|
|
50
53
|
)
|
|
51
54
|
)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
for detection in objects
|
|
56
|
+
]
|
|
57
|
+
#Only a subset of macro fields are required:
|
|
58
|
+
# for detection in detections:
|
|
59
|
+
# new_macros = []
|
|
60
|
+
# for macro in detection.get("macros",[]):
|
|
61
|
+
# new_macro_fields = {}
|
|
62
|
+
# new_macro_fields["name"] = macro.get("name")
|
|
63
|
+
# new_macro_fields["definition"] = macro.get("definition")
|
|
64
|
+
# new_macro_fields["description"] = macro.get("description")
|
|
65
|
+
# if len(macro.get("arguments", [])) > 0:
|
|
66
|
+
# new_macro_fields["arguments"] = macro.get("arguments")
|
|
67
|
+
# new_macros.append(new_macro_fields)
|
|
68
|
+
# detection["macros"] = new_macros
|
|
69
|
+
# del()
|
|
70
|
+
|
|
71
|
+
|
|
59
72
|
JsonWriter.writeJsonObject(
|
|
60
|
-
os.path.join(output_path, "detections.json"),
|
|
73
|
+
os.path.join(output_path, "detections.json"), "detections", detections
|
|
61
74
|
)
|
|
62
75
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
uniques: set[str] = set()
|
|
73
|
-
for obj in obj_array:
|
|
74
|
-
if obj.get("arguments", None) != None:
|
|
75
|
-
uniques.add(json.dumps(obj, sort_keys=True))
|
|
76
|
-
else:
|
|
77
|
-
obj.pop("arguments")
|
|
78
|
-
uniques.add(json.dumps(obj, sort_keys=True))
|
|
79
|
-
|
|
80
|
-
obj_array = []
|
|
81
|
-
for item in uniques:
|
|
82
|
-
obj_array.append(json.loads(item))
|
|
83
|
-
|
|
84
|
-
for obj in obj_array:
|
|
85
|
-
if "file_path" in obj:
|
|
86
|
-
del obj["file_path"]
|
|
87
|
-
|
|
76
|
+
elif contentType == SecurityContentType.macros:
|
|
77
|
+
macros = [
|
|
78
|
+
macro.model_dump(include=set(["definition", "description", "name"]))
|
|
79
|
+
for macro in objects
|
|
80
|
+
]
|
|
81
|
+
for macro in macros:
|
|
82
|
+
for k in ["author", "date","version","id","references"]:
|
|
83
|
+
if k in macro:
|
|
84
|
+
del(macro[k])
|
|
88
85
|
JsonWriter.writeJsonObject(
|
|
89
|
-
os.path.join(output_path, "macros.json"),
|
|
86
|
+
os.path.join(output_path, "macros.json"), "macros", macros
|
|
90
87
|
)
|
|
91
88
|
|
|
92
|
-
elif
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
89
|
+
elif contentType == SecurityContentType.stories:
|
|
90
|
+
stories = [
|
|
91
|
+
story.model_dump(
|
|
92
|
+
include=set(
|
|
93
|
+
[
|
|
94
|
+
"name",
|
|
95
|
+
"author",
|
|
96
|
+
"date",
|
|
97
|
+
"version",
|
|
98
|
+
"id",
|
|
99
|
+
"description",
|
|
100
|
+
"narrative",
|
|
101
|
+
"references",
|
|
102
|
+
"tags",
|
|
103
|
+
"detections_names",
|
|
104
|
+
"investigation_names",
|
|
105
|
+
"baseline_names",
|
|
106
|
+
"detections",
|
|
107
|
+
]
|
|
100
108
|
)
|
|
101
109
|
)
|
|
110
|
+
for story in objects
|
|
111
|
+
]
|
|
112
|
+
# Only get certain fields from detections
|
|
113
|
+
for story in stories:
|
|
114
|
+
# Only use a small subset of fields from the detection
|
|
115
|
+
story["detections"] = [
|
|
116
|
+
{
|
|
117
|
+
"name": detection["name"],
|
|
118
|
+
"source": detection["source"],
|
|
119
|
+
"type": detection["type"],
|
|
120
|
+
"tags": detection["tags"].get("mitre_attack_enrichments", []),
|
|
121
|
+
}
|
|
122
|
+
for detection in story["detections"]
|
|
123
|
+
]
|
|
124
|
+
story["detection_names"] = [f"{app_label} - {name} - Rule" for name in story["detection_names"]]
|
|
125
|
+
|
|
102
126
|
|
|
103
127
|
JsonWriter.writeJsonObject(
|
|
104
|
-
os.path.join(output_path, "stories.json"),
|
|
128
|
+
os.path.join(output_path, "stories.json"), "stories", stories
|
|
105
129
|
)
|
|
106
130
|
|
|
107
|
-
elif
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
131
|
+
elif contentType == SecurityContentType.baselines:
|
|
132
|
+
try:
|
|
133
|
+
baselines = [
|
|
134
|
+
baseline.model_dump(
|
|
135
|
+
include=set(
|
|
136
|
+
[
|
|
137
|
+
"name",
|
|
138
|
+
"author",
|
|
139
|
+
"date",
|
|
140
|
+
"version",
|
|
141
|
+
"id",
|
|
142
|
+
"description",
|
|
143
|
+
"type",
|
|
144
|
+
"datamodel",
|
|
145
|
+
"search",
|
|
146
|
+
"how_to_implement",
|
|
147
|
+
"known_false_positives",
|
|
148
|
+
"references",
|
|
149
|
+
"tags",
|
|
150
|
+
]
|
|
151
|
+
)
|
|
118
152
|
)
|
|
119
|
-
|
|
153
|
+
for baseline in objects
|
|
154
|
+
]
|
|
155
|
+
except Exception as e:
|
|
156
|
+
print(e)
|
|
157
|
+
print('wait')
|
|
120
158
|
|
|
121
159
|
JsonWriter.writeJsonObject(
|
|
122
|
-
|
|
123
|
-
|
|
160
|
+
os.path.join(output_path, "baselines.json"), "baselines", baselines
|
|
161
|
+
)
|
|
124
162
|
|
|
125
|
-
elif
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
"
|
|
133
|
-
|
|
163
|
+
elif contentType == SecurityContentType.investigations:
|
|
164
|
+
investigations = [
|
|
165
|
+
investigation.model_dump(
|
|
166
|
+
include=set(
|
|
167
|
+
[
|
|
168
|
+
"name",
|
|
169
|
+
"author",
|
|
170
|
+
"date",
|
|
171
|
+
"version",
|
|
172
|
+
"id",
|
|
173
|
+
"description",
|
|
174
|
+
"type",
|
|
175
|
+
"datamodel",
|
|
176
|
+
"search",
|
|
177
|
+
"how_to_implemnet",
|
|
178
|
+
"known_false_positives",
|
|
179
|
+
"references",
|
|
180
|
+
"inputs",
|
|
181
|
+
"tags",
|
|
182
|
+
"lowercase_name",
|
|
183
|
+
]
|
|
134
184
|
)
|
|
135
185
|
)
|
|
136
|
-
|
|
186
|
+
for investigation in objects
|
|
187
|
+
]
|
|
137
188
|
JsonWriter.writeJsonObject(
|
|
138
189
|
os.path.join(output_path, "response_tasks.json"),
|
|
139
|
-
|
|
190
|
+
"response_tasks",
|
|
191
|
+
investigations,
|
|
140
192
|
)
|
|
141
193
|
|
|
142
|
-
elif
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
"
|
|
150
|
-
|
|
194
|
+
elif contentType == SecurityContentType.lookups:
|
|
195
|
+
lookups = [
|
|
196
|
+
lookup.model_dump(
|
|
197
|
+
include=set(
|
|
198
|
+
[
|
|
199
|
+
"name",
|
|
200
|
+
"description",
|
|
201
|
+
"collection",
|
|
202
|
+
"fields_list",
|
|
203
|
+
"filename",
|
|
204
|
+
"default_match",
|
|
205
|
+
"match_type",
|
|
206
|
+
"min_matches",
|
|
207
|
+
"case_sensitive_match",
|
|
208
|
+
]
|
|
151
209
|
)
|
|
152
210
|
)
|
|
153
|
-
|
|
211
|
+
for lookup in objects
|
|
212
|
+
]
|
|
213
|
+
for lookup in lookups:
|
|
214
|
+
for k in ["author","date","version","id","references"]:
|
|
215
|
+
if k in lookup:
|
|
216
|
+
del(lookup[k])
|
|
154
217
|
JsonWriter.writeJsonObject(
|
|
155
|
-
os.path.join(output_path, "lookups.json"),
|
|
218
|
+
os.path.join(output_path, "lookups.json"), "lookups", lookups
|
|
156
219
|
)
|
|
157
220
|
|
|
158
|
-
elif
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
"
|
|
167
|
-
|
|
221
|
+
elif contentType == SecurityContentType.deployments:
|
|
222
|
+
deployments = [
|
|
223
|
+
deployment.model_dump(
|
|
224
|
+
include=set(
|
|
225
|
+
[
|
|
226
|
+
"name",
|
|
227
|
+
"author",
|
|
228
|
+
"date",
|
|
229
|
+
"version",
|
|
230
|
+
"id",
|
|
231
|
+
"description",
|
|
232
|
+
"scheduling",
|
|
233
|
+
"rba",
|
|
234
|
+
"tags"
|
|
235
|
+
]
|
|
168
236
|
)
|
|
169
237
|
)
|
|
170
|
-
|
|
238
|
+
for deployment in objects
|
|
239
|
+
]
|
|
240
|
+
#references are not to be included, but have been deleted in the
|
|
241
|
+
#model_serialization logic
|
|
171
242
|
JsonWriter.writeJsonObject(
|
|
172
243
|
os.path.join(output_path, "deployments.json"),
|
|
173
|
-
|
|
174
|
-
|
|
244
|
+
"deployments",
|
|
245
|
+
deployments,
|
|
246
|
+
)
|
|
@@ -1,28 +1,39 @@
|
|
|
1
1
|
import os
|
|
2
|
+
from typing import List,Union
|
|
3
|
+
import pathlib
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
from contentctl.objects.enums import SecurityContentType
|
|
5
|
+
from contentctl.objects.detection import Detection
|
|
5
6
|
from contentctl.output.attack_nav_writer import AttackNavWriter
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class AttackNavOutput():
|
|
9
10
|
|
|
10
|
-
def writeObjects(self,
|
|
11
|
-
techniques =
|
|
11
|
+
def writeObjects(self, detections: List[Detection], output_path: pathlib.Path) -> None:
|
|
12
|
+
techniques:dict[str,dict[str,Union[List[str],int]]] = {}
|
|
13
|
+
for detection in detections:
|
|
14
|
+
for tactic in detection.tags.mitre_attack_id:
|
|
15
|
+
if tactic not in techniques:
|
|
16
|
+
techniques[tactic] = {'score':0,'file_paths':[]}
|
|
17
|
+
|
|
18
|
+
detection_url = f"https://github.com/splunk/security_content/blob/develop/detections/{detection.source}/{detection.file_path.name}"
|
|
19
|
+
techniques[tactic]['score'] += 1
|
|
20
|
+
techniques[tactic]['file_paths'].append(detection_url)
|
|
21
|
+
|
|
22
|
+
'''
|
|
12
23
|
for detection in objects:
|
|
13
24
|
if detection.tags.mitre_attack_enrichments:
|
|
14
25
|
for mitre_attack_enrichment in detection.tags.mitre_attack_enrichments:
|
|
15
26
|
if not mitre_attack_enrichment.mitre_attack_id in techniques:
|
|
16
27
|
techniques[mitre_attack_enrichment.mitre_attack_id] = {
|
|
17
28
|
'score': 1,
|
|
18
|
-
'file_paths': ['https://github.com/splunk/security_content/blob/develop/detections/' + detection.
|
|
29
|
+
'file_paths': ['https://github.com/splunk/security_content/blob/develop/detections/' + detection.getSource() + '/' + self.convertNameToFileName(detection.name)]
|
|
19
30
|
}
|
|
20
31
|
else:
|
|
21
32
|
techniques[mitre_attack_enrichment.mitre_attack_id]['score'] = techniques[mitre_attack_enrichment.mitre_attack_id]['score'] + 1
|
|
22
|
-
techniques[mitre_attack_enrichment.mitre_attack_id]['file_paths'].append('https://github.com/splunk/security_content/blob/develop/detections/' + detection.
|
|
23
|
-
|
|
24
|
-
AttackNavWriter.writeAttackNavFile(techniques,
|
|
25
|
-
|
|
33
|
+
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))
|
|
34
|
+
'''
|
|
35
|
+
AttackNavWriter.writeAttackNavFile(techniques, output_path / 'coverage.json')
|
|
36
|
+
|
|
26
37
|
|
|
27
38
|
def convertNameToFileName(self, name: str):
|
|
28
39
|
file_name = name \
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import json
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
from typing import Union, List
|
|
4
|
+
import pathlib
|
|
5
5
|
VERSION = "4.3"
|
|
6
6
|
NAME = "Detection Coverage"
|
|
7
7
|
DESCRIPTION = "security_content detection coverage"
|
|
@@ -11,7 +11,7 @@ DOMAIN = "mitre-enterprise"
|
|
|
11
11
|
class AttackNavWriter():
|
|
12
12
|
|
|
13
13
|
@staticmethod
|
|
14
|
-
def writeAttackNavFile(mitre_techniques : dict, output_path :
|
|
14
|
+
def writeAttackNavFile(mitre_techniques : dict[str,dict[str,Union[List[str],int]]], output_path : pathlib.Path) -> None:
|
|
15
15
|
max_count = 0
|
|
16
16
|
for technique_id in mitre_techniques.keys():
|
|
17
17
|
if mitre_techniques[technique_id]['score'] > max_count:
|
|
@@ -21,7 +21,7 @@ class BAYmlOutput():
|
|
|
21
21
|
YmlWriter.writeYmlFile(file_path, object)
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
def writeObjects(self, objects: list, output_path: str,
|
|
24
|
+
def writeObjects(self, objects: list, output_path: str, contentType: SecurityContentType = None) -> None:
|
|
25
25
|
for obj in objects:
|
|
26
26
|
file_name = "ssa___" + self.convertNameToFileName(obj.name, obj.tags)
|
|
27
27
|
if self.isComplexBARule(obj.search):
|
|
@@ -46,8 +46,9 @@ class BAYmlOutput():
|
|
|
46
46
|
}
|
|
47
47
|
test_dict["tests"][0]["name"] = obj.name
|
|
48
48
|
for count in range(len(test_dict["tests"][0]["attack_data"])):
|
|
49
|
-
a = urlparse(test_dict["tests"][0]["attack_data"][count]["data"])
|
|
49
|
+
a = urlparse(str(test_dict["tests"][0]["attack_data"][count]["data"]))
|
|
50
50
|
test_dict["tests"][0]["attack_data"][count]["file_name"] = os.path.basename(a.path)
|
|
51
|
+
|
|
51
52
|
test = UnitTestOld.parse_obj(test_dict)
|
|
52
53
|
|
|
53
54
|
obj.test = test
|
|
@@ -150,4 +151,3 @@ class BAYmlOutput():
|
|
|
150
151
|
def isComplexBARule(self, search):
|
|
151
152
|
return re.findall("stats|first_time_event|adaptive_threshold", search)
|
|
152
153
|
|
|
153
|
-
|