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.
@@ -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