stix-shifter-modules-sysdig 8.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.
- stix_shifter_modules/sysdig/__init__.py +0 -0
- stix_shifter_modules/sysdig/configuration/config.json +603 -0
- stix_shifter_modules/sysdig/configuration/dialects.json +6 -0
- stix_shifter_modules/sysdig/configuration/lang_en.json +69 -0
- stix_shifter_modules/sysdig/entry_point.py +12 -0
- stix_shifter_modules/sysdig/stix_translation/__init__.py +0 -0
- stix_shifter_modules/sysdig/stix_translation/json/config_map.json +33 -0
- stix_shifter_modules/sysdig/stix_translation/json/from_stix_map.json +110 -0
- stix_shifter_modules/sysdig/stix_translation/json/operators.json +13 -0
- stix_shifter_modules/sysdig/stix_translation/json/stix_2_1/from_stix_map.json +110 -0
- stix_shifter_modules/sysdig/stix_translation/json/stix_2_1/to_stix_map.json +332 -0
- stix_shifter_modules/sysdig/stix_translation/json/to_stix_map.json +332 -0
- stix_shifter_modules/sysdig/stix_translation/json_to_stix_translator.py +529 -0
- stix_shifter_modules/sysdig/stix_translation/query_constructor.py +472 -0
- stix_shifter_modules/sysdig/stix_translation/query_translator.py +26 -0
- stix_shifter_modules/sysdig/stix_translation/transformers.py +66 -0
- stix_shifter_modules/sysdig/stix_transmission/__init__.py +0 -0
- stix_shifter_modules/sysdig/stix_transmission/api_client.py +37 -0
- stix_shifter_modules/sysdig/stix_transmission/connector.py +213 -0
- stix_shifter_modules/sysdig/stix_transmission/error_mapper.py +34 -0
- stix_shifter_modules_sysdig-8.0.2.dist-info/METADATA +148 -0
- stix_shifter_modules_sysdig-8.0.2.dist-info/RECORD +27 -0
- stix_shifter_modules_sysdig-8.0.2.dist-info/WHEEL +5 -0
- stix_shifter_modules_sysdig-8.0.2.dist-info/licenses/AUTHORS.md +23 -0
- stix_shifter_modules_sysdig-8.0.2.dist-info/licenses/LICENSE.md +219 -0
- stix_shifter_modules_sysdig-8.0.2.dist-info/licenses/NOTICE +32 -0
- stix_shifter_modules_sysdig-8.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
from os import path
|
|
2
|
+
import uuid
|
|
3
|
+
import json
|
|
4
|
+
import regex as re
|
|
5
|
+
|
|
6
|
+
from stix_shifter_utils.utils.helpers import dict_merge
|
|
7
|
+
from stix_shifter_utils.stix_translation.src.json_to_stix import observable, id_contributing_properties
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from stix_shifter_utils.utils import logger
|
|
10
|
+
from stix_shifter_utils.utils.helpers import StixObjectId
|
|
11
|
+
|
|
12
|
+
# "ID Contributing Properties" taken from https://docs.oasis-open.org/cti/stix/v2.1/csprd01/stix-v2.1-csprd01.html#_Toc16070594
|
|
13
|
+
UUID5_NAMESPACE = "00abedb4-aa42-466c-9c01-fed23315a9b7"
|
|
14
|
+
NUMBER_OBSERVED_KEY = 'number_observed'
|
|
15
|
+
FIRST_OBSERVED_KEY = 'first_observed'
|
|
16
|
+
LAST_OBSERVED_KEY = 'last_observed'
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# convert JSON data to STIX object using map_data and transformers
|
|
20
|
+
def convert_to_stix(data_source, map_data, data, transformers, options, callback=None):
|
|
21
|
+
try:
|
|
22
|
+
ds2stix = DataSourceObjToStixObj(data_source, map_data, transformers, options, callback)
|
|
23
|
+
|
|
24
|
+
# map data list to list of transformed objects
|
|
25
|
+
observation = ds2stix.transform
|
|
26
|
+
results = list(map(observation, data))
|
|
27
|
+
|
|
28
|
+
for stix_object in results:
|
|
29
|
+
if ds2stix.spec_version == "2.1":
|
|
30
|
+
del stix_object["objects"]
|
|
31
|
+
ds2stix.bundle["objects"].append(stix_object)
|
|
32
|
+
|
|
33
|
+
for _, value in ds2stix.unique_cybox_objects.items():
|
|
34
|
+
ds2stix.bundle["objects"].append(value)
|
|
35
|
+
|
|
36
|
+
return ds2stix.bundle
|
|
37
|
+
|
|
38
|
+
except Exception as e:
|
|
39
|
+
try:
|
|
40
|
+
# try to print the error line
|
|
41
|
+
logger_log = logger.set_logger(__name__)
|
|
42
|
+
logger_log.error(logger.last_tb_to_string(e))
|
|
43
|
+
except:
|
|
44
|
+
pass
|
|
45
|
+
raise e
|
|
46
|
+
|
|
47
|
+
class DataSourceObjToStixObj:
|
|
48
|
+
logger = logger.set_logger(__name__)
|
|
49
|
+
|
|
50
|
+
def __init__(self, data_source, ds_to_stix_map, transformers, options, callback=None):
|
|
51
|
+
self.identity_id = data_source["id"]
|
|
52
|
+
self.ds_to_stix_map = ds_to_stix_map
|
|
53
|
+
self.transformers = transformers
|
|
54
|
+
self.options = options
|
|
55
|
+
self.callback = callback
|
|
56
|
+
|
|
57
|
+
# parse through options
|
|
58
|
+
|
|
59
|
+
self.properties = observable.properties
|
|
60
|
+
|
|
61
|
+
self.data_source = data_source['name']
|
|
62
|
+
self.ds_key_map = [val for val in self.gen_dict_extract('ds_key', ds_to_stix_map)]
|
|
63
|
+
|
|
64
|
+
self.bundle = {
|
|
65
|
+
"type": "bundle",
|
|
66
|
+
"id": "bundle--" + str(uuid.uuid4()),
|
|
67
|
+
"objects": []
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if options.get("stix_2.1"):
|
|
71
|
+
self.spec_version = "2.1"
|
|
72
|
+
self.contributing_properties_definitions = id_contributing_properties.properties
|
|
73
|
+
else:
|
|
74
|
+
self.spec_version = "2.0"
|
|
75
|
+
self.bundle["spec_version"] = "2.0"
|
|
76
|
+
self.unique_cybox_objects = {}
|
|
77
|
+
self.bundle['objects'] += [data_source]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# get the nested ds_keys in the mapping
|
|
81
|
+
def gen_dict_extract(self, key, var):
|
|
82
|
+
if hasattr(var, 'items'):
|
|
83
|
+
for k, v in var.items():
|
|
84
|
+
if k == key:
|
|
85
|
+
yield v
|
|
86
|
+
if isinstance(v, dict):
|
|
87
|
+
for result in self.gen_dict_extract(key, v):
|
|
88
|
+
yield result
|
|
89
|
+
elif isinstance(v, list):
|
|
90
|
+
for d in v:
|
|
91
|
+
for result in self.gen_dict_extract(key, d):
|
|
92
|
+
yield result
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _valid_stix_value(self, observable_key, stix_value):
|
|
96
|
+
"""
|
|
97
|
+
Checks that the given STIX value is valid for this STIX property
|
|
98
|
+
:param observable_key: the STIX property name
|
|
99
|
+
:param stix_value: the STIX value translated from the input object
|
|
100
|
+
:return: whether STIX value is valid for this STIX property
|
|
101
|
+
:rtype: bool
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
if observable_key in self.properties and 'valid_regex' in self.properties[observable_key]:
|
|
105
|
+
pattern = re.compile(self.properties[observable_key]['valid_regex'])
|
|
106
|
+
match = pattern.match
|
|
107
|
+
if not match(str(stix_value)):
|
|
108
|
+
return False
|
|
109
|
+
return True
|
|
110
|
+
except Exception as e:
|
|
111
|
+
self.logger.debug("Failed to validate STIX property '{}' with value '{}'. Exception: {}".format(observable_key, stix_value, e))
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
def _compose_value_object(self, value, key_list, observable_key=None, object_tag_ref_map=None, transformer=None, references=None, unwrap=False, is_group_ref=False):
|
|
115
|
+
"""
|
|
116
|
+
Converts the value of the data to STIX valid value
|
|
117
|
+
"""
|
|
118
|
+
try:
|
|
119
|
+
return_value = {}
|
|
120
|
+
for key in key_list:
|
|
121
|
+
return_value[key] = self._compose_value_object(value, key_list[1:], observable_key=observable_key, object_tag_ref_map=object_tag_ref_map, transformer=transformer, references=references, unwrap=unwrap, is_group_ref=is_group_ref)
|
|
122
|
+
break
|
|
123
|
+
else:
|
|
124
|
+
if transformer:
|
|
125
|
+
try:
|
|
126
|
+
value = transformer.transform(value)
|
|
127
|
+
if value is None:
|
|
128
|
+
return None
|
|
129
|
+
except Exception:
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
if references:
|
|
133
|
+
if isinstance(references, list):
|
|
134
|
+
return_value = []
|
|
135
|
+
for ref in references:
|
|
136
|
+
if not isinstance(value, list):
|
|
137
|
+
# Fetch the index of reference object name which has a single value.
|
|
138
|
+
parent_key_ind = self._get_tag_ind(ref, object_tag_ref_map, create_on_absence=False)
|
|
139
|
+
if parent_key_ind:
|
|
140
|
+
return_value.append(parent_key_ind)
|
|
141
|
+
else:
|
|
142
|
+
for i, _ in enumerate(value):
|
|
143
|
+
parent_key_ind = self._get_tag_ind(ref, object_tag_ref_map, create_on_absence=False, unwrap=str(i))
|
|
144
|
+
if parent_key_ind:
|
|
145
|
+
return_value.append(parent_key_ind)
|
|
146
|
+
# Iterate through the object_tag_ref_map to fetch the index, for grouping
|
|
147
|
+
# reference objects of nested type through group_ref.
|
|
148
|
+
elif is_group_ref:
|
|
149
|
+
list_parent_ind = ref + '_' + str(i)
|
|
150
|
+
for key, val in object_tag_ref_map['tags'].items():
|
|
151
|
+
if key.startswith(list_parent_ind):
|
|
152
|
+
return_value.append(
|
|
153
|
+
self._get_tag_ind(key, object_tag_ref_map, create_on_absence=False))
|
|
154
|
+
|
|
155
|
+
else:
|
|
156
|
+
# Fetch first object (index 0) for single reference when the value is of type list .
|
|
157
|
+
if isinstance(value, list):
|
|
158
|
+
return_value = self._get_tag_ind(references, object_tag_ref_map, create_on_absence=False,
|
|
159
|
+
unwrap='0')
|
|
160
|
+
else:
|
|
161
|
+
return_value = self._get_tag_ind(references, object_tag_ref_map, create_on_absence=False)
|
|
162
|
+
# if the property has unwrap true and is not a list, convert to list
|
|
163
|
+
if unwrap is True and not isinstance(return_value, list):
|
|
164
|
+
return_value = [return_value]
|
|
165
|
+
|
|
166
|
+
if not return_value:
|
|
167
|
+
return None
|
|
168
|
+
else:
|
|
169
|
+
if unwrap is False and observable_key and not self._valid_stix_value(observable_key, value):
|
|
170
|
+
return None
|
|
171
|
+
return_value = value
|
|
172
|
+
|
|
173
|
+
return return_value
|
|
174
|
+
except Exception as e:
|
|
175
|
+
raise Exception("Error in json_to_stix_translator._compose_value_object: %s" % e)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _get_tag_ind(self, tag, object_tag_ref_map, create_on_absence=False, unwrap=False, property_key=None):
|
|
179
|
+
"""
|
|
180
|
+
Gets the stringified index of the observable object from the `object_tag_ref_map` cached dictionary if it exists
|
|
181
|
+
or creates otherwise.
|
|
182
|
+
"""
|
|
183
|
+
tag_ind = None
|
|
184
|
+
tag_ind_str = None
|
|
185
|
+
try:
|
|
186
|
+
# if the datasource fields is a collection of json object than we need to unwrap it and create multiple objects
|
|
187
|
+
if unwrap:
|
|
188
|
+
tag = tag + '_' + str(unwrap)
|
|
189
|
+
|
|
190
|
+
if tag in object_tag_ref_map['tags']:
|
|
191
|
+
tag_ind = object_tag_ref_map['tags'][tag]['i']
|
|
192
|
+
object_tag_ref_map['tags'][tag]['n'] += 1
|
|
193
|
+
elif create_on_absence:
|
|
194
|
+
tag_ind_str = str(object_tag_ref_map[UUID5_NAMESPACE])
|
|
195
|
+
if self.spec_version == "2.1":
|
|
196
|
+
tag_ind = StixObjectId(tag_ind_str)
|
|
197
|
+
else:
|
|
198
|
+
tag_ind = tag_ind_str
|
|
199
|
+
|
|
200
|
+
object_tag_ref_map[UUID5_NAMESPACE] += 1
|
|
201
|
+
object_tag_ref_map['tags'][tag] = {'i': tag_ind, 'n': 0}
|
|
202
|
+
|
|
203
|
+
if tag_ind is not None:
|
|
204
|
+
if not tag_ind_str:
|
|
205
|
+
tag_ind_str = str(tag_ind)
|
|
206
|
+
if property_key and '_ref' not in property_key:
|
|
207
|
+
object_tag_ref_map['non_ref_props'][tag_ind_str] = True
|
|
208
|
+
except Exception as e:
|
|
209
|
+
raise Exception("Error in json_to_stix_translator._get_tag_ind: %s : %s" % (e, e.__traceback__.tb_lineno))
|
|
210
|
+
|
|
211
|
+
return tag_ind
|
|
212
|
+
|
|
213
|
+
def _add_property(self, type_name, property_key, parent_key_ind, value, objects, group=False, cybox=True):
|
|
214
|
+
"""
|
|
215
|
+
Add observable object property and its STIX valid value to the cached `objects` dictionary
|
|
216
|
+
"""
|
|
217
|
+
named_group = isinstance(group, str) and group.lower() != "true"
|
|
218
|
+
parent_key_ind_str = str(parent_key_ind)
|
|
219
|
+
if not parent_key_ind_str in objects:
|
|
220
|
+
if cybox:
|
|
221
|
+
# Grouped properties go in a list
|
|
222
|
+
if named_group:
|
|
223
|
+
value = [value]
|
|
224
|
+
objects[parent_key_ind_str] = {
|
|
225
|
+
'type': type_name,
|
|
226
|
+
property_key: value
|
|
227
|
+
}
|
|
228
|
+
if self.spec_version == "2.1":
|
|
229
|
+
objects[parent_key_ind_str]["id"] = parent_key_ind
|
|
230
|
+
objects[parent_key_ind_str]["spec_version"] = "2.1"
|
|
231
|
+
else:
|
|
232
|
+
objects[parent_key_ind_str] = {
|
|
233
|
+
property_key: value
|
|
234
|
+
}
|
|
235
|
+
else:
|
|
236
|
+
if not property_key in objects[parent_key_ind_str]:
|
|
237
|
+
objects[parent_key_ind_str][property_key] = value
|
|
238
|
+
# Add grouped value in existing list element
|
|
239
|
+
elif isinstance(value, dict) and named_group and isinstance(objects[parent_key_ind_str][property_key], list):
|
|
240
|
+
objects[parent_key_ind_str][property_key][0] = dict_merge(objects[parent_key_ind_str][property_key][0], value)
|
|
241
|
+
elif isinstance(value, dict):
|
|
242
|
+
objects[parent_key_ind_str][property_key] = dict_merge(objects[parent_key_ind_str][property_key], value)
|
|
243
|
+
elif isinstance(objects[parent_key_ind_str][property_key], list) and group:
|
|
244
|
+
objects[parent_key_ind_str][property_key].extend(value)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _handle_properties(self, to_stix_config_prop, data, objects, object_tag_ref_map, parent_data=None, ds_sub_key=None, object_key_ind=None):
|
|
248
|
+
"""
|
|
249
|
+
Walks through data object, matches the property names of the data elements with the to_stix_map property names and
|
|
250
|
+
send the values to process if the data is the final value
|
|
251
|
+
"""
|
|
252
|
+
try:
|
|
253
|
+
if data is not None:
|
|
254
|
+
if isinstance(to_stix_config_prop, dict) and to_stix_config_prop.get('key') is not None and not isinstance(to_stix_config_prop.get('key'), dict):
|
|
255
|
+
# data variable is the final value, process in bulk
|
|
256
|
+
self._handle_value(data, parent_data, ds_sub_key, to_stix_config_prop, objects, object_tag_ref_map, object_key_ind)
|
|
257
|
+
|
|
258
|
+
elif isinstance(data, list):
|
|
259
|
+
for i, d in enumerate(data):
|
|
260
|
+
if isinstance(d, list) or isinstance(d, dict):
|
|
261
|
+
# Added parent key indexes to inner objects in order to handle nested lists of
|
|
262
|
+
# dictionaries and lists. For Example, if a list of IP address is present inside
|
|
263
|
+
# a list of network objects, then without adding this code, only the IP address information
|
|
264
|
+
# in the first network object will be created and the rest are not created.
|
|
265
|
+
if object_key_ind:
|
|
266
|
+
i = str(object_key_ind) + '_' + str(i)
|
|
267
|
+
# Inorder to include 0th index, the integer field 'i' is converted to string.
|
|
268
|
+
self._handle_properties(to_stix_config_prop, d, objects, object_tag_ref_map, data, ds_sub_key, str(i))
|
|
269
|
+
else:
|
|
270
|
+
# data variable is the final value, process in bulk
|
|
271
|
+
self._handle_value(data, parent_data, ds_sub_key, to_stix_config_prop, objects, object_tag_ref_map, object_key_ind)
|
|
272
|
+
break
|
|
273
|
+
# group the references of list of dictionary field
|
|
274
|
+
if isinstance(to_stix_config_prop, dict):
|
|
275
|
+
group_refs = [key for key, value in to_stix_config_prop.items() if
|
|
276
|
+
isinstance(value, dict) and value.get('group_ref') and value.get(
|
|
277
|
+
'references')]
|
|
278
|
+
for group_ref in group_refs:
|
|
279
|
+
# Added a new boolean (True) parameter (is_group_ref) to indicate grouping of references
|
|
280
|
+
# through group_ref key in mapping
|
|
281
|
+
self._handle_value(data, to_stix_config_prop, ds_sub_key,
|
|
282
|
+
to_stix_config_prop[group_ref],
|
|
283
|
+
objects, object_tag_ref_map, object_key_ind, True)
|
|
284
|
+
|
|
285
|
+
elif isinstance(data, dict):
|
|
286
|
+
for k in data:
|
|
287
|
+
cust_prop = None
|
|
288
|
+
if k in to_stix_config_prop:
|
|
289
|
+
cust_prop = to_stix_config_prop[k]
|
|
290
|
+
elif self.options.get('unmapped_fallback') and k not in object_tag_ref_map['ds_key_cybox']:
|
|
291
|
+
cust_prop = {"key": "x-" + self.data_source.replace("_", "-") + "." + k, "object": "cust_object"}
|
|
292
|
+
|
|
293
|
+
if cust_prop:
|
|
294
|
+
self._handle_properties(cust_prop, data[k], objects, object_tag_ref_map, data, k, object_key_ind)
|
|
295
|
+
else:
|
|
296
|
+
self._handle_value(data, parent_data, ds_sub_key, to_stix_config_prop, objects, object_tag_ref_map, object_key_ind)
|
|
297
|
+
except Exception as e:
|
|
298
|
+
raise Exception("Error in json_to_stix_translator._handle_properties: %s" % e)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _handle_value(self, data, parent_data, ds_sub_key, to_stix_config_prop, objects, object_tag_ref_map, object_key_ind=None, is_group_ref=False):
|
|
302
|
+
"""
|
|
303
|
+
Receives the raw value of a data property, converts to a STIX valid value and adds to the cached observable `objects` dictionary
|
|
304
|
+
"""
|
|
305
|
+
try:
|
|
306
|
+
if isinstance(to_stix_config_prop, dict):
|
|
307
|
+
props = [to_stix_config_prop]
|
|
308
|
+
else:
|
|
309
|
+
props = to_stix_config_prop
|
|
310
|
+
|
|
311
|
+
for prop in props:
|
|
312
|
+
key = prop.get('key', None)
|
|
313
|
+
if key is None:
|
|
314
|
+
continue
|
|
315
|
+
|
|
316
|
+
transformer = self.transformers[prop['transformer']] if 'transformer' in prop else None
|
|
317
|
+
references = references = prop['references'] if 'references' in prop else None
|
|
318
|
+
|
|
319
|
+
# This check avoid using duplicate reference in the multiple objects of the same type.
|
|
320
|
+
# For example: If there are multiple source ipv4-addr and network-traffic objects then
|
|
321
|
+
# without this reference check the first source ipv4-addr object will be referenced to all network-traffic objects.
|
|
322
|
+
if references:
|
|
323
|
+
if isinstance(references, str):
|
|
324
|
+
if object_key_ind:
|
|
325
|
+
references = references + '_' + str(object_key_ind)
|
|
326
|
+
elif not isinstance(data, list):
|
|
327
|
+
references = references + '_' + '0'
|
|
328
|
+
elif isinstance(references, list):
|
|
329
|
+
if object_key_ind:
|
|
330
|
+
references = [ref + '_' + str(object_key_ind) for ref in references]
|
|
331
|
+
elif not isinstance(data, list):
|
|
332
|
+
references = [ref + '_' + '0' for ref in references]
|
|
333
|
+
# unwrap array of stix values to separate stix objects
|
|
334
|
+
unwrap = True if 'unwrap' in prop and isinstance(data, list) else False
|
|
335
|
+
if "." in key:
|
|
336
|
+
cybox = True
|
|
337
|
+
else:
|
|
338
|
+
cybox = False
|
|
339
|
+
|
|
340
|
+
# cybox = prop.get('cybox', self.cybox_default)
|
|
341
|
+
|
|
342
|
+
if self.callback:
|
|
343
|
+
try:
|
|
344
|
+
generic_hash_key = self.callback(parent_data, ds_sub_key, key, self.options)
|
|
345
|
+
if generic_hash_key:
|
|
346
|
+
key = generic_hash_key
|
|
347
|
+
except Exception as e:
|
|
348
|
+
continue
|
|
349
|
+
|
|
350
|
+
config_keys = key.split('.')
|
|
351
|
+
if len(config_keys) < 2:
|
|
352
|
+
# if False is prop.get('cybox', self.cybox_default):
|
|
353
|
+
if not cybox:
|
|
354
|
+
object_tag_ref_map['out_cybox'][key] = self._compose_value_object(data, [], observable_key=key, object_tag_ref_map=object_tag_ref_map, transformer=transformer, references=references, unwrap=unwrap)
|
|
355
|
+
pass
|
|
356
|
+
else:
|
|
357
|
+
type_name = config_keys[0]
|
|
358
|
+
property_key = config_keys[1]
|
|
359
|
+
# set the object to combine properties from same SCO
|
|
360
|
+
parent_key = prop['object'] if 'object' in prop else type_name
|
|
361
|
+
|
|
362
|
+
# set the group to combine properties in a list
|
|
363
|
+
group = prop['group'] if 'group' in prop else False
|
|
364
|
+
substitute_key = prop['ds_key'] if 'ds_key' in prop else None
|
|
365
|
+
|
|
366
|
+
if False is cybox and not substitute_key:
|
|
367
|
+
value = self._compose_value_object(data, config_keys[2:], observable_key=key, object_tag_ref_map=object_tag_ref_map, transformer=transformer, references=references, unwrap=unwrap)
|
|
368
|
+
self._add_property(type_name, property_key, type_name, value, object_tag_ref_map['out_cybox'], cybox=False)
|
|
369
|
+
continue
|
|
370
|
+
|
|
371
|
+
if object_key_ind:
|
|
372
|
+
parent_key = parent_key + '_' + str(object_key_ind)
|
|
373
|
+
# Adding _0 as a tag index for the following
|
|
374
|
+
# 1. For object name of non-list type data or unwrapped data
|
|
375
|
+
# 2. For the object name containing references to list type of data.
|
|
376
|
+
# For Example, when a single IP address value and references to the list of domain values, needs
|
|
377
|
+
# to be added to a single custom object, the 0th index is added to the custom object to have ip
|
|
378
|
+
# address and references to list of domains under the same custom object.
|
|
379
|
+
elif (isinstance(data, list) and not unwrap and not references) \
|
|
380
|
+
or (isinstance(data, list) and references) or (not isinstance(data, list)):
|
|
381
|
+
parent_key = parent_key + '_' + '0'
|
|
382
|
+
|
|
383
|
+
# use the hard-coded value in the mapping
|
|
384
|
+
if 'value' in prop:
|
|
385
|
+
value = prop['value']
|
|
386
|
+
else:
|
|
387
|
+
if substitute_key and parent_data:
|
|
388
|
+
data = parent_data.get(substitute_key)
|
|
389
|
+
|
|
390
|
+
if False is cybox:
|
|
391
|
+
object_tag_ref_map['ds_key_cybox'][substitute_key] = True
|
|
392
|
+
|
|
393
|
+
value = self._compose_value_object(data, config_keys[2:], observable_key=key, object_tag_ref_map=object_tag_ref_map, transformer=transformer, references=references, unwrap=unwrap, is_group_ref=is_group_ref)
|
|
394
|
+
|
|
395
|
+
# Remove the values which has empty list brackets.
|
|
396
|
+
remove_value = False
|
|
397
|
+
if value is None or value in ('', []):
|
|
398
|
+
remove_value = True
|
|
399
|
+
elif isinstance(value, dict):
|
|
400
|
+
for k, v in value.items():
|
|
401
|
+
if isinstance(v, dict) and list(v.values())[0] in ('', []):
|
|
402
|
+
remove_value = True
|
|
403
|
+
continue
|
|
404
|
+
if remove_value:
|
|
405
|
+
continue
|
|
406
|
+
|
|
407
|
+
if not references and unwrap and isinstance(value, list):
|
|
408
|
+
for i, val_el in enumerate(value):
|
|
409
|
+
# Inorder to include 0th index, the integer field 'i' is converted to string.
|
|
410
|
+
parent_key_ind = self._get_tag_ind(parent_key, object_tag_ref_map, create_on_absence=True, unwrap=str(i), property_key=property_key)
|
|
411
|
+
self._add_property(type_name, property_key, parent_key_ind, val_el, objects, group=group)
|
|
412
|
+
else:
|
|
413
|
+
parent_key_ind = self._get_tag_ind(parent_key, object_tag_ref_map, create_on_absence=True, property_key=property_key)
|
|
414
|
+
self._add_property(type_name, property_key, parent_key_ind, value, objects, group=group)
|
|
415
|
+
except Exception as e:
|
|
416
|
+
raise Exception("Error in json_to_stix_translator._handle_value: %s : %s" % (e, e.__traceback__.tb_lineno))
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _generate_deterministic_id(self, cybox):
|
|
420
|
+
# Generates ID based on common namespace and SCO properties (omitting id and spec_version)
|
|
421
|
+
|
|
422
|
+
unique_id = None
|
|
423
|
+
cybox_properties = {}
|
|
424
|
+
cybox_type = cybox.get("type")
|
|
425
|
+
contributing_properties = self.contributing_properties_definitions.get(cybox_type)
|
|
426
|
+
|
|
427
|
+
if contributing_properties:
|
|
428
|
+
for contr_prop in contributing_properties:
|
|
429
|
+
if type(contr_prop) is list: # list of hash types
|
|
430
|
+
for hashtype in contr_prop:
|
|
431
|
+
hash_prop = "hashes.{}".format(hashtype)
|
|
432
|
+
if hash_prop in cybox:
|
|
433
|
+
cybox_properties[hash_prop] = cybox[hash_prop]
|
|
434
|
+
break
|
|
435
|
+
elif contr_prop in cybox and '_ref' not in contr_prop:
|
|
436
|
+
cybox_properties[contr_prop] = cybox[contr_prop]
|
|
437
|
+
|
|
438
|
+
if cybox_properties:
|
|
439
|
+
unique_id = cybox_type + "--" + str(uuid.uuid5(namespace=uuid.UUID(UUID5_NAMESPACE), name=json.dumps(cybox_properties, sort_keys=True, ensure_ascii=False, separators=(",", ":"))))
|
|
440
|
+
|
|
441
|
+
if not unique_id: # STIX process or custom object used UUID4 for identifier
|
|
442
|
+
unique_id = "{}--{}".format(cybox_type, str(uuid.uuid4()))
|
|
443
|
+
|
|
444
|
+
return unique_id
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def transform(self, obj):
|
|
448
|
+
try:
|
|
449
|
+
"""
|
|
450
|
+
Transforms the given object in to a STIX observation based on the mapping file and transform functions
|
|
451
|
+
:param obj: the datasource object that is being converted to stix
|
|
452
|
+
:return: the input object converted to stix valid json
|
|
453
|
+
"""
|
|
454
|
+
object_map = {}
|
|
455
|
+
stix_type = 'observed-data'
|
|
456
|
+
ds_map = self.ds_to_stix_map
|
|
457
|
+
now = "{}Z".format(datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3])
|
|
458
|
+
object_id_map = {}
|
|
459
|
+
|
|
460
|
+
observation = {
|
|
461
|
+
'id': stix_type + '--' + str(uuid.uuid4()),
|
|
462
|
+
'type': stix_type,
|
|
463
|
+
'created_by_ref': self.identity_id,
|
|
464
|
+
'created': now,
|
|
465
|
+
'modified': now,
|
|
466
|
+
'objects': {}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
# create normal type objects
|
|
470
|
+
if isinstance(obj, dict):
|
|
471
|
+
object_tag_ref_map = {UUID5_NAMESPACE: 0, 'tags': {}, 'non_ref_props': {}, 'out_cybox': {}, 'ds_key_cybox': {}}
|
|
472
|
+
|
|
473
|
+
self._handle_properties(ds_map, obj, object_map, object_tag_ref_map)
|
|
474
|
+
# special case:
|
|
475
|
+
# remove object if:
|
|
476
|
+
# a reference attribute object does not contain at least one property other than 'type'
|
|
477
|
+
object_map = self._cleanup_references(object_map, object_tag_ref_map['non_ref_props'])
|
|
478
|
+
observation['objects'] = object_map
|
|
479
|
+
|
|
480
|
+
for k, v in object_tag_ref_map['out_cybox'].items():
|
|
481
|
+
observation[k] = v
|
|
482
|
+
|
|
483
|
+
else:
|
|
484
|
+
self.logger.debug("Not a dict: {}".format(obj))
|
|
485
|
+
|
|
486
|
+
# Add required properties to the observation if it wasn't added from the mapping
|
|
487
|
+
if FIRST_OBSERVED_KEY not in observation:
|
|
488
|
+
observation[FIRST_OBSERVED_KEY] = now
|
|
489
|
+
if LAST_OBSERVED_KEY not in observation:
|
|
490
|
+
observation[LAST_OBSERVED_KEY] = now
|
|
491
|
+
if NUMBER_OBSERVED_KEY not in observation:
|
|
492
|
+
observation[NUMBER_OBSERVED_KEY] = 1
|
|
493
|
+
|
|
494
|
+
if self.spec_version == "2.1":
|
|
495
|
+
object_refs = []
|
|
496
|
+
|
|
497
|
+
for key, value in observation["objects"].items():
|
|
498
|
+
unique_id = self._generate_deterministic_id(value)
|
|
499
|
+
if unique_id:
|
|
500
|
+
if isinstance(value['id'], StixObjectId):
|
|
501
|
+
value['id'].update(unique_id)
|
|
502
|
+
|
|
503
|
+
if unique_id not in object_refs:
|
|
504
|
+
object_refs.append(unique_id)
|
|
505
|
+
self.unique_cybox_objects[unique_id] = value
|
|
506
|
+
|
|
507
|
+
observation["object_refs"] = object_refs
|
|
508
|
+
observation["spec_version"] = "2.1"
|
|
509
|
+
|
|
510
|
+
except Exception as e:
|
|
511
|
+
raise Exception("Error in json_to_stix_translator.transform %s : %s" % (e, e.__traceback__.tb_lineno))
|
|
512
|
+
|
|
513
|
+
return observation
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def _cleanup_references(self, objects, tags):
|
|
517
|
+
new_objects = {}
|
|
518
|
+
tag_keys = list(tags.keys())
|
|
519
|
+
|
|
520
|
+
if not self.spec_version == "2.1":
|
|
521
|
+
try:
|
|
522
|
+
tag_keys.sort(key=lambda x: int(x))
|
|
523
|
+
except Exception:
|
|
524
|
+
pass
|
|
525
|
+
|
|
526
|
+
for ind in tag_keys:
|
|
527
|
+
new_objects[ind] = objects[ind]
|
|
528
|
+
|
|
529
|
+
return new_objects
|