txt2stix 1.0.13__py3-none-any.whl → 1.1.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.
- txt2stix/bundler.py +21 -35
- txt2stix/indicator.py +80 -90
- txt2stix/retriever.py +124 -95
- txt2stix/txt2stix.py +1 -1
- {txt2stix-1.0.13.dist-info → txt2stix-1.1.0.dist-info}/METADATA +2 -1
- {txt2stix-1.0.13.dist-info → txt2stix-1.1.0.dist-info}/RECORD +9 -9
- {txt2stix-1.0.13.dist-info → txt2stix-1.1.0.dist-info}/WHEEL +0 -0
- {txt2stix-1.0.13.dist-info → txt2stix-1.1.0.dist-info}/entry_points.txt +0 -0
- {txt2stix-1.0.13.dist-info → txt2stix-1.1.0.dist-info}/licenses/LICENSE +0 -0
txt2stix/bundler.py
CHANGED
|
@@ -10,6 +10,7 @@ from stix2 import (
|
|
|
10
10
|
)
|
|
11
11
|
from stix2.parsing import dict_to_stix2, parse as parse_stix
|
|
12
12
|
from stix2.serialization import serialize
|
|
13
|
+
from stix2validator.v21.shoulds import relationships_strict
|
|
13
14
|
import hashlib
|
|
14
15
|
from stix2 import (
|
|
15
16
|
v21,
|
|
@@ -226,7 +227,7 @@ class txt2stixBundler:
|
|
|
226
227
|
"external_id": self.uuid,
|
|
227
228
|
},
|
|
228
229
|
{
|
|
229
|
-
"source_name": "
|
|
230
|
+
"source_name": "txt2stix_report_md5",
|
|
230
231
|
"description": self.report_md5,
|
|
231
232
|
},
|
|
232
233
|
] + external_references,
|
|
@@ -359,13 +360,15 @@ class txt2stixBundler:
|
|
|
359
360
|
))
|
|
360
361
|
|
|
361
362
|
def new_relationship(self, source_ref, target_ref, relationship_type, description=None, external_references=None):
|
|
362
|
-
|
|
363
|
+
relationship = dict(
|
|
363
364
|
id="relationship--"
|
|
364
365
|
+ str(
|
|
365
366
|
uuid.uuid5(
|
|
366
367
|
UUID_NAMESPACE, f"{relationship_type}+{source_ref}+{target_ref}"
|
|
367
368
|
)
|
|
368
369
|
),
|
|
370
|
+
type='relationship',
|
|
371
|
+
spec_version='2.1',
|
|
369
372
|
source_ref=source_ref,
|
|
370
373
|
target_ref=target_ref,
|
|
371
374
|
relationship_type=relationship_type,
|
|
@@ -374,7 +377,6 @@ class txt2stixBundler:
|
|
|
374
377
|
description=description,
|
|
375
378
|
modified=self.report.modified,
|
|
376
379
|
object_marking_refs=self.report.object_marking_refs,
|
|
377
|
-
allow_custom=True,
|
|
378
380
|
external_references=external_references or [
|
|
379
381
|
{
|
|
380
382
|
"source_name": "txt2stix_report_id",
|
|
@@ -382,6 +384,12 @@ class txt2stixBundler:
|
|
|
382
384
|
}
|
|
383
385
|
],
|
|
384
386
|
)
|
|
387
|
+
try:
|
|
388
|
+
relationships_strict(relationship)
|
|
389
|
+
except Exception as e:
|
|
390
|
+
relationship['relationship_type'] = 'related-to'
|
|
391
|
+
logger.debug(e)
|
|
392
|
+
return parse_stix(relationship, allow_custom=True)
|
|
385
393
|
|
|
386
394
|
def to_json(self):
|
|
387
395
|
return serialize(self.bundle, indent=4)
|
|
@@ -396,9 +404,10 @@ class txt2stixBundler:
|
|
|
396
404
|
self.add_indicator(ex, add_standard_relationship)
|
|
397
405
|
except BaseException as e:
|
|
398
406
|
logger.debug(
|
|
399
|
-
f"ran into exception while processing observable `{ex}
|
|
400
|
-
|
|
407
|
+
f"ran into exception while processing observable `{ex}`. {e}",
|
|
408
|
+
exc_info=True,
|
|
401
409
|
)
|
|
410
|
+
ex['error'] = str(e)
|
|
402
411
|
|
|
403
412
|
def process_relationships(self, observables):
|
|
404
413
|
for relationship in observables:
|
|
@@ -416,38 +425,15 @@ class txt2stixBundler:
|
|
|
416
425
|
)
|
|
417
426
|
|
|
418
427
|
def add_summary(self, summary, ai_summary_provider):
|
|
419
|
-
self.
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
created=self.report.created,
|
|
425
|
-
modified=self.report.modified,
|
|
426
|
-
created_by_ref=self.report.created_by_ref,
|
|
427
|
-
external_references=[
|
|
428
|
-
{
|
|
429
|
-
"source_name": "txt2stix_ai_summary_provider",
|
|
430
|
-
"external_id": ai_summary_provider,
|
|
431
|
-
},
|
|
432
|
-
],
|
|
433
|
-
abstract=f"AI Summary: {self.report.name}",
|
|
434
|
-
content=summary,
|
|
435
|
-
object_refs=[self.report.id],
|
|
436
|
-
object_marking_refs=self.report.object_marking_refs,
|
|
437
|
-
labels=self.report.get('labels'),
|
|
438
|
-
confidence=self.report.get('confidence')
|
|
439
|
-
)
|
|
440
|
-
|
|
441
|
-
self.add_ref(summary_note_obj)
|
|
442
|
-
self.add_ref(
|
|
443
|
-
self.new_relationship(
|
|
444
|
-
summary_note_obj["id"],
|
|
445
|
-
self.report.id,
|
|
446
|
-
relationship_type="summary-of",
|
|
447
|
-
description=f"AI generated summary for {self.report.name}",
|
|
448
|
-
external_references=summary_note_obj["external_references"],
|
|
428
|
+
self.report.external_references.append(
|
|
429
|
+
dict(
|
|
430
|
+
source_name='txt2stix_ai_summary',
|
|
431
|
+
external_id=ai_summary_provider,
|
|
432
|
+
description=summary
|
|
449
433
|
)
|
|
450
434
|
)
|
|
435
|
+
self.summary = summary
|
|
436
|
+
|
|
451
437
|
|
|
452
438
|
@property
|
|
453
439
|
def flow_objects(self):
|
txt2stix/indicator.py
CHANGED
|
@@ -24,7 +24,7 @@ if TYPE_CHECKING:
|
|
|
24
24
|
|
|
25
25
|
from .common import MinorException
|
|
26
26
|
|
|
27
|
-
from .retriever import retrieve_stix_objects
|
|
27
|
+
from .retriever import _retrieve_stix_objects, retrieve_stix_objects
|
|
28
28
|
|
|
29
29
|
logger = logging.getLogger("txt2stix.indicator")
|
|
30
30
|
|
|
@@ -93,7 +93,7 @@ def build_observables(
|
|
|
93
93
|
except BadDataException:
|
|
94
94
|
raise
|
|
95
95
|
except BaseException as e:
|
|
96
|
-
raise BadDataException("unknown data error") from e
|
|
96
|
+
raise BadDataException(f"unknown data error: {e}") from e
|
|
97
97
|
|
|
98
98
|
|
|
99
99
|
def _build_observables(
|
|
@@ -102,13 +102,6 @@ def _build_observables(
|
|
|
102
102
|
retrieved_objects = retrieve_stix_objects(stix_mapping, extracted_value)
|
|
103
103
|
if retrieved_objects:
|
|
104
104
|
return retrieved_objects, [sdo["id"] for sdo in retrieved_objects]
|
|
105
|
-
if retrieved_objects == []:
|
|
106
|
-
logger.warning(
|
|
107
|
-
f"could not find `{stix_mapping}` with id=`{extracted_value}` in remote"
|
|
108
|
-
)
|
|
109
|
-
raise BadDataException(
|
|
110
|
-
f"could not find `{stix_mapping}` with id=`{extracted_value}` in remote"
|
|
111
|
-
)
|
|
112
105
|
|
|
113
106
|
stix_objects = [indicator]
|
|
114
107
|
|
|
@@ -124,10 +117,10 @@ def _build_observables(
|
|
|
124
117
|
|
|
125
118
|
stix_objects.append(
|
|
126
119
|
bundler.new_relationship(
|
|
127
|
-
stix_objects[1].id,
|
|
128
120
|
indicator["id"],
|
|
129
|
-
|
|
130
|
-
|
|
121
|
+
stix_objects[1].id,
|
|
122
|
+
"related-to",
|
|
123
|
+
description=f"STIX pattern contains {stix_objects[1]['value']}",
|
|
131
124
|
external_references=indicator["external_references"],
|
|
132
125
|
)
|
|
133
126
|
)
|
|
@@ -145,10 +138,10 @@ def _build_observables(
|
|
|
145
138
|
id = stix_objects[-1].id
|
|
146
139
|
stix_objects.append(
|
|
147
140
|
bundler.new_relationship(
|
|
148
|
-
stix_objects[1].id,
|
|
149
141
|
indicator["id"],
|
|
150
|
-
|
|
151
|
-
|
|
142
|
+
stix_objects[1].id,
|
|
143
|
+
"related-to",
|
|
144
|
+
description=f"STIX pattern contains {stix_objects[1]['value']}",
|
|
152
145
|
)
|
|
153
146
|
)
|
|
154
147
|
|
|
@@ -175,10 +168,10 @@ def _build_observables(
|
|
|
175
168
|
)
|
|
176
169
|
stix_objects.append(
|
|
177
170
|
bundler.new_relationship(
|
|
178
|
-
stix_objects[1].id,
|
|
179
171
|
indicator["id"],
|
|
180
|
-
|
|
181
|
-
|
|
172
|
+
stix_objects[1].id,
|
|
173
|
+
"related-to",
|
|
174
|
+
description=f"STIX pattern contains {stix_objects[1]['value']}",
|
|
182
175
|
external_references=indicator["external_references"],
|
|
183
176
|
)
|
|
184
177
|
)
|
|
@@ -196,10 +189,10 @@ def _build_observables(
|
|
|
196
189
|
id = stix_objects[-1].id
|
|
197
190
|
stix_objects.append(
|
|
198
191
|
bundler.new_relationship(
|
|
199
|
-
stix_objects[1].id,
|
|
200
192
|
indicator["id"],
|
|
201
|
-
|
|
202
|
-
|
|
193
|
+
stix_objects[1].id,
|
|
194
|
+
"related-to",
|
|
195
|
+
description=f"STIX pattern contains {stix_objects[1]['value']}",
|
|
203
196
|
external_references=indicator["external_references"],
|
|
204
197
|
)
|
|
205
198
|
)
|
|
@@ -236,10 +229,10 @@ def _build_observables(
|
|
|
236
229
|
)
|
|
237
230
|
stix_objects.append(
|
|
238
231
|
bundler.new_relationship(
|
|
239
|
-
stix_objects[1].id,
|
|
240
232
|
indicator["id"],
|
|
241
|
-
|
|
242
|
-
|
|
233
|
+
stix_objects[1].id,
|
|
234
|
+
"related-to",
|
|
235
|
+
description=f"STIX pattern contains {extracted_value}",
|
|
243
236
|
external_references=indicator["external_references"],
|
|
244
237
|
)
|
|
245
238
|
)
|
|
@@ -258,10 +251,10 @@ def _build_observables(
|
|
|
258
251
|
)
|
|
259
252
|
stix_objects.append(
|
|
260
253
|
bundler.new_relationship(
|
|
261
|
-
stix_objects[1].id,
|
|
262
254
|
indicator["id"],
|
|
263
|
-
|
|
264
|
-
|
|
255
|
+
stix_objects[1].id,
|
|
256
|
+
"related-to",
|
|
257
|
+
description=f"STIX pattern contains {extracted_value}",
|
|
265
258
|
external_references=indicator["external_references"],
|
|
266
259
|
)
|
|
267
260
|
)
|
|
@@ -270,6 +263,8 @@ def _build_observables(
|
|
|
270
263
|
if stix_mapping in ["file", "directory-file"]:
|
|
271
264
|
if not mimetype:
|
|
272
265
|
raise BadDataException(f"invalid file extension in `{extracted_value}`")
|
|
266
|
+
|
|
267
|
+
if stix_mapping == "file":
|
|
273
268
|
file = dict_to_stix2(
|
|
274
269
|
{
|
|
275
270
|
"type": "file",
|
|
@@ -278,18 +273,16 @@ def _build_observables(
|
|
|
278
273
|
"mime_type": mimetype,
|
|
279
274
|
}
|
|
280
275
|
)
|
|
281
|
-
|
|
282
|
-
if stix_mapping == "file":
|
|
283
276
|
indicator["name"] = f"File name: {extracted_value}"
|
|
284
277
|
indicator["pattern"] = f"[ file:name = { repr(extracted_value) } ]"
|
|
285
278
|
|
|
286
279
|
stix_objects.append(file)
|
|
287
280
|
stix_objects.append(
|
|
288
281
|
bundler.new_relationship(
|
|
289
|
-
stix_objects[1].id,
|
|
290
282
|
indicator["id"],
|
|
291
|
-
|
|
292
|
-
|
|
283
|
+
stix_objects[1].id,
|
|
284
|
+
"related-to",
|
|
285
|
+
description=f"STIX pattern contains {extracted_value}",
|
|
293
286
|
external_references=indicator["external_references"],
|
|
294
287
|
)
|
|
295
288
|
)
|
|
@@ -305,46 +298,43 @@ def _build_observables(
|
|
|
305
298
|
)
|
|
306
299
|
stix_objects.append(
|
|
307
300
|
bundler.new_relationship(
|
|
308
|
-
stix_objects[1].id,
|
|
309
301
|
indicator["id"],
|
|
310
|
-
|
|
311
|
-
|
|
302
|
+
stix_objects[1].id,
|
|
303
|
+
"related-to",
|
|
304
|
+
description=f"STIX pattern contains {extracted_value}",
|
|
312
305
|
external_references=indicator["external_references"],
|
|
313
306
|
)
|
|
314
307
|
)
|
|
315
308
|
|
|
316
309
|
if stix_mapping == "directory-file":
|
|
317
310
|
path = parse_path(extracted_value)
|
|
318
|
-
extracted_value = str(path.parent)
|
|
319
|
-
indicator["name"] = f"Directory: {extracted_value}"
|
|
320
|
-
indicator["pattern"] = f"[ directory:path = { repr(extracted_value) } ]"
|
|
321
|
-
|
|
322
311
|
dir_obj = dict_to_stix2(
|
|
323
|
-
{"type": "directory", "spec_version": "2.1", "path":
|
|
312
|
+
{"type": "directory", "spec_version": "2.1", "path": str(path.parent)}
|
|
324
313
|
)
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
"
|
|
332
|
-
|
|
333
|
-
external_references=indicator["external_references"],
|
|
334
|
-
)
|
|
314
|
+
file = dict_to_stix2(
|
|
315
|
+
{
|
|
316
|
+
"type": "file",
|
|
317
|
+
"spec_version": "2.1",
|
|
318
|
+
"name": path.name,
|
|
319
|
+
"mime_type": mimetype,
|
|
320
|
+
"parent_directory_ref": dir_obj.id,
|
|
321
|
+
}
|
|
335
322
|
)
|
|
323
|
+
indicator["name"] = f"Directory File: {extracted_value}"
|
|
324
|
+
indicator["pattern"] = f"[ directory:path = { repr(dir_obj.path) } OR file:name = {repr(file.name)}]"
|
|
336
325
|
|
|
326
|
+
stix_objects.append(dir_obj)
|
|
337
327
|
stix_objects.append(file)
|
|
338
328
|
stix_objects.append(
|
|
339
329
|
bundler.new_relationship(
|
|
330
|
+
indicator["id"],
|
|
340
331
|
file.id,
|
|
341
|
-
|
|
342
|
-
"
|
|
343
|
-
description=f"{extracted_value} directory {indicator['name']}",
|
|
332
|
+
"related-to",
|
|
333
|
+
description=f"STIX pattern contains {extracted_value}",
|
|
344
334
|
external_references=indicator["external_references"],
|
|
345
335
|
)
|
|
346
336
|
)
|
|
347
|
-
return stix_objects, [
|
|
337
|
+
return stix_objects, [file.id]
|
|
348
338
|
|
|
349
339
|
if stix_mapping == "file-hash":
|
|
350
340
|
file_hash_type = (
|
|
@@ -369,10 +359,10 @@ def _build_observables(
|
|
|
369
359
|
)
|
|
370
360
|
stix_objects.append(
|
|
371
361
|
bundler.new_relationship(
|
|
372
|
-
stix_objects[1].id,
|
|
373
362
|
indicator["id"],
|
|
374
|
-
|
|
375
|
-
|
|
363
|
+
stix_objects[1].id,
|
|
364
|
+
"related-to",
|
|
365
|
+
description=f"STIX pattern contains {extracted_value}",
|
|
376
366
|
external_references=indicator["external_references"],
|
|
377
367
|
)
|
|
378
368
|
)
|
|
@@ -391,10 +381,10 @@ def _build_observables(
|
|
|
391
381
|
)
|
|
392
382
|
stix_objects.append(
|
|
393
383
|
bundler.new_relationship(
|
|
394
|
-
stix_objects[1].id,
|
|
395
384
|
indicator["id"],
|
|
396
|
-
|
|
397
|
-
|
|
385
|
+
stix_objects[1].id,
|
|
386
|
+
"related-to",
|
|
387
|
+
description=f"STIX pattern contains {extracted_value}",
|
|
398
388
|
external_references=indicator["external_references"],
|
|
399
389
|
)
|
|
400
390
|
)
|
|
@@ -413,10 +403,10 @@ def _build_observables(
|
|
|
413
403
|
)
|
|
414
404
|
stix_objects.append(
|
|
415
405
|
bundler.new_relationship(
|
|
416
|
-
stix_objects[1].id,
|
|
417
406
|
indicator["id"],
|
|
418
|
-
|
|
419
|
-
|
|
407
|
+
stix_objects[1].id,
|
|
408
|
+
"related-to",
|
|
409
|
+
description=f"STIX pattern contains {extracted_value}",
|
|
420
410
|
external_references=indicator["external_references"],
|
|
421
411
|
)
|
|
422
412
|
)
|
|
@@ -440,10 +430,10 @@ def _build_observables(
|
|
|
440
430
|
)
|
|
441
431
|
stix_objects.append(
|
|
442
432
|
bundler.new_relationship(
|
|
443
|
-
stix_objects[1].id,
|
|
444
433
|
indicator["id"],
|
|
445
|
-
|
|
446
|
-
|
|
434
|
+
stix_objects[1].id,
|
|
435
|
+
"related-to",
|
|
436
|
+
description=f"STIX pattern contains {extracted_value}",
|
|
447
437
|
external_references=indicator["external_references"],
|
|
448
438
|
)
|
|
449
439
|
)
|
|
@@ -459,10 +449,10 @@ def _build_observables(
|
|
|
459
449
|
)
|
|
460
450
|
stix_objects.append(
|
|
461
451
|
bundler.new_relationship(
|
|
462
|
-
stix_objects[1].id,
|
|
463
452
|
indicator["id"],
|
|
464
|
-
|
|
465
|
-
|
|
453
|
+
stix_objects[1].id,
|
|
454
|
+
"related-to",
|
|
455
|
+
description=f"STIX pattern contains {extracted_value}",
|
|
466
456
|
external_references=indicator["external_references"],
|
|
467
457
|
)
|
|
468
458
|
)
|
|
@@ -491,10 +481,10 @@ def _build_observables(
|
|
|
491
481
|
)
|
|
492
482
|
stix_objects.append(
|
|
493
483
|
bundler.new_relationship(
|
|
494
|
-
stix_objects[1].id,
|
|
495
484
|
indicator["id"],
|
|
496
|
-
|
|
497
|
-
|
|
485
|
+
stix_objects[1].id,
|
|
486
|
+
"related-to",
|
|
487
|
+
description=f"STIX pattern contains {extracted_value}",
|
|
498
488
|
external_references=indicator["external_references"],
|
|
499
489
|
)
|
|
500
490
|
)
|
|
@@ -517,10 +507,10 @@ def _build_observables(
|
|
|
517
507
|
stix_objects.extend(other_objects)
|
|
518
508
|
stix_objects.append(
|
|
519
509
|
bundler.new_relationship(
|
|
520
|
-
wallet_obj.id,
|
|
521
510
|
indicator["id"],
|
|
522
|
-
|
|
523
|
-
|
|
511
|
+
wallet_obj.id,
|
|
512
|
+
"related-to",
|
|
513
|
+
description=f"STIX pattern contains {extracted_value}",
|
|
524
514
|
external_references=indicator["external_references"],
|
|
525
515
|
)
|
|
526
516
|
)
|
|
@@ -541,10 +531,10 @@ def _build_observables(
|
|
|
541
531
|
stix_objects.extend(other_objects)
|
|
542
532
|
stix_objects.append(
|
|
543
533
|
bundler.new_relationship(
|
|
544
|
-
txn_object.id,
|
|
545
534
|
indicator["id"],
|
|
546
|
-
|
|
547
|
-
|
|
535
|
+
txn_object.id,
|
|
536
|
+
"related-to",
|
|
537
|
+
description=f"STIX pattern contains {extracted_value}",
|
|
548
538
|
external_references=indicator["external_references"],
|
|
549
539
|
)
|
|
550
540
|
)
|
|
@@ -569,10 +559,10 @@ def _build_observables(
|
|
|
569
559
|
stix_objects.extend(other_objects)
|
|
570
560
|
stix_objects.append(
|
|
571
561
|
bundler.new_relationship(
|
|
572
|
-
wallet_obj.id,
|
|
573
562
|
indicator["id"],
|
|
574
|
-
|
|
575
|
-
|
|
563
|
+
wallet_obj.id,
|
|
564
|
+
"related-to",
|
|
565
|
+
description=f"STIX pattern contains {extracted_value}",
|
|
576
566
|
external_references=indicator["external_references"],
|
|
577
567
|
)
|
|
578
568
|
)
|
|
@@ -599,10 +589,10 @@ def _build_observables(
|
|
|
599
589
|
|
|
600
590
|
stix_objects.append(
|
|
601
591
|
bundler.new_relationship(
|
|
602
|
-
card_object["id"],
|
|
603
592
|
indicator["id"],
|
|
604
|
-
"
|
|
605
|
-
|
|
593
|
+
card_object["id"],
|
|
594
|
+
"related-to",
|
|
595
|
+
description=f"STIX pattern contains {extracted_value}",
|
|
606
596
|
external_references=indicator["external_references"],
|
|
607
597
|
)
|
|
608
598
|
)
|
|
@@ -632,10 +622,10 @@ def _build_observables(
|
|
|
632
622
|
)
|
|
633
623
|
stix_objects.append(
|
|
634
624
|
bundler.new_relationship(
|
|
635
|
-
stix_objects[1].id,
|
|
636
625
|
indicator["id"],
|
|
637
|
-
|
|
638
|
-
|
|
626
|
+
stix_objects[1].id,
|
|
627
|
+
"related-to",
|
|
628
|
+
description=f"STIX pattern contains {extracted_value}",
|
|
639
629
|
external_references=indicator["external_references"],
|
|
640
630
|
)
|
|
641
631
|
)
|
|
@@ -662,10 +652,10 @@ def _build_observables(
|
|
|
662
652
|
)
|
|
663
653
|
stix_objects.append(
|
|
664
654
|
bundler.new_relationship(
|
|
665
|
-
stix_objects[1].id,
|
|
666
655
|
indicator["id"],
|
|
667
|
-
|
|
668
|
-
|
|
656
|
+
stix_objects[1].id,
|
|
657
|
+
"related-to",
|
|
658
|
+
description=f"STIX pattern contains {extracted_value}",
|
|
669
659
|
external_references=indicator["external_references"],
|
|
670
660
|
)
|
|
671
661
|
)
|
txt2stix/retriever.py
CHANGED
|
@@ -4,55 +4,77 @@ import dotenv, os
|
|
|
4
4
|
import stix2
|
|
5
5
|
import requests
|
|
6
6
|
|
|
7
|
+
from txt2stix.common import MinorException
|
|
8
|
+
|
|
7
9
|
dotenv.load_dotenv()
|
|
8
10
|
|
|
9
11
|
|
|
12
|
+
class UnsupportedRemoteExtraction(MinorException):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
10
16
|
class STIXObjectRetriever:
|
|
11
17
|
def __init__(self, host="ctibutler") -> None:
|
|
12
18
|
if host == "ctibutler":
|
|
13
|
-
self.api_root = os.environ[
|
|
14
|
-
self.api_key = os.environ.get(
|
|
19
|
+
self.api_root = os.environ["CTIBUTLER_BASE_URL"] + "/"
|
|
20
|
+
self.api_key = os.environ.get("CTIBUTLER_API_KEY")
|
|
15
21
|
elif host == "vulmatch":
|
|
16
|
-
self.api_root = os.environ[
|
|
17
|
-
self.api_key = os.environ.get(
|
|
22
|
+
self.api_root = os.environ["VULMATCH_BASE_URL"] + "/"
|
|
23
|
+
self.api_key = os.environ.get("VULMATCH_API_KEY")
|
|
18
24
|
else:
|
|
19
|
-
raise
|
|
25
|
+
raise UnsupportedRemoteExtraction("The type `%s` is not supported", host)
|
|
26
|
+
|
|
20
27
|
self.session = requests.Session()
|
|
21
|
-
self.session.headers.update(
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
self.session.headers.update(
|
|
29
|
+
{
|
|
30
|
+
"API-KEY": self.api_key,
|
|
31
|
+
}
|
|
32
|
+
)
|
|
24
33
|
|
|
25
34
|
def get_attack_object(self, matrix, attack_id):
|
|
26
35
|
endpoint = urljoin(self.api_root, f"v1/attack-{matrix}/objects/{attack_id}/")
|
|
27
36
|
return self._retrieve_objects(endpoint)
|
|
28
|
-
|
|
37
|
+
|
|
29
38
|
def get_attack_tactics(self, matrix):
|
|
30
|
-
endpoint = urljoin(
|
|
31
|
-
|
|
39
|
+
endpoint = urljoin(
|
|
40
|
+
self.api_root, f"v1/attack-{matrix}/objects/?attack_type=Tactic"
|
|
41
|
+
)
|
|
42
|
+
version_url = urljoin(self.api_root, f"v1/attack-{matrix}/versions/installed/")
|
|
32
43
|
tactics = self._retrieve_objects(endpoint)
|
|
33
|
-
retval = dict(version=self.session.get(version_url).json()[
|
|
44
|
+
retval = dict(version=self.session.get(version_url).json()["latest"])
|
|
34
45
|
for tac in tactics:
|
|
35
|
-
retval[tac[
|
|
36
|
-
retval[tac[
|
|
46
|
+
retval[tac["x_mitre_shortname"]] = tac
|
|
47
|
+
retval[tac["external_references"][0]["external_id"]] = tac
|
|
37
48
|
return retval
|
|
38
|
-
|
|
49
|
+
|
|
39
50
|
def get_attack_objects(self, matrix, attack_ids):
|
|
40
|
-
endpoint = urljoin(
|
|
51
|
+
endpoint = urljoin(
|
|
52
|
+
self.api_root,
|
|
53
|
+
f"v1/attack-{matrix}/objects/?attack_id={','.join(attack_ids)}",
|
|
54
|
+
)
|
|
41
55
|
return self._retrieve_objects(endpoint)
|
|
42
|
-
|
|
56
|
+
|
|
43
57
|
def get_objects_by_id(self, id, type):
|
|
44
|
-
return self._retrieve_objects(
|
|
45
|
-
|
|
58
|
+
return self._retrieve_objects(
|
|
59
|
+
urljoin(self.api_root, f"v1/{type}/objects/{id}/")
|
|
60
|
+
)
|
|
61
|
+
|
|
46
62
|
def get_location_objects(self, id):
|
|
47
|
-
return self._retrieve_objects(
|
|
48
|
-
|
|
63
|
+
return self._retrieve_objects(
|
|
64
|
+
urljoin(self.api_root, f"v1/location/objects/?alpha2_code={id}")
|
|
65
|
+
)
|
|
66
|
+
|
|
49
67
|
def get_objects_by_name(self, name, type):
|
|
50
|
-
return self._retrieve_objects(
|
|
51
|
-
|
|
68
|
+
return self._retrieve_objects(
|
|
69
|
+
urljoin(self.api_root, f"v1/{type}/objects/?name={name}")
|
|
70
|
+
)
|
|
71
|
+
|
|
52
72
|
def get_objects_by_alias(self, alias, type):
|
|
53
|
-
return self._retrieve_objects(
|
|
54
|
-
|
|
55
|
-
|
|
73
|
+
return self._retrieve_objects(
|
|
74
|
+
urljoin(self.api_root, f"v1/{type}/objects/?alias={alias}")
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def _retrieve_objects(self, endpoint, key="objects"):
|
|
56
78
|
data = []
|
|
57
79
|
page = 1
|
|
58
80
|
while True:
|
|
@@ -62,75 +84,82 @@ class STIXObjectRetriever:
|
|
|
62
84
|
if len(d[key]) == 0:
|
|
63
85
|
break
|
|
64
86
|
data.extend(d[key])
|
|
65
|
-
page+=1
|
|
66
|
-
if d[
|
|
87
|
+
page += 1
|
|
88
|
+
if d["page_results_count"] < d["page_size"]:
|
|
67
89
|
break
|
|
68
90
|
return data
|
|
69
|
-
|
|
70
|
-
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _retrieve_stix_objects(host, knowledge_base, filter_value):
|
|
94
|
+
retreiver = STIXObjectRetriever(host)
|
|
95
|
+
match knowledge_base:
|
|
96
|
+
### ATT&CK by ID
|
|
97
|
+
case "mitre-attack-ics-id":
|
|
98
|
+
return retreiver.get_attack_object("ics", filter_value)
|
|
99
|
+
case "mitre-attack-mobile-id":
|
|
100
|
+
return retreiver.get_attack_object("mobile", filter_value)
|
|
101
|
+
case "mitre-attack-enterprise-id":
|
|
102
|
+
return retreiver.get_attack_object("enterprise", filter_value)
|
|
103
|
+
|
|
104
|
+
### Others by ID
|
|
105
|
+
case "mitre-capec-id":
|
|
106
|
+
return retreiver.get_objects_by_id(filter_value, "capec")
|
|
107
|
+
case "mitre-atlas-id":
|
|
108
|
+
return retreiver.get_objects_by_id(filter_value, "atlas")
|
|
109
|
+
case "disarm-id":
|
|
110
|
+
return retreiver.get_objects_by_id(filter_value, "disarm")
|
|
111
|
+
case "mitre-cwe-id":
|
|
112
|
+
return retreiver.get_objects_by_id(filter_value, "cwe")
|
|
113
|
+
case "cve-id":
|
|
114
|
+
return retreiver.get_objects_by_id(filter_value, "cve")
|
|
115
|
+
case "cpe-id":
|
|
116
|
+
return retreiver.get_objects_by_id(filter_value, "cpe")
|
|
117
|
+
case "location":
|
|
118
|
+
return retreiver.get_location_objects(filter_value)
|
|
119
|
+
|
|
120
|
+
### ATT&CK by Name
|
|
121
|
+
case "mitre-attack-enterprise-name":
|
|
122
|
+
return retreiver.get_objects_by_name(filter_value, "attack-enterprise")
|
|
123
|
+
case "mitre-attack-mobile-name":
|
|
124
|
+
return retreiver.get_objects_by_name(filter_value, "attack-mobile")
|
|
125
|
+
case "mitre-attack-ics-name":
|
|
126
|
+
return retreiver.get_objects_by_name(filter_value, "attack-ics")
|
|
127
|
+
|
|
128
|
+
### ATT&CK by Alias
|
|
129
|
+
case "mitre-attack-enterprise-aliases":
|
|
130
|
+
return retreiver.get_objects_by_alias(filter_value, "attack-enterprise")
|
|
131
|
+
case "mitre-attack-mobile-aliases":
|
|
132
|
+
return retreiver.get_objects_by_alias(filter_value, "attack-mobile")
|
|
133
|
+
case "mitre-attack-ics-aliases":
|
|
134
|
+
return retreiver.get_objects_by_alias(filter_value, "attack-ics")
|
|
135
|
+
|
|
136
|
+
### OTHERS by Name
|
|
137
|
+
case "mitre-capec-name":
|
|
138
|
+
return retreiver.get_objects_by_name(filter_value, "capec")
|
|
139
|
+
case "mitre-cwe-name":
|
|
140
|
+
return retreiver.get_objects_by_name(filter_value, "cwe")
|
|
141
|
+
case "mitre-atlas-name":
|
|
142
|
+
return retreiver.get_objects_by_name(filter_value, "atlas")
|
|
143
|
+
case "disarm-name":
|
|
144
|
+
return retreiver.get_objects_by_name(filter_value, "disarm")
|
|
145
|
+
case _:
|
|
146
|
+
raise UnsupportedRemoteExtraction(
|
|
147
|
+
f"pair {(host, knowledge_base)=} not implemented"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def retrieve_stix_objects(stix_mapping: str, filter_value, host=None):
|
|
152
|
+
knowledge_base = stix_mapping
|
|
153
|
+
if stix_mapping in ["location"]:
|
|
154
|
+
host = "ctibutler"
|
|
155
|
+
if not host:
|
|
156
|
+
host, _, knowledge_base = stix_mapping.partition("-")
|
|
71
157
|
try:
|
|
72
|
-
|
|
73
|
-
if
|
|
74
|
-
host
|
|
75
|
-
if
|
|
76
|
-
host
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
case 'mitre-attack-ics-id':
|
|
81
|
-
return retreiver.get_attack_object('ics', id)
|
|
82
|
-
case 'mitre-attack-mobile-id':
|
|
83
|
-
return retreiver.get_attack_object('mobile', id)
|
|
84
|
-
case 'mitre-attack-enterprise-id':
|
|
85
|
-
return retreiver.get_attack_object('enterprise', id)
|
|
86
|
-
|
|
87
|
-
### Others by ID
|
|
88
|
-
case "mitre-capec-id":
|
|
89
|
-
return retreiver.get_objects_by_id(id, 'capec')
|
|
90
|
-
case "mitre-atlas-id":
|
|
91
|
-
return retreiver.get_objects_by_id(id, 'atlas')
|
|
92
|
-
case "disarm-id":
|
|
93
|
-
return retreiver.get_objects_by_id(id, 'disarm')
|
|
94
|
-
case "mitre-cwe-id":
|
|
95
|
-
return retreiver.get_objects_by_id(id, 'cwe')
|
|
96
|
-
case "cve-id":
|
|
97
|
-
return retreiver.get_objects_by_id(id, 'cve')
|
|
98
|
-
case "cpe-id":
|
|
99
|
-
return retreiver.get_objects_by_id(id, 'cpe')
|
|
100
|
-
case "location":
|
|
101
|
-
return retreiver.get_location_objects(id)
|
|
102
|
-
|
|
103
|
-
### ATT&CK by Name
|
|
104
|
-
case "mitre-attack-enterprise-name":
|
|
105
|
-
return retreiver.get_objects_by_name(id, 'attack-enterprise')
|
|
106
|
-
case "mitre-attack-mobile-name":
|
|
107
|
-
return retreiver.get_objects_by_name(id, 'attack-mobile')
|
|
108
|
-
case "mitre-attack-ics-name":
|
|
109
|
-
return retreiver.get_objects_by_name(id, 'attack-ics')
|
|
110
|
-
|
|
111
|
-
### ATT&CK by Alias
|
|
112
|
-
case "mitre-attack-enterprise-aliases":
|
|
113
|
-
return retreiver.get_objects_by_alias(id, 'attack-enterprise')
|
|
114
|
-
case "mitre-attack-mobile-aliases":
|
|
115
|
-
return retreiver.get_objects_by_alias(id, 'attack-mobile')
|
|
116
|
-
case "mitre-attack-ics-aliases":
|
|
117
|
-
return retreiver.get_objects_by_alias(id, 'attack-ics')
|
|
118
|
-
|
|
119
|
-
### OTHERS by Name
|
|
120
|
-
case "mitre-capec-name":
|
|
121
|
-
return retreiver.get_objects_by_name(id, 'capec')
|
|
122
|
-
case "mitre-cwe-name":
|
|
123
|
-
return retreiver.get_objects_by_name(id, 'cwe')
|
|
124
|
-
case "mitre-atlas-name":
|
|
125
|
-
return retreiver.get_objects_by_name(id, 'atlas')
|
|
126
|
-
case "disarm-name":
|
|
127
|
-
return retreiver.get_objects_by_name(id, 'disarm')
|
|
128
|
-
case _:
|
|
129
|
-
raise NotImplementedError(f"pair {(host, object_path)=} not implemented")
|
|
130
|
-
except (NotImplementedError, ValueError):
|
|
131
|
-
pass
|
|
132
|
-
except Exception as e:
|
|
133
|
-
msg = f"failed to get {object_path} for {id} from {host}"
|
|
134
|
-
logging.info(msg)
|
|
135
|
-
logging.debug(msg, exc_info=True)
|
|
136
|
-
return None
|
|
158
|
+
objects = _retrieve_stix_objects(host, knowledge_base, filter_value)
|
|
159
|
+
if not objects:
|
|
160
|
+
raise MinorException(f"{host} returned no data")
|
|
161
|
+
if len(objects)> 15:
|
|
162
|
+
raise MinorException(f"{host} returned too much data ({len(objects)})")
|
|
163
|
+
return objects
|
|
164
|
+
except UnsupportedRemoteExtraction:
|
|
165
|
+
return None
|
txt2stix/txt2stix.py
CHANGED
|
@@ -394,7 +394,7 @@ def run_txt2stix(bundler: txt2stixBundler, preprocessed_text: str, extractors_ma
|
|
|
394
394
|
logging.info("=== ai-check-content output ====")
|
|
395
395
|
logging.info(retval.content_check.model_dump_json())
|
|
396
396
|
for classification in retval.content_check.incident_classification:
|
|
397
|
-
bundler.report.labels.append(f'
|
|
397
|
+
bundler.report.labels.append(f'classification.{classification}'.lower())
|
|
398
398
|
bundler.add_summary(retval.content_check.summary, model.extractor_name)
|
|
399
399
|
|
|
400
400
|
if should_extract or ai_extract_if_no_incidence:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: txt2stix
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: txt2stix is a Python script that is designed to identify and extract IoCs and TTPs from text files, identify the relationships between them, convert them to STIX 2.1 objects, and output as a STIX 2.1 bundle.
|
|
5
5
|
Project-URL: Homepage, https://github.com/muchdogesec/txt2stix
|
|
6
6
|
Project-URL: Issues, https://github.com/muchdogesec/txt2stix/issues
|
|
@@ -22,6 +22,7 @@ Requires-Dist: phonenumbers>=8.13.39
|
|
|
22
22
|
Requires-Dist: python-dotenv>=1.0.1
|
|
23
23
|
Requires-Dist: requests>=2.32.4
|
|
24
24
|
Requires-Dist: schwifty>=2024.6.1
|
|
25
|
+
Requires-Dist: stix2-validator
|
|
25
26
|
Requires-Dist: stix2extensions
|
|
26
27
|
Requires-Dist: tld>=0.13
|
|
27
28
|
Requires-Dist: tldextract>=5.1.2
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
txt2stix/__init__.py,sha256=Sm_VT913IFuAZ6dJEdVz3baPwC5VYtHySVfBAOUG92w,803
|
|
2
2
|
txt2stix/attack_flow.py,sha256=FA5mRf4iVe9E6e_WfGF9PK6MTz8f3UhvDKjO_PDFHso,9164
|
|
3
|
-
txt2stix/bundler.py,sha256=
|
|
3
|
+
txt2stix/bundler.py,sha256=TLGvi19mPiAVwz4434PcyPiO_FGFFo35hQvcJoNQrN4,16264
|
|
4
4
|
txt2stix/common.py,sha256=ISnGNKqJPE1EcfhL-x_4G18mcwt1urmorkW-ru9kV-0,585
|
|
5
5
|
txt2stix/credential_checker.py,sha256=eWDP-jY3-jm8zI0JMoUcyoQZ_JqPNfCIr_HAO8nVYz0,3044
|
|
6
6
|
txt2stix/extractions.py,sha256=_tlsqYHhfAoV-PJzxRHysrX47uxCsMlSg7PQWxww1u0,2171
|
|
7
|
-
txt2stix/indicator.py,sha256=
|
|
7
|
+
txt2stix/indicator.py,sha256=lukbmRMrRTKvirs_BnIOoAcmRIdyqkodJGDmQE_D6YE,29621
|
|
8
8
|
txt2stix/lookups.py,sha256=h42YVtYUkWZm6ZPv2h5hHDHDzDs3yBqrT_T7pj2MDZI,2301
|
|
9
|
-
txt2stix/retriever.py,sha256=
|
|
9
|
+
txt2stix/retriever.py,sha256=cOcC6h-eyutfdIFQqAPUodneBFwgk21q0u2rXI9L57Q,6177
|
|
10
10
|
txt2stix/stix.py,sha256=9nXD9a2dCY4uaatl-mlIA1k3srwQBhGW-tUSho3iYe0,30
|
|
11
|
-
txt2stix/txt2stix.py,sha256=
|
|
11
|
+
txt2stix/txt2stix.py,sha256=4iVvzlLbUeDIKUPPHGUWZufsy-LIMPk6ejrw8kSI1o8,18595
|
|
12
12
|
txt2stix/utils.py,sha256=n6mh4t9ZRJ7iT4Jvp9ai_dfCXjgXNcRtF_zXO7nkpnk,3304
|
|
13
13
|
txt2stix/ai_extractor/__init__.py,sha256=5Tf6Co9THzytBdFEVhD-7vvT05TT3nSpltnAV1sfdoM,349
|
|
14
14
|
txt2stix/ai_extractor/anthropic.py,sha256=mdz-8CB-BSCEqnK5l35DRZURVPUf508ef2b48XMxmuk,441
|
|
@@ -113,8 +113,8 @@ txt2stix/includes/lookups/threat_actor.txt,sha256=QfDO9maQuqKBgW_Sdd7VGv1SHZ9Ra-
|
|
|
113
113
|
txt2stix/includes/lookups/tld.txt,sha256=-MEgJea2NMG_KDsnc4BVvI8eRk5Dm93L-t8SGYx5wMo,8598
|
|
114
114
|
txt2stix/includes/lookups/tool.txt,sha256=HGKG6JpUE26w6ezzSxOjBkp15UpSaB7N-mZ_NU_3G7A,6
|
|
115
115
|
txt2stix/includes/tests/test_cases.yaml,sha256=QD1FdIunpPkOpsn6wJRqs2vil_hv8OSVaqUp4a96aZg,22247
|
|
116
|
-
txt2stix-1.0.
|
|
117
|
-
txt2stix-1.0.
|
|
118
|
-
txt2stix-1.0.
|
|
119
|
-
txt2stix-1.0.
|
|
120
|
-
txt2stix-1.0.
|
|
116
|
+
txt2stix-1.1.0.dist-info/METADATA,sha256=gnYa1a2OgArD_OllpkEDC78bQjaY4bevDONJCP7vmm4,15513
|
|
117
|
+
txt2stix-1.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
118
|
+
txt2stix-1.1.0.dist-info/entry_points.txt,sha256=x6QPtt65hWeomw4IpJ_wQUesBl1M4WOLODbhOKyWMFg,55
|
|
119
|
+
txt2stix-1.1.0.dist-info/licenses/LICENSE,sha256=BK8Ppqlc4pdgnNzIxnxde0taoQ1BgicdyqmBvMiNYgY,11364
|
|
120
|
+
txt2stix-1.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|