contentctl 4.2.5__py3-none-any.whl → 4.3.0__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 +0 -14
- contentctl/actions/validate.py +0 -1
- contentctl/input/director.py +9 -44
- contentctl/objects/abstract_security_content_objects/detection_abstract.py +56 -74
- contentctl/objects/config.py +0 -2
- {contentctl-4.2.5.dist-info → contentctl-4.3.0.dist-info}/METADATA +15 -5
- {contentctl-4.2.5.dist-info → contentctl-4.3.0.dist-info}/RECORD +10 -16
- contentctl/actions/convert.py +0 -25
- contentctl/input/backend_splunk_ba.py +0 -144
- contentctl/input/sigma_converter.py +0 -436
- contentctl/input/ssa_detection_builder.py +0 -169
- contentctl/output/ba_yml_output.py +0 -153
- contentctl/output/finding_report_writer.py +0 -91
- {contentctl-4.2.5.dist-info → contentctl-4.3.0.dist-info}/LICENSE.md +0 -0
- {contentctl-4.2.5.dist-info → contentctl-4.3.0.dist-info}/WHEEL +0 -0
- {contentctl-4.2.5.dist-info → contentctl-4.3.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
import re
|
|
3
|
-
import os
|
|
4
|
-
|
|
5
|
-
from pydantic import ValidationError
|
|
6
|
-
from typing import List
|
|
7
|
-
from contentctl.input.yml_reader import YmlReader
|
|
8
|
-
from contentctl.objects.detection import Detection
|
|
9
|
-
from contentctl.objects.security_content_object import SecurityContentObject
|
|
10
|
-
from contentctl.objects.macro import Macro
|
|
11
|
-
from contentctl.objects.mitre_attack_enrichment import MitreAttackEnrichment
|
|
12
|
-
from contentctl.enrichments.cve_enrichment import CveEnrichment
|
|
13
|
-
from contentctl.enrichments.splunk_app_enrichment import SplunkAppEnrichment
|
|
14
|
-
from contentctl.objects.ssa_detection import SSADetection
|
|
15
|
-
from contentctl.objects.constants import *
|
|
16
|
-
from contentctl.enrichments.attack_enrichment import AttackEnrichment
|
|
17
|
-
|
|
18
|
-
class SSADetectionBuilder():
|
|
19
|
-
security_content_obj : SSADetection
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def setObject(self, path: str) -> None:
|
|
23
|
-
yml_dict = YmlReader.load_file(path)
|
|
24
|
-
self.security_content_obj = SSADetection.parse_obj(yml_dict)
|
|
25
|
-
self.security_content_obj.source = os.path.split(os.path.dirname(self.security_content_obj.file_path))[-1]
|
|
26
|
-
|
|
27
|
-
def addProvidingTechnologies(self) -> None:
|
|
28
|
-
if self.security_content_obj:
|
|
29
|
-
if 'Endpoint' in str(self.security_content_obj.search):
|
|
30
|
-
self.security_content_obj.providing_technologies = ["Sysmon", "Microsoft Windows","Carbon Black Response","CrowdStrike Falcon", "Symantec Endpoint Protection"]
|
|
31
|
-
if "`cloudtrail`" in str(self.security_content_obj.search):
|
|
32
|
-
self.security_content_obj.providing_technologies = ["Amazon Web Services - Cloudtrail"]
|
|
33
|
-
if '`wineventlog_security`' in self.security_content_obj.search or '`powershell`' in self.security_content_obj.search:
|
|
34
|
-
self.security_content_obj.providing_technologies = ["Microsoft Windows"]
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def addMappings(self) -> None:
|
|
38
|
-
if self.security_content_obj:
|
|
39
|
-
keys = ['mitre_attack', 'kill_chain_phases', 'cis20', 'nist']
|
|
40
|
-
mappings = {}
|
|
41
|
-
for key in keys:
|
|
42
|
-
if key == 'mitre_attack':
|
|
43
|
-
if getattr(self.security_content_obj.tags, 'mitre_attack_id'):
|
|
44
|
-
mappings[key] = getattr(self.security_content_obj.tags, 'mitre_attack_id')
|
|
45
|
-
elif getattr(self.security_content_obj.tags, key):
|
|
46
|
-
mappings[key] = getattr(self.security_content_obj.tags, key)
|
|
47
|
-
self.security_content_obj.mappings = mappings
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def addAnnotations(self) -> None:
|
|
51
|
-
if self.security_content_obj:
|
|
52
|
-
annotations = {}
|
|
53
|
-
annotation_keys = ['mitre_attack', 'kill_chain_phases', 'cis20', 'nist',
|
|
54
|
-
'analytic_story', 'context', 'impact', 'confidence', 'cve']
|
|
55
|
-
for key in annotation_keys:
|
|
56
|
-
if key == 'mitre_attack':
|
|
57
|
-
if getattr(self.security_content_obj.tags, 'mitre_attack_id'):
|
|
58
|
-
annotations[key] = getattr(self.security_content_obj.tags, 'mitre_attack_id')
|
|
59
|
-
try:
|
|
60
|
-
if getattr(self.security_content_obj.tags, key):
|
|
61
|
-
annotations[key] = getattr(self.security_content_obj.tags, key)
|
|
62
|
-
except AttributeError as e:
|
|
63
|
-
continue
|
|
64
|
-
self.security_content_obj.annotations = annotations
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def addUnitTest(self) -> None:
|
|
68
|
-
if self.security_content_obj:
|
|
69
|
-
if self.security_content_obj.tests:
|
|
70
|
-
self.security_content_obj.test = self.security_content_obj.tests[0]
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def addMitreAttackEnrichment(self, attack_enrichment: dict) -> None:
|
|
74
|
-
if self.security_content_obj:
|
|
75
|
-
if attack_enrichment:
|
|
76
|
-
if self.security_content_obj.tags.mitre_attack_id:
|
|
77
|
-
self.security_content_obj.tags.mitre_attack_enrichments = []
|
|
78
|
-
|
|
79
|
-
for mitre_attack_id in self.security_content_obj.tags.mitre_attack_id:
|
|
80
|
-
if mitre_attack_id in attack_enrichment:
|
|
81
|
-
mitre_attack_enrichment = MitreAttackEnrichment(
|
|
82
|
-
mitre_attack_id = mitre_attack_id,
|
|
83
|
-
mitre_attack_technique = attack_enrichment[mitre_attack_id]["technique"],
|
|
84
|
-
mitre_attack_tactics = sorted(attack_enrichment[mitre_attack_id]["tactics"]),
|
|
85
|
-
mitre_attack_groups = sorted(attack_enrichment[mitre_attack_id]["groups"])
|
|
86
|
-
)
|
|
87
|
-
self.security_content_obj.tags.mitre_attack_enrichments.append(mitre_attack_enrichment)
|
|
88
|
-
else:
|
|
89
|
-
#print("mitre_attack_id " + mitre_attack_id + " doesn't exist for detecction " + self.security_content_obj.name)
|
|
90
|
-
raise ValueError("mitre_attack_id " + mitre_attack_id + " doesn't exist for detection " + self.security_content_obj.name)
|
|
91
|
-
def addMitreAttackEnrichmentNew(self, attack_enrichment: AttackEnrichment) -> None:
|
|
92
|
-
# We skip enriching if configured to do so
|
|
93
|
-
if attack_enrichment.use_enrichment:
|
|
94
|
-
if self.security_content_obj and self.security_content_obj.tags.mitre_attack_id:
|
|
95
|
-
self.security_content_obj.tags.mitre_attack_enrichments = []
|
|
96
|
-
for mitre_attack_id in self.security_content_obj.tags.mitre_attack_id:
|
|
97
|
-
enrichment_obj = attack_enrichment.getEnrichmentByMitreID(mitre_attack_id)
|
|
98
|
-
if enrichment_obj is not None:
|
|
99
|
-
self.security_content_obj.tags.mitre_attack_enrichments.append(enrichment_obj)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def addCIS(self) -> None:
|
|
104
|
-
if self.security_content_obj:
|
|
105
|
-
if self.security_content_obj.tags.security_domain == "network":
|
|
106
|
-
self.security_content_obj.tags.cis20 = ["CIS 13"]
|
|
107
|
-
else:
|
|
108
|
-
self.security_content_obj.tags.cis20 = ["CIS 10"]
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def addKillChainPhase(self) -> None:
|
|
112
|
-
if self.security_content_obj:
|
|
113
|
-
if not self.security_content_obj.tags.kill_chain_phases:
|
|
114
|
-
kill_chain_phases = list()
|
|
115
|
-
if self.security_content_obj.tags.mitre_attack_enrichments:
|
|
116
|
-
for mitre_attack_enrichment in self.security_content_obj.tags.mitre_attack_enrichments:
|
|
117
|
-
for mitre_attack_tactic in mitre_attack_enrichment.mitre_attack_tactics:
|
|
118
|
-
kill_chain_phases.append(ATTACK_TACTICS_KILLCHAIN_MAPPING[mitre_attack_tactic])
|
|
119
|
-
self.security_content_obj.tags.kill_chain_phases = list(dict.fromkeys(kill_chain_phases))
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def addNist(self) -> None:
|
|
123
|
-
if self.security_content_obj:
|
|
124
|
-
if self.security_content_obj.type == "TTP":
|
|
125
|
-
self.security_content_obj.tags.nist = ["DE.CM"]
|
|
126
|
-
else:
|
|
127
|
-
self.security_content_obj.tags.nist = ["DE.AE"]
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def addDatamodel(self) -> None:
|
|
131
|
-
if self.security_content_obj:
|
|
132
|
-
self.security_content_obj.datamodel = []
|
|
133
|
-
data_models = [
|
|
134
|
-
"Authentication",
|
|
135
|
-
"Change",
|
|
136
|
-
"Change_Analysis",
|
|
137
|
-
"Email",
|
|
138
|
-
"Endpoint",
|
|
139
|
-
"Network_Resolution",
|
|
140
|
-
"Network_Sessions",
|
|
141
|
-
"Network_Traffic",
|
|
142
|
-
"Risk",
|
|
143
|
-
"Splunk_Audit",
|
|
144
|
-
"UEBA",
|
|
145
|
-
"Updates",
|
|
146
|
-
"Vulnerabilities",
|
|
147
|
-
"Web"
|
|
148
|
-
]
|
|
149
|
-
for data_model in data_models:
|
|
150
|
-
if data_model in self.security_content_obj.search:
|
|
151
|
-
self.security_content_obj.datamodel.append(data_model)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def addRBA(self) -> None:
|
|
155
|
-
if self.security_content_obj:
|
|
156
|
-
if self.security_content_obj.tags.risk_score >= 80:
|
|
157
|
-
self.security_content_obj.tags.risk_severity = 'high'
|
|
158
|
-
elif (self.security_content_obj.tags.risk_score >= 50 and self.security_content_obj.tags.risk_score <= 79):
|
|
159
|
-
self.security_content_obj.tags.risk_severity = 'medium'
|
|
160
|
-
else:
|
|
161
|
-
self.security_content_obj.tags.risk_severity = 'low'
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def reset(self) -> None:
|
|
165
|
-
self.security_content_obj = None
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def getObject(self) -> SSADetection:
|
|
169
|
-
return self.security_content_obj
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import re
|
|
3
|
-
|
|
4
|
-
from urllib.parse import urlparse
|
|
5
|
-
|
|
6
|
-
from contentctl.output.yml_writer import YmlWriter
|
|
7
|
-
from contentctl.objects.enums import SecurityContentType
|
|
8
|
-
from contentctl.output.finding_report_writer import FindingReportObject
|
|
9
|
-
from contentctl.objects.unit_test_old import UnitTestOld
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class BAYmlOutput():
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def writeObjectsInPlace(self, objects: list) -> None:
|
|
16
|
-
for object in objects:
|
|
17
|
-
file_path = object['file_path']
|
|
18
|
-
object.pop('file_path')
|
|
19
|
-
object.pop('deprecated')
|
|
20
|
-
object.pop('experimental')
|
|
21
|
-
YmlWriter.writeYmlFile(file_path, object)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def writeObjects(self, objects: list, output_path: str, contentType: SecurityContentType = None) -> None:
|
|
25
|
-
for obj in objects:
|
|
26
|
-
file_name = "ssa___" + self.convertNameToFileName(obj.name, obj.tags)
|
|
27
|
-
if self.isComplexBARule(obj.search):
|
|
28
|
-
file_path = os.path.join(output_path, 'complex', file_name)
|
|
29
|
-
else:
|
|
30
|
-
file_path = os.path.join(output_path, 'srs', file_name)
|
|
31
|
-
|
|
32
|
-
# add research object
|
|
33
|
-
RESEARCH_SITE_BASE = 'https://research.splunk.com/'
|
|
34
|
-
research_site_url = RESEARCH_SITE_BASE + obj.source + "/" + obj.id + "/"
|
|
35
|
-
obj.tags.research_site_url = research_site_url
|
|
36
|
-
|
|
37
|
-
# add ocsf schema tag
|
|
38
|
-
obj.tags.event_schema = 'ocsf'
|
|
39
|
-
|
|
40
|
-
body = FindingReportObject.writeFindingReport(obj)
|
|
41
|
-
|
|
42
|
-
if obj.test:
|
|
43
|
-
test_dict = {
|
|
44
|
-
"name": obj.name + " Unit Test",
|
|
45
|
-
"tests": [obj.test.dict()]
|
|
46
|
-
}
|
|
47
|
-
test_dict["tests"][0]["name"] = obj.name
|
|
48
|
-
for count in range(len(test_dict["tests"][0]["attack_data"])):
|
|
49
|
-
a = urlparse(str(test_dict["tests"][0]["attack_data"][count]["data"]))
|
|
50
|
-
test_dict["tests"][0]["attack_data"][count]["file_name"] = os.path.basename(a.path)
|
|
51
|
-
|
|
52
|
-
test = UnitTestOld.parse_obj(test_dict)
|
|
53
|
-
|
|
54
|
-
obj.test = test
|
|
55
|
-
|
|
56
|
-
# create annotations object
|
|
57
|
-
obj.tags.annotations = {
|
|
58
|
-
"analytic_story": obj.tags.analytic_story,
|
|
59
|
-
"cis20": obj.tags.cis20,
|
|
60
|
-
"kill_chain_phases": obj.tags.kill_chain_phases,
|
|
61
|
-
"mitre_attack_id": obj.tags.mitre_attack_id,
|
|
62
|
-
"nist": obj.tags.nist
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
obj.runtime = "SPL2"
|
|
66
|
-
obj.internalVersion = 2
|
|
67
|
-
|
|
68
|
-
### Adding detection_type as top level key for SRS detections
|
|
69
|
-
obj.detection_type = "STREAMING"
|
|
70
|
-
|
|
71
|
-
# remove unncessary fields
|
|
72
|
-
YmlWriter.writeYmlFile(file_path, obj.dict(
|
|
73
|
-
exclude_none=True,
|
|
74
|
-
include =
|
|
75
|
-
{
|
|
76
|
-
"name": True,
|
|
77
|
-
"id": True,
|
|
78
|
-
"version": True,
|
|
79
|
-
"status": True,
|
|
80
|
-
"detection_type": True,
|
|
81
|
-
"description": True,
|
|
82
|
-
"search": True,
|
|
83
|
-
"how_to_implement": True,
|
|
84
|
-
"known_false_positives": True,
|
|
85
|
-
"references": True,
|
|
86
|
-
"runtime": True,
|
|
87
|
-
"internalVersion": True,
|
|
88
|
-
"tags":
|
|
89
|
-
{
|
|
90
|
-
#"analytic_story": True,
|
|
91
|
-
#"cis20" : True,
|
|
92
|
-
#"nist": True,
|
|
93
|
-
#"kill_chain_phases": True,
|
|
94
|
-
"annotations": True,
|
|
95
|
-
"mappings": True,
|
|
96
|
-
#"mitre_attack_id": True,
|
|
97
|
-
"risk_severity": True,
|
|
98
|
-
"risk_score": True,
|
|
99
|
-
"security_domain": True,
|
|
100
|
-
"required_fields": True,
|
|
101
|
-
"research_site_url": True,
|
|
102
|
-
"event_schema": True
|
|
103
|
-
},
|
|
104
|
-
"test":
|
|
105
|
-
{
|
|
106
|
-
"name": True,
|
|
107
|
-
"tests": {
|
|
108
|
-
'__all__':
|
|
109
|
-
{
|
|
110
|
-
"name": True,
|
|
111
|
-
"file": True,
|
|
112
|
-
"pass_condition": True,
|
|
113
|
-
"attack_data": {
|
|
114
|
-
'__all__':
|
|
115
|
-
{
|
|
116
|
-
"file_name": True,
|
|
117
|
-
"data": True,
|
|
118
|
-
"source": True
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
))
|
|
126
|
-
|
|
127
|
-
# Add Finding Report Object
|
|
128
|
-
with open(file_path, 'r') as file:
|
|
129
|
-
data = file.read().replace('--finding_report--', body)
|
|
130
|
-
|
|
131
|
-
f = open(file_path, "w")
|
|
132
|
-
f.write(data)
|
|
133
|
-
f.close()
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def convertNameToFileName(self, name: str, product: list):
|
|
137
|
-
file_name = name \
|
|
138
|
-
.replace(' ', '_') \
|
|
139
|
-
.replace('-','_') \
|
|
140
|
-
.replace('.','_') \
|
|
141
|
-
.replace('/','_') \
|
|
142
|
-
.lower()
|
|
143
|
-
if 'Splunk Behavioral Analytics' in product:
|
|
144
|
-
|
|
145
|
-
file_name = 'ssa___' + file_name + '.yml'
|
|
146
|
-
else:
|
|
147
|
-
file_name = file_name + '.yml'
|
|
148
|
-
return file_name
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def isComplexBARule(self, search):
|
|
152
|
-
return re.findall("stats|first_time_event|adaptive_threshold", search)
|
|
153
|
-
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import re
|
|
3
|
-
from jinja2 import Environment, FileSystemLoader
|
|
4
|
-
|
|
5
|
-
from contentctl.objects.ssa_detection import SSADetection
|
|
6
|
-
from contentctl.objects.constants import *
|
|
7
|
-
|
|
8
|
-
class FindingReportObject():
|
|
9
|
-
|
|
10
|
-
@staticmethod
|
|
11
|
-
def writeFindingReport(detection : SSADetection) -> None:
|
|
12
|
-
|
|
13
|
-
if detection.tags.confidence < 33:
|
|
14
|
-
detection.tags.confidence_id = 1
|
|
15
|
-
elif detection.tags.confidence < 66:
|
|
16
|
-
detection.tags.confidence_id = 2
|
|
17
|
-
else:
|
|
18
|
-
detection.tags.confidence_id = 3
|
|
19
|
-
|
|
20
|
-
if detection.tags.impact < 20:
|
|
21
|
-
detection.tags.impact_id = 1
|
|
22
|
-
elif detection.tags.impact < 40:
|
|
23
|
-
detection.tags.impact_id = 2
|
|
24
|
-
elif detection.tags.impact < 60:
|
|
25
|
-
detection.tags.impact_id = 3
|
|
26
|
-
elif detection.tags.impact < 80:
|
|
27
|
-
detection.tags.impact_id = 4
|
|
28
|
-
else:
|
|
29
|
-
detection.tags.impact_id = 5
|
|
30
|
-
|
|
31
|
-
detection.tags.kill_chain_phases_id = dict()
|
|
32
|
-
for kill_chain_phase in detection.tags.kill_chain_phases:
|
|
33
|
-
detection.tags.kill_chain_phases_id[kill_chain_phase] = SES_KILL_CHAIN_MAPPINGS[kill_chain_phase]
|
|
34
|
-
|
|
35
|
-
kill_chain_phase_str = "["
|
|
36
|
-
i = 0
|
|
37
|
-
for kill_chain_phase in detection.tags.kill_chain_phases_id.keys():
|
|
38
|
-
kill_chain_phase_str = kill_chain_phase_str + '{"phase": "' + kill_chain_phase + '", "phase_id": ' + str(detection.tags.kill_chain_phases_id[kill_chain_phase]) + "}"
|
|
39
|
-
if not i == (len(detection.tags.kill_chain_phases_id.keys()) - 1):
|
|
40
|
-
kill_chain_phase_str = kill_chain_phase_str + ', '
|
|
41
|
-
i = i + 1
|
|
42
|
-
kill_chain_phase_str = kill_chain_phase_str + ']'
|
|
43
|
-
detection.tags.kill_chain_phases_str = kill_chain_phase_str
|
|
44
|
-
|
|
45
|
-
if detection.tags.risk_score < 20:
|
|
46
|
-
detection.tags.risk_level_id = 0
|
|
47
|
-
detection.tags.risk_level = "Info"
|
|
48
|
-
elif detection.tags.risk_score < 40:
|
|
49
|
-
detection.tags.risk_level_id = 1
|
|
50
|
-
detection.tags.risk_level = "Low"
|
|
51
|
-
elif detection.tags.risk_score < 60:
|
|
52
|
-
detection.tags.risk_level_id = 2
|
|
53
|
-
detection.tags.risk_level = "Medium"
|
|
54
|
-
elif detection.tags.risk_score < 80:
|
|
55
|
-
detection.tags.risk_level_id = 3
|
|
56
|
-
detection.tags.risk_level = "High"
|
|
57
|
-
else:
|
|
58
|
-
detection.tags.risk_level_id = 4
|
|
59
|
-
detection.tags.risk_level = "Critical"
|
|
60
|
-
|
|
61
|
-
evidence_str = "{"
|
|
62
|
-
for i in range(len(detection.tags.required_fields)):
|
|
63
|
-
evidence_str = evidence_str + '"' + detection.tags.required_fields[i] + '": ' + detection.tags.required_fields[i].replace(".", "_")
|
|
64
|
-
if not i == (len(detection.tags.required_fields) - 1):
|
|
65
|
-
evidence_str = evidence_str + ', '
|
|
66
|
-
|
|
67
|
-
evidence_str = evidence_str + ', "sourceType": metadata.source_type, "source": metadata.source}'
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
detection.tags.evidence_str = evidence_str
|
|
71
|
-
|
|
72
|
-
analytics_story_str = "["
|
|
73
|
-
for i in range(len(detection.tags.analytic_story)):
|
|
74
|
-
analytics_story_str = analytics_story_str + '"' + detection.tags.analytic_story[i] + '"'
|
|
75
|
-
if not i == (len(detection.tags.analytic_story) - 1):
|
|
76
|
-
analytics_story_str = analytics_story_str + ', '
|
|
77
|
-
analytics_story_str = analytics_story_str + ']'
|
|
78
|
-
detection.tags.analytics_story_str = analytics_story_str
|
|
79
|
-
|
|
80
|
-
if "actor.user.name" in detection.tags.required_fields:
|
|
81
|
-
actor_user_name = "actor_user_name"
|
|
82
|
-
else:
|
|
83
|
-
actor_user_name = "\"Unknown\""
|
|
84
|
-
|
|
85
|
-
j2_env = Environment(
|
|
86
|
-
loader=FileSystemLoader(os.path.join(os.path.dirname(__file__), 'templates')),
|
|
87
|
-
trim_blocks=True)
|
|
88
|
-
template = j2_env.get_template('finding_report.j2')
|
|
89
|
-
body = template.render(detection=detection, attack_tactics_id_mapping=SES_ATTACK_TACTICS_ID_MAPPING, actor_user_name=actor_user_name)
|
|
90
|
-
|
|
91
|
-
return body
|
|
File without changes
|
|
File without changes
|
|
File without changes
|