contentctl 4.2.5__py3-none-any.whl → 4.3.1__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/detection_testing/infrastructures/DetectionTestingInfrastructure.py +60 -24
- contentctl/actions/validate.py +0 -1
- contentctl/input/director.py +9 -44
- contentctl/objects/abstract_security_content_objects/detection_abstract.py +61 -74
- contentctl/objects/config.py +0 -2
- contentctl/objects/constants.py +5 -0
- contentctl/objects/observable.py +5 -3
- contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml +2 -2
- {contentctl-4.2.5.dist-info → contentctl-4.3.1.dist-info}/METADATA +16 -6
- {contentctl-4.2.5.dist-info → contentctl-4.3.1.dist-info}/RECORD +14 -20
- 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.1.dist-info}/LICENSE.md +0 -0
- {contentctl-4.2.5.dist-info → contentctl-4.3.1.dist-info}/WHEEL +0 -0
- {contentctl-4.2.5.dist-info → contentctl-4.3.1.dist-info}/entry_points.txt +0 -0
|
@@ -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
|