synapse 2.169.0__py311-none-any.whl → 2.171.0__py311-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.
Potentially problematic release.
This version of synapse might be problematic. Click here for more details.
- synapse/cortex.py +99 -3
- synapse/datamodel.py +5 -0
- synapse/lib/ast.py +70 -12
- synapse/lib/cell.py +76 -7
- synapse/lib/layer.py +75 -6
- synapse/lib/lmdbslab.py +17 -0
- synapse/lib/node.py +7 -0
- synapse/lib/snap.py +22 -4
- synapse/lib/storm.py +1 -1
- synapse/lib/stormlib/cortex.py +1 -1
- synapse/lib/stormlib/model.py +339 -40
- synapse/lib/stormtypes.py +58 -1
- synapse/lib/types.py +36 -1
- synapse/lib/version.py +2 -2
- synapse/lib/view.py +94 -15
- synapse/models/files.py +40 -0
- synapse/models/inet.py +8 -4
- synapse/models/infotech.py +355 -17
- synapse/tests/files/cpedata.json +525034 -0
- synapse/tests/test_cortex.py +108 -0
- synapse/tests/test_lib_ast.py +66 -0
- synapse/tests/test_lib_cell.py +112 -0
- synapse/tests/test_lib_layer.py +52 -1
- synapse/tests/test_lib_lmdbslab.py +36 -0
- synapse/tests/test_lib_scrape.py +72 -71
- synapse/tests/test_lib_snap.py +16 -1
- synapse/tests/test_lib_storm.py +118 -0
- synapse/tests/test_lib_stormlib_cortex.py +15 -0
- synapse/tests/test_lib_stormlib_model.py +427 -0
- synapse/tests/test_lib_stormtypes.py +147 -15
- synapse/tests/test_lib_types.py +21 -0
- synapse/tests/test_lib_view.py +77 -0
- synapse/tests/test_model_files.py +52 -0
- synapse/tests/test_model_inet.py +63 -1
- synapse/tests/test_model_infotech.py +187 -26
- synapse/tests/utils.py +42 -9
- {synapse-2.169.0.dist-info → synapse-2.171.0.dist-info}/METADATA +1 -1
- {synapse-2.169.0.dist-info → synapse-2.171.0.dist-info}/RECORD +41 -40
- {synapse-2.169.0.dist-info → synapse-2.171.0.dist-info}/LICENSE +0 -0
- {synapse-2.169.0.dist-info → synapse-2.171.0.dist-info}/WHEEL +0 -0
- {synapse-2.169.0.dist-info → synapse-2.171.0.dist-info}/top_level.txt +0 -0
synapse/models/infotech.py
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import string
|
|
1
3
|
import asyncio
|
|
2
4
|
import logging
|
|
3
5
|
|
|
6
|
+
import regex
|
|
7
|
+
|
|
4
8
|
import synapse.exc as s_exc
|
|
5
9
|
import synapse.data as s_data
|
|
6
10
|
|
|
@@ -9,10 +13,53 @@ import synapse.common as s_common
|
|
|
9
13
|
import synapse.lib.chop as s_chop
|
|
10
14
|
import synapse.lib.types as s_types
|
|
11
15
|
import synapse.lib.module as s_module
|
|
16
|
+
import synapse.lib.scrape as s_scrape
|
|
12
17
|
import synapse.lib.version as s_version
|
|
13
18
|
|
|
14
19
|
logger = logging.getLogger(__name__)
|
|
15
20
|
|
|
21
|
+
# This is the regular expression pattern for CPE2.2. It's kind of a hybrid
|
|
22
|
+
# between compatible binding and preferred binding. Differences are here:
|
|
23
|
+
# - Use only the list of percent encoded values specified by preferred binding.
|
|
24
|
+
# This is to ensure it converts properly to CPE2.3.
|
|
25
|
+
# - Add tilde (~) to the UNRESERVED list which removes the need to specify the
|
|
26
|
+
# PACKED encoding specifically.
|
|
27
|
+
ALPHA = '[A-Za-z]'
|
|
28
|
+
DIGIT = '[0-9]'
|
|
29
|
+
UNRESERVED = r'[A-Za-z0-9\-\.\_~]'
|
|
30
|
+
SPEC1 = '%01'
|
|
31
|
+
SPEC2 = '%02'
|
|
32
|
+
# This is defined in the ABNF but not actually referenced
|
|
33
|
+
# SPECIAL = f'(?:{SPEC1}|{SPEC2})'
|
|
34
|
+
SPEC_CHRS = f'(?:{SPEC1}+|{SPEC2})'
|
|
35
|
+
PCT_ENCODED = '%(?:21|22|23|24|25|26|27|28|28|29|2a|2b|2c|2f|3a|3b|3c|3d|3e|3f|40|5b|5c|5d|5e|60|7b|7c|7d|7e)'
|
|
36
|
+
STR_WO_SPECIAL = f'(?:{UNRESERVED}|{PCT_ENCODED})*'
|
|
37
|
+
STR_W_SPECIAL = f'{SPEC_CHRS}? (?:{UNRESERVED}|{PCT_ENCODED})+ {SPEC_CHRS}?'
|
|
38
|
+
STRING = f'(?:{STR_W_SPECIAL}|{STR_WO_SPECIAL})'
|
|
39
|
+
REGION = f'(?:{ALPHA}{{2}}|{DIGIT}{{3}})'
|
|
40
|
+
LANGTAG = rf'(?:{ALPHA}{{2,3}}(?:\-{REGION})?)'
|
|
41
|
+
PART = '[hoa]?'
|
|
42
|
+
VENDOR = STRING
|
|
43
|
+
PRODUCT = STRING
|
|
44
|
+
VERSION = STRING
|
|
45
|
+
UPDATE = STRING
|
|
46
|
+
EDITION = STRING
|
|
47
|
+
LANG = f'{LANGTAG}?'
|
|
48
|
+
COMPONENT_LIST = f'''
|
|
49
|
+
(?:
|
|
50
|
+
{PART}:{VENDOR}:{PRODUCT}:{VERSION}:{UPDATE}:{EDITION}:{LANG} |
|
|
51
|
+
{PART}:{VENDOR}:{PRODUCT}:{VERSION}:{UPDATE}:{EDITION} |
|
|
52
|
+
{PART}:{VENDOR}:{PRODUCT}:{VERSION}:{UPDATE} |
|
|
53
|
+
{PART}:{VENDOR}:{PRODUCT}:{VERSION} |
|
|
54
|
+
{PART}:{VENDOR}:{PRODUCT} |
|
|
55
|
+
{PART}:{VENDOR} |
|
|
56
|
+
{PART}
|
|
57
|
+
)
|
|
58
|
+
'''
|
|
59
|
+
|
|
60
|
+
cpe22_regex = regex.compile(f'cpe:/{COMPONENT_LIST}', regex.VERBOSE | regex.IGNORECASE)
|
|
61
|
+
cpe23_regex = regex.compile(s_scrape._cpe23_regex, regex.VERBOSE | regex.IGNORECASE)
|
|
62
|
+
|
|
16
63
|
def cpesplit(text):
|
|
17
64
|
part = ''
|
|
18
65
|
parts = []
|
|
@@ -36,7 +83,160 @@ def cpesplit(text):
|
|
|
36
83
|
except StopIteration:
|
|
37
84
|
parts.append(part)
|
|
38
85
|
|
|
39
|
-
return parts
|
|
86
|
+
return [part.strip() for part in parts]
|
|
87
|
+
|
|
88
|
+
# Formatted String Binding characters that need to be escaped
|
|
89
|
+
FSB_ESCAPE_CHARS = [
|
|
90
|
+
'!', '"', '#', '$', '%', '&', "'", '(', ')',
|
|
91
|
+
'+', ',', '/', ':', ';', '<', '=', '>', '@',
|
|
92
|
+
'[', ']', '^', '`', '{', '|', '}', '~',
|
|
93
|
+
'\\', '?', '*'
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
FSB_VALID_CHARS = ['-', '.', '_']
|
|
97
|
+
FSB_VALID_CHARS.extend(string.ascii_letters)
|
|
98
|
+
FSB_VALID_CHARS.extend(string.digits)
|
|
99
|
+
FSB_VALID_CHARS.extend(FSB_ESCAPE_CHARS)
|
|
100
|
+
|
|
101
|
+
def fsb_escape(text):
|
|
102
|
+
ret = ''
|
|
103
|
+
if text in ('*', '-'):
|
|
104
|
+
return text
|
|
105
|
+
|
|
106
|
+
# Check validity of text first
|
|
107
|
+
if (invalid := [char for char in text if char not in FSB_VALID_CHARS]):
|
|
108
|
+
badchars = ', '.join(invalid)
|
|
109
|
+
mesg = f'Invalid CPE 2.3 character(s) ({badchars}) detected.'
|
|
110
|
+
raise s_exc.BadTypeValu(mesg=mesg, valu=text)
|
|
111
|
+
|
|
112
|
+
textlen = len(text)
|
|
113
|
+
|
|
114
|
+
for idx, char in enumerate(text):
|
|
115
|
+
if char not in FSB_ESCAPE_CHARS:
|
|
116
|
+
ret += char
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
escchar = f'\\{char}'
|
|
120
|
+
|
|
121
|
+
# The only character in the string
|
|
122
|
+
if idx == 0 and idx == textlen - 1:
|
|
123
|
+
ret += escchar
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
# Handle the backslash as a special case
|
|
127
|
+
if char == '\\':
|
|
128
|
+
if idx == 0:
|
|
129
|
+
# Its the first character and escaping another special character
|
|
130
|
+
if text[idx + 1] in FSB_ESCAPE_CHARS:
|
|
131
|
+
ret += char
|
|
132
|
+
else:
|
|
133
|
+
ret += escchar
|
|
134
|
+
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
if idx == textlen - 1:
|
|
138
|
+
# Its the last character and being escaped
|
|
139
|
+
if text[idx - 1] == '\\':
|
|
140
|
+
ret += char
|
|
141
|
+
else:
|
|
142
|
+
ret += escchar
|
|
143
|
+
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
# The backslash is in the middle somewhere
|
|
147
|
+
|
|
148
|
+
# It's already escaped or it's escaping a special char
|
|
149
|
+
if text[idx - 1] == '\\' or text[idx + 1] in FSB_ESCAPE_CHARS:
|
|
150
|
+
ret += char
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
# Lone backslash, escape it and move on
|
|
154
|
+
ret += escchar
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
# First char, no look behind
|
|
158
|
+
if idx == 0:
|
|
159
|
+
# Escape the first character and go around
|
|
160
|
+
ret += escchar
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
escaped = text[idx - 1] == '\\'
|
|
164
|
+
|
|
165
|
+
if not escaped:
|
|
166
|
+
ret += escchar
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
ret += char
|
|
170
|
+
|
|
171
|
+
return ret
|
|
172
|
+
|
|
173
|
+
def fsb_unescape(text):
|
|
174
|
+
ret = ''
|
|
175
|
+
textlen = len(text)
|
|
176
|
+
|
|
177
|
+
for idx, char in enumerate(text):
|
|
178
|
+
# The last character so we can't look ahead
|
|
179
|
+
if idx == textlen - 1:
|
|
180
|
+
ret += char
|
|
181
|
+
continue
|
|
182
|
+
|
|
183
|
+
if char == '\\' and text[idx + 1] in FSB_ESCAPE_CHARS:
|
|
184
|
+
continue
|
|
185
|
+
|
|
186
|
+
ret += char
|
|
187
|
+
|
|
188
|
+
return ret
|
|
189
|
+
|
|
190
|
+
# URI Binding characters that can be encoded in percent format
|
|
191
|
+
URI_PERCENT_CHARS = [
|
|
192
|
+
# Do the percent first so we don't double encode by accident
|
|
193
|
+
('%25', '%'),
|
|
194
|
+
('%21', '!'), ('%22', '"'), ('%23', '#'), ('%24', '$'), ('%26', '&'), ('%27', "'"),
|
|
195
|
+
('%28', '('), ('%29', ')'), ('%2a', '*'), ('%2b', '+'), ('%2c', ','), ('%2f', '/'), ('%3a', ':'),
|
|
196
|
+
('%3b', ';'), ('%3c', '<'), ('%3d', '='), ('%3e', '>'), ('%3f', '?'), ('%40', '@'), ('%5b', '['),
|
|
197
|
+
('%5c', '\\'), ('%5d', ']'), ('%5e', '^'), ('%60', '`'), ('%7b', '{'), ('%7c', '|'), ('%7d', '}'),
|
|
198
|
+
('%7e', '~'),
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
def uri_quote(text):
|
|
202
|
+
ret = ''
|
|
203
|
+
for (pct, char) in URI_PERCENT_CHARS:
|
|
204
|
+
text = text.replace(char, pct)
|
|
205
|
+
return text
|
|
206
|
+
|
|
207
|
+
def uri_unquote(text):
|
|
208
|
+
# iterate backwards so we do the % last to avoid double unquoting
|
|
209
|
+
# example: "%2521" would turn into "%21" which would then replace into "!"
|
|
210
|
+
for (pct, char) in URI_PERCENT_CHARS[::-1]:
|
|
211
|
+
text = text.replace(pct, char)
|
|
212
|
+
return text
|
|
213
|
+
|
|
214
|
+
UNSPECIFIED = ('', '*')
|
|
215
|
+
def uri_pack(edition, sw_edition, target_sw, target_hw, other):
|
|
216
|
+
# If the four extended attributes are unspecified, only return the edition value
|
|
217
|
+
if (sw_edition in UNSPECIFIED and target_sw in UNSPECIFIED and target_hw in UNSPECIFIED and other in UNSPECIFIED):
|
|
218
|
+
return edition
|
|
219
|
+
|
|
220
|
+
ret = [edition, '', '', '', '']
|
|
221
|
+
|
|
222
|
+
if sw_edition not in UNSPECIFIED:
|
|
223
|
+
ret[1] = sw_edition
|
|
224
|
+
|
|
225
|
+
if target_sw not in UNSPECIFIED:
|
|
226
|
+
ret[2] = target_sw
|
|
227
|
+
|
|
228
|
+
if target_hw not in UNSPECIFIED:
|
|
229
|
+
ret[3] = target_hw
|
|
230
|
+
|
|
231
|
+
if other not in UNSPECIFIED:
|
|
232
|
+
ret[4] = other
|
|
233
|
+
|
|
234
|
+
return '~' + '~'.join(ret)
|
|
235
|
+
|
|
236
|
+
def uri_unpack(edition):
|
|
237
|
+
if edition.startswith('~') and edition.count('~') == 5:
|
|
238
|
+
return edition[1:].split('~', 5)
|
|
239
|
+
return None
|
|
40
240
|
|
|
41
241
|
class Cpe22Str(s_types.Str):
|
|
42
242
|
'''
|
|
@@ -60,7 +260,14 @@ class Cpe22Str(s_types.Str):
|
|
|
60
260
|
mesg = 'CPE 2.2 string is expected to start with "cpe:/"'
|
|
61
261
|
raise s_exc.BadTypeValu(valu=valu, mesg=mesg)
|
|
62
262
|
|
|
63
|
-
|
|
263
|
+
v2_2 = zipCpe22(parts)
|
|
264
|
+
|
|
265
|
+
rgx = cpe22_regex.match(v2_2)
|
|
266
|
+
if rgx is None or rgx.group() != v2_2:
|
|
267
|
+
mesg = 'CPE 2.2 string appears to be invalid.'
|
|
268
|
+
raise s_exc.BadTypeValu(mesg=mesg, valu=valu)
|
|
269
|
+
|
|
270
|
+
return v2_2, {}
|
|
64
271
|
|
|
65
272
|
def _normPyList(self, parts):
|
|
66
273
|
return zipCpe22(parts), {}
|
|
@@ -77,7 +284,7 @@ def chopCpe22(text):
|
|
|
77
284
|
CPE 2.2 Formatted String
|
|
78
285
|
https://cpe.mitre.org/files/cpe-specification_2.2.pdf
|
|
79
286
|
'''
|
|
80
|
-
if not text.startswith('cpe:/'):
|
|
287
|
+
if not text.startswith('cpe:/'): # pragma: no cover
|
|
81
288
|
mesg = 'CPE 2.2 string is expected to start with "cpe:/"'
|
|
82
289
|
raise s_exc.BadTypeValu(valu=text, mesg=mesg)
|
|
83
290
|
|
|
@@ -89,6 +296,18 @@ def chopCpe22(text):
|
|
|
89
296
|
|
|
90
297
|
return parts
|
|
91
298
|
|
|
299
|
+
PART_IDX_PART = 0
|
|
300
|
+
PART_IDX_VENDOR = 1
|
|
301
|
+
PART_IDX_PRODUCT = 2
|
|
302
|
+
PART_IDX_VERSION = 3
|
|
303
|
+
PART_IDX_UPDATE = 4
|
|
304
|
+
PART_IDX_EDITION = 5
|
|
305
|
+
PART_IDX_LANG = 6
|
|
306
|
+
PART_IDX_SW_EDITION = 7
|
|
307
|
+
PART_IDX_TARGET_SW = 8
|
|
308
|
+
PART_IDX_TARGET_HW = 9
|
|
309
|
+
PART_IDX_OTHER = 10
|
|
310
|
+
|
|
92
311
|
class Cpe23Str(s_types.Str):
|
|
93
312
|
'''
|
|
94
313
|
CPE 2.3 Formatted String
|
|
@@ -119,31 +338,113 @@ class Cpe23Str(s_types.Str):
|
|
|
119
338
|
|
|
120
339
|
extsize = 11 - len(parts)
|
|
121
340
|
parts.extend(['*' for _ in range(extsize)])
|
|
341
|
+
|
|
342
|
+
v2_3 = 'cpe:2.3:' + ':'.join(parts)
|
|
343
|
+
|
|
344
|
+
v2_2 = copy.copy(parts)
|
|
345
|
+
for idx, part in enumerate(v2_2):
|
|
346
|
+
if part == '*':
|
|
347
|
+
v2_2[idx] = ''
|
|
348
|
+
continue
|
|
349
|
+
|
|
350
|
+
part = fsb_unescape(part)
|
|
351
|
+
v2_2[idx] = uri_quote(part)
|
|
352
|
+
|
|
353
|
+
v2_2[PART_IDX_EDITION] = uri_pack(
|
|
354
|
+
v2_2[PART_IDX_EDITION],
|
|
355
|
+
v2_2[PART_IDX_SW_EDITION],
|
|
356
|
+
v2_2[PART_IDX_TARGET_SW],
|
|
357
|
+
v2_2[PART_IDX_TARGET_HW],
|
|
358
|
+
v2_2[PART_IDX_OTHER]
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
v2_2 = v2_2[:7]
|
|
362
|
+
|
|
363
|
+
parts = [fsb_unescape(k) for k in parts]
|
|
364
|
+
|
|
122
365
|
elif text.startswith('cpe:/'):
|
|
366
|
+
|
|
367
|
+
v2_2 = text
|
|
123
368
|
# automatically normalize CPE 2.2 format to CPE 2.3
|
|
124
369
|
parts = chopCpe22(text)
|
|
370
|
+
|
|
371
|
+
# Account for blank fields
|
|
372
|
+
for idx, part in enumerate(parts):
|
|
373
|
+
if not part:
|
|
374
|
+
parts[idx] = '*'
|
|
375
|
+
|
|
125
376
|
extsize = 11 - len(parts)
|
|
126
377
|
parts.extend(['*' for _ in range(extsize)])
|
|
378
|
+
|
|
379
|
+
# URI bindings can pack extended attributes into the
|
|
380
|
+
# edition field, handle that here.
|
|
381
|
+
unpacked = uri_unpack(parts[PART_IDX_EDITION])
|
|
382
|
+
if unpacked:
|
|
383
|
+
(edition, sw_edition, target_sw, target_hw, other) = unpacked
|
|
384
|
+
|
|
385
|
+
if edition:
|
|
386
|
+
parts[PART_IDX_EDITION] = edition
|
|
387
|
+
else:
|
|
388
|
+
parts[PART_IDX_EDITION] = '*'
|
|
389
|
+
|
|
390
|
+
if sw_edition:
|
|
391
|
+
parts[PART_IDX_SW_EDITION] = sw_edition
|
|
392
|
+
|
|
393
|
+
if target_sw:
|
|
394
|
+
parts[PART_IDX_TARGET_SW] = target_sw
|
|
395
|
+
|
|
396
|
+
if target_hw:
|
|
397
|
+
parts[PART_IDX_TARGET_HW] = target_hw
|
|
398
|
+
|
|
399
|
+
if other:
|
|
400
|
+
parts[PART_IDX_OTHER] = other
|
|
401
|
+
|
|
402
|
+
parts = [uri_unquote(part) for part in parts]
|
|
403
|
+
|
|
404
|
+
# This feels a little uninuitive to escape parts for "escaped" and
|
|
405
|
+
# unescape parts for "parts" but values in parts could be incorrectly
|
|
406
|
+
# escaped or incorrectly unescaped so just do both.
|
|
407
|
+
escaped = [fsb_escape(part) for part in parts]
|
|
408
|
+
parts = [fsb_unescape(part) for part in parts]
|
|
409
|
+
|
|
410
|
+
v2_3 = 'cpe:2.3:' + ':'.join(escaped)
|
|
411
|
+
|
|
127
412
|
else:
|
|
128
413
|
mesg = 'CPE 2.3 string is expected to start with "cpe:2.3:"'
|
|
129
414
|
raise s_exc.BadTypeValu(valu=valu, mesg=mesg)
|
|
130
415
|
|
|
416
|
+
rgx = cpe23_regex.match(v2_3)
|
|
417
|
+
if rgx is None or rgx.group() != v2_3:
|
|
418
|
+
mesg = 'CPE 2.3 string appears to be invalid.'
|
|
419
|
+
raise s_exc.BadTypeValu(mesg=mesg, valu=valu)
|
|
420
|
+
|
|
421
|
+
if isinstance(v2_2, list):
|
|
422
|
+
cpe22 = zipCpe22(v2_2)
|
|
423
|
+
else:
|
|
424
|
+
cpe22 = v2_2
|
|
425
|
+
|
|
426
|
+
rgx = cpe22_regex.match(cpe22)
|
|
427
|
+
if rgx is None or rgx.group() != cpe22:
|
|
428
|
+
v2_2 = None
|
|
429
|
+
|
|
131
430
|
subs = {
|
|
132
|
-
'
|
|
133
|
-
'
|
|
134
|
-
'
|
|
135
|
-
'
|
|
136
|
-
'
|
|
137
|
-
'
|
|
138
|
-
'
|
|
139
|
-
'
|
|
140
|
-
'
|
|
141
|
-
'
|
|
142
|
-
'
|
|
143
|
-
'other': parts[10],
|
|
431
|
+
'part': parts[PART_IDX_PART],
|
|
432
|
+
'vendor': parts[PART_IDX_VENDOR],
|
|
433
|
+
'product': parts[PART_IDX_PRODUCT],
|
|
434
|
+
'version': parts[PART_IDX_VERSION],
|
|
435
|
+
'update': parts[PART_IDX_UPDATE],
|
|
436
|
+
'edition': parts[PART_IDX_EDITION],
|
|
437
|
+
'language': parts[PART_IDX_LANG],
|
|
438
|
+
'sw_edition': parts[PART_IDX_SW_EDITION],
|
|
439
|
+
'target_sw': parts[PART_IDX_TARGET_SW],
|
|
440
|
+
'target_hw': parts[PART_IDX_TARGET_HW],
|
|
441
|
+
'other': parts[PART_IDX_OTHER],
|
|
144
442
|
}
|
|
145
443
|
|
|
146
|
-
|
|
444
|
+
if v2_2 is not None:
|
|
445
|
+
subs['v2_2'] = v2_2
|
|
446
|
+
|
|
447
|
+
return v2_3, {'subs': subs}
|
|
147
448
|
|
|
148
449
|
class SemVer(s_types.Int):
|
|
149
450
|
'''
|
|
@@ -412,6 +713,13 @@ class ItModule(s_module.CoreModule):
|
|
|
412
713
|
'doc': 'A MITRE ATT&CK Campaign ID.',
|
|
413
714
|
'ex': 'C0028',
|
|
414
715
|
}),
|
|
716
|
+
('it:mitre:attack:datasource', ('str', {'regex': r'^DS[0-9]{4}$'}), {
|
|
717
|
+
'doc': 'A MITRE ATT&CK Datasource ID.',
|
|
718
|
+
'ex': 'DS0026',
|
|
719
|
+
}),
|
|
720
|
+
('it:mitre:attack:data:component', ('guid', {}), {
|
|
721
|
+
'doc': 'A MITRE ATT&CK data component.',
|
|
722
|
+
}),
|
|
415
723
|
('it:mitre:attack:flow', ('guid', {}), {
|
|
416
724
|
'doc': 'A MITRE ATT&CK Flow diagram.',
|
|
417
725
|
}),
|
|
@@ -1216,6 +1524,10 @@ class ItModule(s_module.CoreModule):
|
|
|
1216
1524
|
'uniq': True, 'sorted': True, 'split': ','}), {
|
|
1217
1525
|
'doc': 'An array of ATT&CK tactics that include this technique.',
|
|
1218
1526
|
}),
|
|
1527
|
+
('data:components', ('array', {'type': 'it:mitre:attack:data:component',
|
|
1528
|
+
'uniq': True, 'sorted': True}), {
|
|
1529
|
+
'doc': 'An array of MITRE ATT&CK data components that detect the ATT&CK technique.',
|
|
1530
|
+
}),
|
|
1219
1531
|
)),
|
|
1220
1532
|
('it:mitre:attack:software', {}, (
|
|
1221
1533
|
('software', ('it:prod:soft', {}), {
|
|
@@ -1335,6 +1647,27 @@ class ItModule(s_module.CoreModule):
|
|
|
1335
1647
|
('author:contact', ('ps:contact', {}), {
|
|
1336
1648
|
'doc': 'The contact information for the author of the ATT&CK Flow diagram.'}),
|
|
1337
1649
|
)),
|
|
1650
|
+
('it:mitre:attack:datasource', {}, (
|
|
1651
|
+
('name', ('str', {'lower': True, 'onespace': True}), {
|
|
1652
|
+
'doc': 'The name of the datasource.'}),
|
|
1653
|
+
('description', ('str', {}), {
|
|
1654
|
+
'disp': {'hint': 'text'},
|
|
1655
|
+
'doc': 'A description of the datasource.'}),
|
|
1656
|
+
('references', ('array', {'type': 'inet:url', 'uniq': True, 'sorted': True}), {
|
|
1657
|
+
'doc': 'An array of URLs that document the datasource.',
|
|
1658
|
+
}),
|
|
1659
|
+
)),
|
|
1660
|
+
('it:mitre:attack:data:component', {}, (
|
|
1661
|
+
('name', ('str', {'lower': True, 'onespace': True}), {
|
|
1662
|
+
'ro': True,
|
|
1663
|
+
'doc': 'The name of the data component.'}),
|
|
1664
|
+
('description', ('str', {}), {
|
|
1665
|
+
'disp': {'hint': 'text'},
|
|
1666
|
+
'doc': 'A description of the data component.'}),
|
|
1667
|
+
('datasource', ('it:mitre:attack:datasource', {}), {
|
|
1668
|
+
'ro': True,
|
|
1669
|
+
'doc': 'The datasource this data component belongs to.'}),
|
|
1670
|
+
)),
|
|
1338
1671
|
('it:dev:int', {}, ()),
|
|
1339
1672
|
('it:dev:pipe', {}, ()),
|
|
1340
1673
|
('it:dev:mutex', {}, ()),
|
|
@@ -1573,8 +1906,13 @@ class ItModule(s_module.CoreModule):
|
|
|
1573
1906
|
'doc': 'A brief description of the hardware.'}),
|
|
1574
1907
|
('cpe', ('it:sec:cpe', {}), {
|
|
1575
1908
|
'doc': 'The NIST CPE 2.3 string specifying this hardware.'}),
|
|
1909
|
+
('manufacturer', ('ou:org', {}), {
|
|
1910
|
+
'doc': 'The organization that manufactures this hardware.'}),
|
|
1911
|
+
('manufacturer:name', ('ou:name', {}), {
|
|
1912
|
+
'doc': 'The name of the organization that manufactures this hardware.'}),
|
|
1576
1913
|
('make', ('ou:name', {}), {
|
|
1577
|
-
'
|
|
1914
|
+
'deprecated': True,
|
|
1915
|
+
'doc': 'Deprecated. Please use :manufacturer:name.'}),
|
|
1578
1916
|
('model', ('str', {'lower': True, 'onespace': True}), {
|
|
1579
1917
|
'doc': 'The model name or number for this hardware specification.'}),
|
|
1580
1918
|
('version', ('str', {'lower': True, 'onespace': True}), {
|