pycti 6.3.1__tar.gz → 6.3.3__tar.gz
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 pycti might be problematic. Click here for more details.
- {pycti-6.3.1 → pycti-6.3.3}/PKG-INFO +1 -1
- {pycti-6.3.1 → pycti-6.3.3}/pycti/__init__.py +1 -1
- {pycti-6.3.1 → pycti-6.3.3}/pycti/connector/opencti_connector_helper.py +14 -13
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_case_rfi.py +1 -1
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_case_rft.py +1 -1
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_kill_chain_phase.py +4 -6
- {pycti-6.3.1 → pycti-6.3.3}/pycti/utils/opencti_stix2.py +9 -4
- pycti-6.3.3/pycti/utils/opencti_stix2_identifier.py +22 -0
- pycti-6.3.3/pycti/utils/opencti_stix2_splitter.py +263 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/utils/opencti_stix2_utils.py +47 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti.egg-info/PKG-INFO +1 -1
- {pycti-6.3.1 → pycti-6.3.3}/pycti.egg-info/SOURCES.txt +1 -0
- pycti-6.3.1/pycti/utils/opencti_stix2_splitter.py +0 -144
- {pycti-6.3.1 → pycti-6.3.3}/LICENSE +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/README.md +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/api/__init__.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/api/opencti_api_client.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/api/opencti_api_connector.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/api/opencti_api_playbook.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/api/opencti_api_work.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/connector/__init__.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/connector/opencti_connector.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/connector/opencti_metric_handler.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/__init__.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/indicator/__init__.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/indicator/opencti_indicator_properties.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_attack_pattern.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_campaign.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_case_incident.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_channel.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_course_of_action.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_data_component.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_data_source.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_event.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_external_reference.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_feedback.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_grouping.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_identity.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_incident.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_indicator.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_infrastructure.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_intrusion_set.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_label.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_language.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_location.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_malware.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_malware_analysis.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_marking_definition.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_narrative.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_note.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_observed_data.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_opinion.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_report.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_stix.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_stix_core_object.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_stix_core_relationship.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_stix_cyber_observable.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_stix_domain_object.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_stix_nested_ref_relationship.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_stix_object_or_stix_relationship.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_stix_sighting_relationship.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_task.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_threat_actor.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_threat_actor_group.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_threat_actor_individual.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_tool.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_vocabulary.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/opencti_vulnerability.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/stix_cyber_observable/__init__.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_deprecated.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_properties.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/utils/__init__.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/utils/constants.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/utils/opencti_logger.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti/utils/opencti_stix2_update.py +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti.egg-info/dependency_links.txt +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti.egg-info/requires.txt +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pycti.egg-info/top_level.txt +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/pyproject.toml +0 -0
- {pycti-6.3.1 → pycti-6.3.3}/setup.cfg +0 -0
|
@@ -376,7 +376,7 @@ class ListenQueue(threading.Thread):
|
|
|
376
376
|
self.pika_credentials = pika.PlainCredentials(self.user, self.password)
|
|
377
377
|
self.pika_parameters = pika.ConnectionParameters(
|
|
378
378
|
heartbeat=10,
|
|
379
|
-
blocked_connection_timeout=
|
|
379
|
+
blocked_connection_timeout=30,
|
|
380
380
|
host=self.host,
|
|
381
381
|
port=self.port,
|
|
382
382
|
virtual_host=self.vhost,
|
|
@@ -1556,6 +1556,8 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1556
1556
|
:type entities_types: list, optional
|
|
1557
1557
|
:param update: whether to updated data in the database, defaults to False
|
|
1558
1558
|
:type update: bool, optional
|
|
1559
|
+
:param bypass_split: use to prevent splitting of the bundle. This option has been removed since 6.3 and is no longer used.
|
|
1560
|
+
:type bypass_split: bool, optional
|
|
1559
1561
|
:raises ValueError: if the bundle is empty
|
|
1560
1562
|
:return: list of bundles
|
|
1561
1563
|
:rtype: list
|
|
@@ -1564,11 +1566,11 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1564
1566
|
entities_types = kwargs.get("entities_types", None)
|
|
1565
1567
|
update = kwargs.get("update", False)
|
|
1566
1568
|
event_version = kwargs.get("event_version", None)
|
|
1567
|
-
bypass_split = kwargs.get("bypass_split", False)
|
|
1568
1569
|
bypass_validation = kwargs.get("bypass_validation", False)
|
|
1569
1570
|
entity_id = kwargs.get("entity_id", None)
|
|
1570
1571
|
file_name = kwargs.get("file_name", None)
|
|
1571
1572
|
bundle_send_to_queue = kwargs.get("send_to_queue", self.bundle_send_to_queue)
|
|
1573
|
+
cleanup_inconsistent_bundle = kwargs.get("cleanup_inconsistent_bundle", False)
|
|
1572
1574
|
bundle_send_to_directory = kwargs.get(
|
|
1573
1575
|
"send_to_directory", self.bundle_send_to_directory
|
|
1574
1576
|
)
|
|
@@ -1690,17 +1692,16 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1690
1692
|
final_write_file = os.path.join(bundle_send_to_directory_path, bundle_file)
|
|
1691
1693
|
os.rename(write_file, final_write_file)
|
|
1692
1694
|
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
expectations_number
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
)
|
|
1695
|
+
stix2_splitter = OpenCTIStix2Splitter()
|
|
1696
|
+
(
|
|
1697
|
+
expectations_number,
|
|
1698
|
+
bundles,
|
|
1699
|
+
) = stix2_splitter.split_bundle_with_expectations(
|
|
1700
|
+
bundle=bundle,
|
|
1701
|
+
use_json=True,
|
|
1702
|
+
event_version=event_version,
|
|
1703
|
+
cleanup_inconsistent_bundle=cleanup_inconsistent_bundle,
|
|
1704
|
+
)
|
|
1704
1705
|
|
|
1705
1706
|
if len(bundles) == 0:
|
|
1706
1707
|
self.metric.inc("error_count")
|
|
@@ -747,7 +747,7 @@ class CaseRfi:
|
|
|
747
747
|
},
|
|
748
748
|
)
|
|
749
749
|
query = """
|
|
750
|
-
mutation CaseRfiEditRelationAdd($id: ID!, $input: StixRefRelationshipAddInput) {
|
|
750
|
+
mutation CaseRfiEditRelationAdd($id: ID!, $input: StixRefRelationshipAddInput!) {
|
|
751
751
|
stixDomainObjectEdit(id: $id) {
|
|
752
752
|
relationAdd(input: $input) {
|
|
753
753
|
id
|
|
@@ -746,7 +746,7 @@ class CaseRft:
|
|
|
746
746
|
},
|
|
747
747
|
)
|
|
748
748
|
query = """
|
|
749
|
-
mutation CaseRftEditRelationAdd($id: ID!, $input: StixRefRelationshipAddInput) {
|
|
749
|
+
mutation CaseRftEditRelationAdd($id: ID!, $input: StixRefRelationshipAddInput!) {
|
|
750
750
|
stixDomainObjectEdit(id: $id) {
|
|
751
751
|
relationAdd(input: $input) {
|
|
752
752
|
id
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
import uuid
|
|
5
4
|
|
|
6
|
-
from
|
|
5
|
+
from pycti.utils.opencti_stix2_identifier import kill_chain_phase_generate_id
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
class KillChainPhase:
|
|
@@ -25,10 +24,9 @@ class KillChainPhase:
|
|
|
25
24
|
|
|
26
25
|
@staticmethod
|
|
27
26
|
def generate_id(phase_name, kill_chain_name):
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return "kill-chain-phase--" + id
|
|
27
|
+
return kill_chain_phase_generate_id(
|
|
28
|
+
phase_name=phase_name, kill_chain_name=kill_chain_name
|
|
29
|
+
)
|
|
32
30
|
|
|
33
31
|
"""
|
|
34
32
|
List Kill-Chain-Phase objects
|
|
@@ -2280,6 +2280,12 @@ class OpenCTIStix2:
|
|
|
2280
2280
|
do_list = lister.get(
|
|
2281
2281
|
entity_type, lambda **kwargs: self.unknown_type({"type": entity_type})
|
|
2282
2282
|
)
|
|
2283
|
+
|
|
2284
|
+
if getAll and (orderBy is None or orderBy == "_score"):
|
|
2285
|
+
orderBy = "created_at"
|
|
2286
|
+
if orderMode is None:
|
|
2287
|
+
orderMode = "desc"
|
|
2288
|
+
|
|
2283
2289
|
# noinspection PyTypeChecker
|
|
2284
2290
|
return do_list(
|
|
2285
2291
|
search=search,
|
|
@@ -2619,10 +2625,9 @@ class OpenCTIStix2:
|
|
|
2619
2625
|
else None
|
|
2620
2626
|
)
|
|
2621
2627
|
stix2_splitter = OpenCTIStix2Splitter()
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
bundles = [stix_bundle]
|
|
2628
|
+
_, bundles = stix2_splitter.split_bundle_with_expectations(
|
|
2629
|
+
stix_bundle, False, event_version
|
|
2630
|
+
)
|
|
2626
2631
|
# Import every element in a specific order
|
|
2627
2632
|
imported_elements = []
|
|
2628
2633
|
for bundle in bundles:
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
|
|
3
|
+
from stix2.canonicalization.Canonicalize import canonicalize
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def external_reference_generate_id(url=None, source_name=None, external_id=None):
|
|
7
|
+
if url is not None:
|
|
8
|
+
data = {"url": url}
|
|
9
|
+
elif source_name is not None and external_id is not None:
|
|
10
|
+
data = {"source_name": source_name, "external_id": external_id}
|
|
11
|
+
else:
|
|
12
|
+
return None
|
|
13
|
+
data = canonicalize(data, utf8=False)
|
|
14
|
+
id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
|
|
15
|
+
return "external-reference--" + id
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def kill_chain_phase_generate_id(phase_name, kill_chain_name):
|
|
19
|
+
data = {"phase_name": phase_name, "kill_chain_name": kill_chain_name}
|
|
20
|
+
data = canonicalize(data, utf8=False)
|
|
21
|
+
id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
|
|
22
|
+
return "kill-chain-phase--" + id
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import uuid
|
|
3
|
+
from typing import Tuple
|
|
4
|
+
|
|
5
|
+
from typing_extensions import deprecated
|
|
6
|
+
|
|
7
|
+
from pycti.utils.opencti_stix2_identifier import (
|
|
8
|
+
external_reference_generate_id,
|
|
9
|
+
kill_chain_phase_generate_id,
|
|
10
|
+
)
|
|
11
|
+
from pycti.utils.opencti_stix2_utils import (
|
|
12
|
+
STIX_CYBER_OBSERVABLE_MAPPING,
|
|
13
|
+
SUPPORTED_STIX_ENTITY_OBJECTS,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
supported_types = (
|
|
17
|
+
SUPPORTED_STIX_ENTITY_OBJECTS # entities
|
|
18
|
+
+ list(STIX_CYBER_OBSERVABLE_MAPPING.keys()) # observables
|
|
19
|
+
+ ["relationship", "sighting"] # relationships
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def is_id_supported(key):
|
|
24
|
+
id_type = key.split("--")[0]
|
|
25
|
+
return id_type in supported_types
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class OpenCTIStix2Splitter:
|
|
29
|
+
def __init__(self):
|
|
30
|
+
self.cache_index = {}
|
|
31
|
+
self.cache_refs = {}
|
|
32
|
+
self.elements = []
|
|
33
|
+
|
|
34
|
+
def enlist_element(
|
|
35
|
+
self, item_id, raw_data, cleanup_inconsistent_bundle, parent_acc
|
|
36
|
+
):
|
|
37
|
+
nb_deps = 1
|
|
38
|
+
if item_id not in raw_data:
|
|
39
|
+
return 0
|
|
40
|
+
|
|
41
|
+
existing_item = self.cache_index.get(item_id)
|
|
42
|
+
if existing_item is not None:
|
|
43
|
+
return existing_item["nb_deps"]
|
|
44
|
+
|
|
45
|
+
item = raw_data[item_id]
|
|
46
|
+
if self.cache_refs.get(item_id) is None:
|
|
47
|
+
self.cache_refs[item_id] = []
|
|
48
|
+
for key in list(item.keys()):
|
|
49
|
+
value = item[key]
|
|
50
|
+
# Recursive enlist for every refs
|
|
51
|
+
if key.endswith("_refs"):
|
|
52
|
+
to_keep = []
|
|
53
|
+
for element_ref in item[key]:
|
|
54
|
+
# We need to check if this ref is not already a reference
|
|
55
|
+
is_missing_ref = raw_data.get(element_ref) is None
|
|
56
|
+
must_be_cleaned = is_missing_ref and cleanup_inconsistent_bundle
|
|
57
|
+
not_dependency_ref = (
|
|
58
|
+
self.cache_refs.get(element_ref) is None
|
|
59
|
+
or item_id not in self.cache_refs[element_ref]
|
|
60
|
+
)
|
|
61
|
+
# Prevent any self reference
|
|
62
|
+
if (
|
|
63
|
+
is_id_supported(element_ref)
|
|
64
|
+
and not must_be_cleaned
|
|
65
|
+
and element_ref not in parent_acc
|
|
66
|
+
and element_ref != item_id
|
|
67
|
+
and not_dependency_ref
|
|
68
|
+
):
|
|
69
|
+
self.cache_refs[item_id].append(element_ref)
|
|
70
|
+
nb_deps += self.enlist_element(
|
|
71
|
+
element_ref,
|
|
72
|
+
raw_data,
|
|
73
|
+
cleanup_inconsistent_bundle,
|
|
74
|
+
parent_acc + [element_ref],
|
|
75
|
+
)
|
|
76
|
+
if element_ref not in to_keep:
|
|
77
|
+
to_keep.append(element_ref)
|
|
78
|
+
item[key] = to_keep
|
|
79
|
+
elif key.endswith("_ref"):
|
|
80
|
+
is_missing_ref = raw_data.get(value) is None
|
|
81
|
+
must_be_cleaned = is_missing_ref and cleanup_inconsistent_bundle
|
|
82
|
+
not_dependency_ref = (
|
|
83
|
+
self.cache_refs.get(value) is None
|
|
84
|
+
or item_id not in self.cache_refs[value]
|
|
85
|
+
)
|
|
86
|
+
# Prevent any self reference
|
|
87
|
+
if (
|
|
88
|
+
value is not None
|
|
89
|
+
and not must_be_cleaned
|
|
90
|
+
and value not in parent_acc
|
|
91
|
+
and is_id_supported(value)
|
|
92
|
+
and value != item_id
|
|
93
|
+
and not_dependency_ref
|
|
94
|
+
):
|
|
95
|
+
self.cache_refs[item_id].append(value)
|
|
96
|
+
nb_deps += self.enlist_element(
|
|
97
|
+
value,
|
|
98
|
+
raw_data,
|
|
99
|
+
cleanup_inconsistent_bundle,
|
|
100
|
+
parent_acc + [value],
|
|
101
|
+
)
|
|
102
|
+
else:
|
|
103
|
+
item[key] = None
|
|
104
|
+
# Case for embedded elements (deduplicating and cleanup)
|
|
105
|
+
elif key == "external_references":
|
|
106
|
+
# specific case of splitting external references
|
|
107
|
+
# reference_ids = []
|
|
108
|
+
deduplicated_references = []
|
|
109
|
+
deduplicated_references_cache = {}
|
|
110
|
+
references = item[key]
|
|
111
|
+
for reference in references:
|
|
112
|
+
reference_id = external_reference_generate_id(
|
|
113
|
+
url=reference.get("url"),
|
|
114
|
+
source_name=reference.get("source_name"),
|
|
115
|
+
external_id=reference.get("external_id"),
|
|
116
|
+
)
|
|
117
|
+
if (
|
|
118
|
+
reference_id is not None
|
|
119
|
+
and deduplicated_references_cache.get(reference_id) is None
|
|
120
|
+
):
|
|
121
|
+
deduplicated_references_cache[reference_id] = reference_id
|
|
122
|
+
deduplicated_references.append(reference)
|
|
123
|
+
# - Needed for a future move of splitting the elements
|
|
124
|
+
# reference["id"] = reference_id
|
|
125
|
+
# reference["type"] = "External-Reference"
|
|
126
|
+
# raw_data[reference_id] = reference
|
|
127
|
+
# if reference_id not in reference_ids:
|
|
128
|
+
# reference_ids.append(reference_id)
|
|
129
|
+
# nb_deps += self.enlist_element(reference_id, raw_data)
|
|
130
|
+
item[key] = deduplicated_references
|
|
131
|
+
elif key == "kill_chain_phases":
|
|
132
|
+
# specific case of splitting kill_chain phases
|
|
133
|
+
# kill_chain_ids = []
|
|
134
|
+
deduplicated_kill_chain = []
|
|
135
|
+
deduplicated_kill_chain_cache = {}
|
|
136
|
+
kill_chains = item[key]
|
|
137
|
+
for kill_chain in kill_chains:
|
|
138
|
+
kill_chain_id = kill_chain_phase_generate_id(
|
|
139
|
+
kill_chain_name=kill_chain.get("kill_chain_name"),
|
|
140
|
+
phase_name=kill_chain.get("phase_name"),
|
|
141
|
+
)
|
|
142
|
+
if (
|
|
143
|
+
kill_chain_id is not None
|
|
144
|
+
and deduplicated_kill_chain_cache.get(kill_chain_id) is None
|
|
145
|
+
):
|
|
146
|
+
deduplicated_kill_chain_cache[kill_chain_id] = kill_chain_id
|
|
147
|
+
deduplicated_kill_chain.append(kill_chain)
|
|
148
|
+
# - Needed for a future move of splitting the elements
|
|
149
|
+
# kill_chain["id"] = kill_chain_id
|
|
150
|
+
# kill_chain["type"] = "Kill-Chain-Phase"
|
|
151
|
+
# raw_data[kill_chain_id] = kill_chain
|
|
152
|
+
# if kill_chain_id not in kill_chain_ids:
|
|
153
|
+
# kill_chain_ids.append(kill_chain_id)
|
|
154
|
+
# nb_deps += self.enlist_element(kill_chain_id, raw_data)
|
|
155
|
+
item[key] = deduplicated_kill_chain
|
|
156
|
+
|
|
157
|
+
# Get the final dep counting and add in cache
|
|
158
|
+
item["nb_deps"] = nb_deps
|
|
159
|
+
# Put in cache
|
|
160
|
+
if self.cache_index.get(item_id) is None:
|
|
161
|
+
# enlist only if compatible
|
|
162
|
+
if item["type"] == "relationship":
|
|
163
|
+
is_compatible = (
|
|
164
|
+
item["source_ref"] is not None and item["target_ref"] is not None
|
|
165
|
+
)
|
|
166
|
+
elif item["type"] == "sighting":
|
|
167
|
+
is_compatible = (
|
|
168
|
+
item["sighting_of_ref"] is not None
|
|
169
|
+
and len(item["where_sighted_refs"]) > 0
|
|
170
|
+
)
|
|
171
|
+
else:
|
|
172
|
+
is_compatible = is_id_supported(item_id)
|
|
173
|
+
if is_compatible:
|
|
174
|
+
self.elements.append(item)
|
|
175
|
+
self.cache_index[item_id] = item
|
|
176
|
+
|
|
177
|
+
return nb_deps
|
|
178
|
+
|
|
179
|
+
def split_bundle_with_expectations(
|
|
180
|
+
self,
|
|
181
|
+
bundle,
|
|
182
|
+
use_json=True,
|
|
183
|
+
event_version=None,
|
|
184
|
+
cleanup_inconsistent_bundle=False,
|
|
185
|
+
) -> Tuple[int, list]:
|
|
186
|
+
"""splits a valid stix2 bundle into a list of bundles"""
|
|
187
|
+
if use_json:
|
|
188
|
+
try:
|
|
189
|
+
bundle_data = json.loads(bundle)
|
|
190
|
+
except:
|
|
191
|
+
raise Exception("File data is not a valid JSON")
|
|
192
|
+
else:
|
|
193
|
+
bundle_data = bundle
|
|
194
|
+
|
|
195
|
+
if "objects" not in bundle_data:
|
|
196
|
+
raise Exception("File data is not a valid bundle")
|
|
197
|
+
if "id" not in bundle_data:
|
|
198
|
+
bundle_data["id"] = "bundle--" + str(uuid.uuid4())
|
|
199
|
+
|
|
200
|
+
raw_data = {}
|
|
201
|
+
|
|
202
|
+
# Build flat list of elements
|
|
203
|
+
for item in bundle_data["objects"]:
|
|
204
|
+
raw_data[item["id"]] = item
|
|
205
|
+
for item in bundle_data["objects"]:
|
|
206
|
+
self.enlist_element(item["id"], raw_data, cleanup_inconsistent_bundle, [])
|
|
207
|
+
|
|
208
|
+
# Build the bundles
|
|
209
|
+
bundles = []
|
|
210
|
+
|
|
211
|
+
def by_dep_size(elem):
|
|
212
|
+
return elem["nb_deps"]
|
|
213
|
+
|
|
214
|
+
self.elements.sort(key=by_dep_size)
|
|
215
|
+
|
|
216
|
+
elements_with_deps = list(
|
|
217
|
+
map(lambda e: {"nb_deps": e["nb_deps"], "elements": [e]}, self.elements)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
number_expectations = 0
|
|
221
|
+
for entity in elements_with_deps:
|
|
222
|
+
number_expectations += len(entity["elements"])
|
|
223
|
+
bundles.append(
|
|
224
|
+
self.stix2_create_bundle(
|
|
225
|
+
bundle_data["id"],
|
|
226
|
+
entity["nb_deps"],
|
|
227
|
+
entity["elements"],
|
|
228
|
+
use_json,
|
|
229
|
+
event_version,
|
|
230
|
+
)
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
return number_expectations, bundles
|
|
234
|
+
|
|
235
|
+
@deprecated("Use split_bundle_with_expectations instead")
|
|
236
|
+
def split_bundle(self, bundle, use_json=True, event_version=None) -> list:
|
|
237
|
+
expectations, bundles = self.split_bundle_with_expectations(
|
|
238
|
+
bundle, use_json, event_version
|
|
239
|
+
)
|
|
240
|
+
return bundles
|
|
241
|
+
|
|
242
|
+
@staticmethod
|
|
243
|
+
def stix2_create_bundle(bundle_id, bundle_seq, items, use_json, event_version=None):
|
|
244
|
+
"""create a stix2 bundle with items
|
|
245
|
+
|
|
246
|
+
:param items: valid stix2 items
|
|
247
|
+
:type items:
|
|
248
|
+
:param use_json: use JSON?
|
|
249
|
+
:type use_json:
|
|
250
|
+
:return: JSON of the stix2 bundle
|
|
251
|
+
:rtype:
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
bundle = {
|
|
255
|
+
"type": "bundle",
|
|
256
|
+
"id": bundle_id,
|
|
257
|
+
"spec_version": "2.1",
|
|
258
|
+
"x_opencti_seq": bundle_seq,
|
|
259
|
+
"objects": items,
|
|
260
|
+
}
|
|
261
|
+
if event_version is not None:
|
|
262
|
+
bundle["x_opencti_event_version"] = event_version
|
|
263
|
+
return json.dumps(bundle) if use_json else bundle
|
|
@@ -2,6 +2,51 @@ from typing import Any, Dict
|
|
|
2
2
|
|
|
3
3
|
from stix2 import EqualityComparisonExpression, ObjectPath, ObservationExpression
|
|
4
4
|
|
|
5
|
+
SUPPORTED_STIX_ENTITY_OBJECTS = [
|
|
6
|
+
"attack-pattern",
|
|
7
|
+
"campaign",
|
|
8
|
+
"case-incident",
|
|
9
|
+
"x-opencti-case-incident",
|
|
10
|
+
"case-rfi",
|
|
11
|
+
"x-opencti-case-rfi",
|
|
12
|
+
"case-rft",
|
|
13
|
+
"x-opencti-case-rft",
|
|
14
|
+
"channel",
|
|
15
|
+
"course-of-action",
|
|
16
|
+
"data-component",
|
|
17
|
+
"x-mitre-data-component",
|
|
18
|
+
"data-source",
|
|
19
|
+
"x-mitre-data-source",
|
|
20
|
+
"event",
|
|
21
|
+
"external-reference",
|
|
22
|
+
"feedback",
|
|
23
|
+
"x-opencti-feedback",
|
|
24
|
+
"grouping",
|
|
25
|
+
"identity",
|
|
26
|
+
"incident",
|
|
27
|
+
"indicator",
|
|
28
|
+
"infrastructure",
|
|
29
|
+
"intrusion-set",
|
|
30
|
+
"kill-chain-phase",
|
|
31
|
+
"label",
|
|
32
|
+
"language",
|
|
33
|
+
"location",
|
|
34
|
+
"malware",
|
|
35
|
+
"malware-analysis",
|
|
36
|
+
"marking-definition",
|
|
37
|
+
"narrative",
|
|
38
|
+
"note",
|
|
39
|
+
"observed-data",
|
|
40
|
+
"opinion",
|
|
41
|
+
"report",
|
|
42
|
+
"task",
|
|
43
|
+
"x-opencti-task",
|
|
44
|
+
"threat-actor",
|
|
45
|
+
"tool",
|
|
46
|
+
"vocabulary",
|
|
47
|
+
"vulnerability",
|
|
48
|
+
]
|
|
49
|
+
|
|
5
50
|
STIX_CYBER_OBSERVABLE_MAPPING = {
|
|
6
51
|
"autonomous-system": "Autonomous-System",
|
|
7
52
|
"directory": "Directory",
|
|
@@ -34,6 +79,8 @@ STIX_CYBER_OBSERVABLE_MAPPING = {
|
|
|
34
79
|
"tracking-number": "Tracking-Number",
|
|
35
80
|
"payment-card": "Payment-Card",
|
|
36
81
|
"media-content": "Media-Content",
|
|
82
|
+
"simple-observable": "Simple-Observable",
|
|
83
|
+
"persona": "Persona",
|
|
37
84
|
}
|
|
38
85
|
|
|
39
86
|
PATTERN_MAPPING = {
|
|
@@ -72,6 +72,7 @@ pycti/utils/__init__.py
|
|
|
72
72
|
pycti/utils/constants.py
|
|
73
73
|
pycti/utils/opencti_logger.py
|
|
74
74
|
pycti/utils/opencti_stix2.py
|
|
75
|
+
pycti/utils/opencti_stix2_identifier.py
|
|
75
76
|
pycti/utils/opencti_stix2_splitter.py
|
|
76
77
|
pycti/utils/opencti_stix2_update.py
|
|
77
78
|
pycti/utils/opencti_stix2_utils.py
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import re
|
|
3
|
-
import uuid
|
|
4
|
-
from typing import Tuple
|
|
5
|
-
|
|
6
|
-
from typing_extensions import deprecated
|
|
7
|
-
|
|
8
|
-
MITRE_X_CAPEC = (
|
|
9
|
-
"x_capec_*" # https://github.com/mitre-attack/attack-stix-data/issues/34
|
|
10
|
-
)
|
|
11
|
-
unsupported_ref_patterns = [MITRE_X_CAPEC]
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class OpenCTIStix2Splitter:
|
|
15
|
-
def __init__(self):
|
|
16
|
-
self.cache_index = {}
|
|
17
|
-
self.elements = []
|
|
18
|
-
self.unsupported_patterns = list(
|
|
19
|
-
map(lambda pattern: re.compile(pattern), unsupported_ref_patterns)
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
def is_ref_key_supported(self, key):
|
|
23
|
-
for pattern in self.unsupported_patterns:
|
|
24
|
-
if pattern.match(key):
|
|
25
|
-
return False
|
|
26
|
-
return True
|
|
27
|
-
|
|
28
|
-
def enlist_element(self, item_id, raw_data):
|
|
29
|
-
nb_deps = 1
|
|
30
|
-
if item_id not in raw_data:
|
|
31
|
-
return 0
|
|
32
|
-
existing_item = self.cache_index.get(item_id)
|
|
33
|
-
if existing_item is not None:
|
|
34
|
-
return existing_item["nb_deps"]
|
|
35
|
-
# Recursive enlist for every refs
|
|
36
|
-
item = raw_data[item_id]
|
|
37
|
-
for key in list(item.keys()):
|
|
38
|
-
value = item[key]
|
|
39
|
-
if key.endswith("_refs") and self.is_ref_key_supported(key):
|
|
40
|
-
to_keep = []
|
|
41
|
-
for element_ref in item[key]:
|
|
42
|
-
if element_ref != item_id:
|
|
43
|
-
nb_deps += self.enlist_element(element_ref, raw_data)
|
|
44
|
-
to_keep.append(element_ref)
|
|
45
|
-
item[key] = to_keep
|
|
46
|
-
elif key.endswith("_ref") and self.is_ref_key_supported(key):
|
|
47
|
-
if item[key] == item_id:
|
|
48
|
-
item[key] = None
|
|
49
|
-
else:
|
|
50
|
-
# Need to handle the special case of recursive ref for created by ref
|
|
51
|
-
is_created_by_ref = key == "created_by_ref"
|
|
52
|
-
if is_created_by_ref:
|
|
53
|
-
is_marking = item["id"].startswith("marking-definition--")
|
|
54
|
-
if is_marking is False:
|
|
55
|
-
nb_deps += self.enlist_element(value, raw_data)
|
|
56
|
-
else:
|
|
57
|
-
nb_deps += self.enlist_element(value, raw_data)
|
|
58
|
-
# Get the final dep counting and add in cache
|
|
59
|
-
item["nb_deps"] = nb_deps
|
|
60
|
-
self.elements.append(item)
|
|
61
|
-
self.cache_index[item_id] = item # Put in cache
|
|
62
|
-
return nb_deps
|
|
63
|
-
|
|
64
|
-
def split_bundle_with_expectations(
|
|
65
|
-
self, bundle, use_json=True, event_version=None
|
|
66
|
-
) -> Tuple[int, list]:
|
|
67
|
-
"""splits a valid stix2 bundle into a list of bundles"""
|
|
68
|
-
if use_json:
|
|
69
|
-
try:
|
|
70
|
-
bundle_data = json.loads(bundle)
|
|
71
|
-
except:
|
|
72
|
-
raise Exception("File data is not a valid JSON")
|
|
73
|
-
else:
|
|
74
|
-
bundle_data = bundle
|
|
75
|
-
|
|
76
|
-
if "objects" not in bundle_data:
|
|
77
|
-
raise Exception("File data is not a valid bundle")
|
|
78
|
-
if "id" not in bundle_data:
|
|
79
|
-
bundle_data["id"] = "bundle--" + str(uuid.uuid4())
|
|
80
|
-
|
|
81
|
-
raw_data = {}
|
|
82
|
-
|
|
83
|
-
# Build flat list of elements
|
|
84
|
-
for item in bundle_data["objects"]:
|
|
85
|
-
raw_data[item["id"]] = item
|
|
86
|
-
for item in bundle_data["objects"]:
|
|
87
|
-
self.enlist_element(item["id"], raw_data)
|
|
88
|
-
|
|
89
|
-
# Build the bundles
|
|
90
|
-
bundles = []
|
|
91
|
-
|
|
92
|
-
def by_dep_size(elem):
|
|
93
|
-
return elem["nb_deps"]
|
|
94
|
-
|
|
95
|
-
self.elements.sort(key=by_dep_size)
|
|
96
|
-
|
|
97
|
-
elements_with_deps = list(
|
|
98
|
-
map(lambda e: {"nb_deps": e["nb_deps"], "elements": [e]}, self.elements)
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
number_expectations = 0
|
|
102
|
-
for entity in elements_with_deps:
|
|
103
|
-
number_expectations += len(entity["elements"])
|
|
104
|
-
bundles.append(
|
|
105
|
-
self.stix2_create_bundle(
|
|
106
|
-
bundle_data["id"],
|
|
107
|
-
entity["nb_deps"],
|
|
108
|
-
entity["elements"],
|
|
109
|
-
use_json,
|
|
110
|
-
event_version,
|
|
111
|
-
)
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
return number_expectations, bundles
|
|
115
|
-
|
|
116
|
-
@deprecated("Use split_bundle_with_expectations instead")
|
|
117
|
-
def split_bundle(self, bundle, use_json=True, event_version=None) -> list:
|
|
118
|
-
expectations, bundles = self.split_bundle_with_expectations(
|
|
119
|
-
bundle, use_json, event_version
|
|
120
|
-
)
|
|
121
|
-
return bundles
|
|
122
|
-
|
|
123
|
-
@staticmethod
|
|
124
|
-
def stix2_create_bundle(bundle_id, bundle_seq, items, use_json, event_version=None):
|
|
125
|
-
"""create a stix2 bundle with items
|
|
126
|
-
|
|
127
|
-
:param items: valid stix2 items
|
|
128
|
-
:type items:
|
|
129
|
-
:param use_json: use JSON?
|
|
130
|
-
:type use_json:
|
|
131
|
-
:return: JSON of the stix2 bundle
|
|
132
|
-
:rtype:
|
|
133
|
-
"""
|
|
134
|
-
|
|
135
|
-
bundle = {
|
|
136
|
-
"type": "bundle",
|
|
137
|
-
"id": bundle_id,
|
|
138
|
-
"spec_version": "2.1",
|
|
139
|
-
"x_opencti_seq": bundle_seq,
|
|
140
|
-
"objects": items,
|
|
141
|
-
}
|
|
142
|
-
if event_version is not None:
|
|
143
|
-
bundle["x_opencti_event_version"] = event_version
|
|
144
|
-
return json.dumps(bundle) if use_json else bundle
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|