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 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": "txt2stix Report MD5",
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
- return Relationship(
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
- stack_info=True,
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.summary = summary
420
- summary_note_obj = Note(
421
- type="note",
422
- spec_version="2.1",
423
- id=self.report.id.replace("report", "note"),
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
- "detected-using",
130
- description=f"{stix_objects[1]['value']} can be detected in the STIX pattern {indicator['name']}",
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
- "detected-using",
151
- description=f"{stix_objects[1]['value']} can be detected in the STIX pattern {indicator['name']}",
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
- "detected-using",
181
- description=f"{stix_objects[1]['value']} can be detected in the STIX pattern {indicator['name']}",
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
- "detected-using",
202
- description=f"{stix_objects[1]['value']} can be detected in the STIX pattern {indicator['name']}",
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
- "detected-using",
242
- description=f"{extracted_value} can be detected in the STIX pattern {indicator['name']}",
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
- "detected-using",
264
- description=f"{extracted_value} can be detected in the STIX pattern {indicator['name']}",
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
- "detected-using",
292
- description=f"{extracted_value} can be detected in the STIX pattern {indicator['name']}",
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
- "detected-using",
311
- description=f"{extracted_value} can be detected in the STIX pattern {indicator['name']}",
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": extracted_value}
312
+ {"type": "directory", "spec_version": "2.1", "path": str(path.parent)}
324
313
  )
325
- stix_objects.append(dir_obj)
326
- dir = stix_objects[-1]
327
- stix_objects.append(
328
- bundler.new_relationship(
329
- stix_objects[1].id,
330
- indicator["id"],
331
- "detected-using",
332
- description=f"{extracted_value} can be detected in the STIX pattern {indicator['name']}",
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
- dir.id,
342
- "directory",
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, [dir_obj.id]
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
- "detected-using",
375
- description=f"{extracted_value} can be detected in the STIX pattern {indicator['name']}",
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
- "detected-using",
397
- description=f"{extracted_value} can be detected in the STIX pattern {indicator['name']}",
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
- "detected-using",
419
- description=f"{extracted_value} can be detected in the STIX pattern {indicator['name']}",
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
- "detected-using",
446
- description=f"{extracted_value} can be detected in the STIX pattern {indicator['name']}",
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
- "detected-using",
465
- description=f"{extracted_value} can be detected in the STIX pattern {indicator['name']}",
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
- "detected-using",
497
- description=f"{extracted_value} can be detected in the STIX pattern {indicator['name']}",
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
- "detected-using",
523
- description=f"{extracted_value} can be detected in the STIX pattern {indicator['name']}",
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
- "detected-using",
547
- description=f"{extracted_value} can be detected in the STIX pattern {indicator['name']}",
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
- "detected-using",
575
- description=f"{extracted_value} can be detected in the STIX pattern {indicator['name']}",
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
- "detected-using",
605
- description=f"{extracted_value} can be detected in the STIX pattern {indicator['name']}",
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
- "detected-using",
638
- description=f"{extracted_value} can be detected in the STIX pattern {indicator['name']}",
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
- "detected-using",
668
- description=f"{extracted_value} can be detected in the STIX pattern {indicator['name']}",
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['CTIBUTLER_BASE_URL'] + '/'
14
- self.api_key = os.environ.get('CTIBUTLER_API_KEY')
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['VULMATCH_BASE_URL'] + '/'
17
- self.api_key = os.environ.get('VULMATCH_API_KEY')
22
+ self.api_root = os.environ["VULMATCH_BASE_URL"] + "/"
23
+ self.api_key = os.environ.get("VULMATCH_API_KEY")
18
24
  else:
19
- raise NotImplementedError("The type `%s` is not supported", host)
25
+ raise UnsupportedRemoteExtraction("The type `%s` is not supported", host)
26
+
20
27
  self.session = requests.Session()
21
- self.session.headers.update({
22
- "API-KEY": self.api_key,
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(self.api_root, f"v1/attack-{matrix}/objects/?attack_type=Tactic")
31
- version_url = urljoin(self.api_root, f'v1/attack-{matrix}/versions/installed/')
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()['latest'])
44
+ retval = dict(version=self.session.get(version_url).json()["latest"])
34
45
  for tac in tactics:
35
- retval[tac['x_mitre_shortname']] = tac
36
- retval[tac['external_references'][0]['external_id']] = 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(self.api_root, f"v1/attack-{matrix}/objects/?attack_id={','.join(attack_ids)}")
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(urljoin(self.api_root, f"v1/{type}/objects/{id}/"))
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(urljoin(self.api_root, f"v1/location/objects/?alpha2_code={id}"))
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(urljoin(self.api_root, f"v1/{type}/objects/?name={name}"))
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(urljoin(self.api_root, f"v1/{type}/objects/?alias={alias}"))
54
-
55
- def _retrieve_objects(self, endpoint, key='objects'):
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['page_results_count'] < d['page_size']:
87
+ page += 1
88
+ if d["page_results_count"] < d["page_size"]:
67
89
  break
68
90
  return data
69
-
70
- def retrieve_stix_objects(stix_mapping: str, id, host=None):
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
- object_path = stix_mapping
73
- if stix_mapping in ['location']:
74
- host = 'ctibutler'
75
- if not host:
76
- host, object_path = stix_mapping.split('-', 1)
77
- retreiver = STIXObjectRetriever(host)
78
- match object_path:
79
- ### ATT&CK by ID
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'txt2stix:{classification}'.lower())
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.13
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=kqUNW9_jktuMyWSkoAa-ydZY-L5gzSSkthb7OdhUiKo,16854
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=c6S0xx0K8JM-PT_Qd1PlN_ZlDXdnEwiRS8529iUp3yg,30774
7
+ txt2stix/indicator.py,sha256=lukbmRMrRTKvirs_BnIOoAcmRIdyqkodJGDmQE_D6YE,29621
8
8
  txt2stix/lookups.py,sha256=h42YVtYUkWZm6ZPv2h5hHDHDzDs3yBqrT_T7pj2MDZI,2301
9
- txt2stix/retriever.py,sha256=biRSRwYsZoSvR758y4OFONjfrEMcxgj1PLHFLFydoSU,5729
9
+ txt2stix/retriever.py,sha256=cOcC6h-eyutfdIFQqAPUodneBFwgk21q0u2rXI9L57Q,6177
10
10
  txt2stix/stix.py,sha256=9nXD9a2dCY4uaatl-mlIA1k3srwQBhGW-tUSho3iYe0,30
11
- txt2stix/txt2stix.py,sha256=Y7vr4zzh8PvFCD-pX8-qm8kxuintjkhnqQ-OYfq7CRs,18589
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.13.dist-info/METADATA,sha256=pzYsUzHCP45Ab_8MWy0slJzmFiXzNeiVg49Z53lMDG8,15483
117
- txt2stix-1.0.13.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
118
- txt2stix-1.0.13.dist-info/entry_points.txt,sha256=x6QPtt65hWeomw4IpJ_wQUesBl1M4WOLODbhOKyWMFg,55
119
- txt2stix-1.0.13.dist-info/licenses/LICENSE,sha256=BK8Ppqlc4pdgnNzIxnxde0taoQ1BgicdyqmBvMiNYgY,11364
120
- txt2stix-1.0.13.dist-info/RECORD,,
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,,