pycti 6.0.9__tar.gz → 6.0.10__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.0.9 → pycti-6.0.10}/PKG-INFO +4 -4
- {pycti-6.0.9 → pycti-6.0.10}/pycti/__init__.py +11 -1
- {pycti-6.0.9 → pycti-6.0.10}/pycti/api/opencti_api_client.py +2 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/api/opencti_api_work.py +63 -54
- {pycti-6.0.9 → pycti-6.0.10}/pycti/connector/opencti_connector_helper.py +139 -40
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_incident.py +4 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_indicator.py +11 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_intrusion_set.py +12 -1
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_malware_analysis.py +11 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_stix_core_object.py +6 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_stix_cyber_observable.py +68 -10
- {pycti-6.0.9 → pycti-6.0.10}/pycti/utils/constants.py +110 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/utils/opencti_stix2.py +15 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/utils/opencti_stix2_utils.py +12 -1
- {pycti-6.0.9 → pycti-6.0.10}/pycti.egg-info/PKG-INFO +4 -4
- {pycti-6.0.9 → pycti-6.0.10}/pycti.egg-info/requires.txt +3 -3
- {pycti-6.0.9 → pycti-6.0.10}/setup.cfg +3 -3
- {pycti-6.0.9 → pycti-6.0.10}/LICENSE +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/README.md +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/api/__init__.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/api/opencti_api_connector.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/api/opencti_api_playbook.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/connector/__init__.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/connector/opencti_connector.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/connector/opencti_metric_handler.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/__init__.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_attack_pattern.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_campaign.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_case_incident.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_case_rfi.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_case_rft.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_channel.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_course_of_action.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_data_component.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_data_source.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_event.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_external_reference.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_feedback.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_grouping.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_identity.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_infrastructure.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_kill_chain_phase.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_label.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_language.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_location.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_malware.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_marking_definition.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_narrative.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_note.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_observed_data.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_opinion.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_report.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_stix.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_stix_core_relationship.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_stix_domain_object.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_stix_nested_ref_relationship.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_stix_object_or_stix_relationship.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_stix_sighting_relationship.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_task.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_threat_actor.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_threat_actor_group.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_threat_actor_individual.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_tool.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_vocabulary.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/entities/opencti_vulnerability.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/utils/__init__.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/utils/opencti_logger.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/utils/opencti_stix2_splitter.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti/utils/opencti_stix2_update.py +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti.egg-info/SOURCES.txt +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti.egg-info/dependency_links.txt +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pycti.egg-info/top_level.txt +0 -0
- {pycti-6.0.9 → pycti-6.0.10}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pycti
|
|
3
|
-
Version: 6.0.
|
|
3
|
+
Version: 6.0.10
|
|
4
4
|
Summary: Python API client for OpenCTI.
|
|
5
5
|
Home-page: https://github.com/OpenCTI-Platform/client-python
|
|
6
6
|
Author: Filigran
|
|
@@ -29,12 +29,12 @@ Requires-Dist: python-magic-bin~=0.4.14; sys_platform == "win32"
|
|
|
29
29
|
Requires-Dist: python_json_logger~=2.0.4
|
|
30
30
|
Requires-Dist: pyyaml~=6.0
|
|
31
31
|
Requires-Dist: requests~=2.31.0
|
|
32
|
-
Requires-Dist: setuptools~=69.
|
|
32
|
+
Requires-Dist: setuptools~=69.5.1
|
|
33
33
|
Requires-Dist: filigran-sseclient~=1.0.0
|
|
34
34
|
Requires-Dist: stix2~=3.0.1
|
|
35
35
|
Requires-Dist: cachetools~=5.3.0
|
|
36
36
|
Provides-Extra: dev
|
|
37
|
-
Requires-Dist: black~=24.
|
|
37
|
+
Requires-Dist: black~=24.4.0; extra == "dev"
|
|
38
38
|
Requires-Dist: build~=1.2.1; extra == "dev"
|
|
39
39
|
Requires-Dist: isort~=5.13.0; extra == "dev"
|
|
40
40
|
Requires-Dist: types-pytz~=2024.1.0.20240203; extra == "dev"
|
|
@@ -47,7 +47,7 @@ Requires-Dist: types-python-dateutil~=2.9.0; extra == "dev"
|
|
|
47
47
|
Requires-Dist: wheel~=0.43.0; extra == "dev"
|
|
48
48
|
Provides-Extra: doc
|
|
49
49
|
Requires-Dist: autoapi~=2.0.1; extra == "doc"
|
|
50
|
-
Requires-Dist: sphinx-autodoc-typehints~=2.
|
|
50
|
+
Requires-Dist: sphinx-autodoc-typehints~=2.1.0; extra == "doc"
|
|
51
51
|
Requires-Dist: sphinx-rtd-theme~=2.0.0; extra == "doc"
|
|
52
52
|
|
|
53
53
|
# OpenCTI client for Python
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
__version__ = "6.0.
|
|
2
|
+
__version__ = "6.0.10"
|
|
3
3
|
|
|
4
4
|
from .api.opencti_api_client import OpenCTIApiClient
|
|
5
5
|
from .api.opencti_api_connector import OpenCTIApiConnector
|
|
@@ -53,9 +53,14 @@ from .entities.opencti_vulnerability import Vulnerability
|
|
|
53
53
|
from .utils.constants import (
|
|
54
54
|
CustomObjectCaseIncident,
|
|
55
55
|
CustomObjectTask,
|
|
56
|
+
CustomObservableBankAccount,
|
|
57
|
+
CustomObservableCredential,
|
|
56
58
|
CustomObservableCryptocurrencyWallet,
|
|
57
59
|
CustomObservableHostname,
|
|
60
|
+
CustomObservablePaymentCard,
|
|
61
|
+
CustomObservablePhoneNumber,
|
|
58
62
|
CustomObservableText,
|
|
63
|
+
CustomObservableTrackingNumber,
|
|
59
64
|
CustomObservableUserAgent,
|
|
60
65
|
MultipleRefRelationship,
|
|
61
66
|
StixCyberObservableTypes,
|
|
@@ -128,9 +133,14 @@ __all__ = [
|
|
|
128
133
|
"CustomObjectCaseIncident",
|
|
129
134
|
"CustomObjectTask",
|
|
130
135
|
"StixCyberObservableTypes",
|
|
136
|
+
"CustomObservableCredential",
|
|
131
137
|
"CustomObservableHostname",
|
|
132
138
|
"CustomObservableUserAgent",
|
|
139
|
+
"CustomObservableBankAccount",
|
|
133
140
|
"CustomObservableCryptocurrencyWallet",
|
|
141
|
+
"CustomObservablePaymentCard",
|
|
142
|
+
"CustomObservablePhoneNumber",
|
|
143
|
+
"CustomObservableTrackingNumber",
|
|
134
144
|
"CustomObservableText",
|
|
135
145
|
"STIX_EXT_MITRE",
|
|
136
146
|
"STIX_EXT_OCTI_SCO",
|
|
@@ -108,6 +108,7 @@ class OpenCTIApiClient:
|
|
|
108
108
|
ssl_verify=False,
|
|
109
109
|
proxies=None,
|
|
110
110
|
json_logging=False,
|
|
111
|
+
bundle_send_to_queue=True,
|
|
111
112
|
cert=None,
|
|
112
113
|
auth=None,
|
|
113
114
|
perform_health_check=True,
|
|
@@ -115,6 +116,7 @@ class OpenCTIApiClient:
|
|
|
115
116
|
"""Constructor method"""
|
|
116
117
|
|
|
117
118
|
# Check configuration
|
|
119
|
+
self.bundle_send_to_queue = bundle_send_to_queue
|
|
118
120
|
self.ssl_verify = ssl_verify
|
|
119
121
|
self.cert = cert
|
|
120
122
|
self.proxies = proxies
|
|
@@ -9,28 +9,34 @@ class OpenCTIApiWork:
|
|
|
9
9
|
self.api = api
|
|
10
10
|
|
|
11
11
|
def to_received(self, work_id: str, message: str):
|
|
12
|
-
self.api.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
if self.api.bundle_send_to_queue:
|
|
13
|
+
self.api.app_logger.info(
|
|
14
|
+
"Reporting work update_received", {"work_id": work_id}
|
|
15
|
+
)
|
|
16
|
+
query = """
|
|
17
|
+
mutation workToReceived($id: ID!, $message: String) {
|
|
18
|
+
workEdit(id: $id) {
|
|
19
|
+
toReceived (message: $message)
|
|
20
|
+
}
|
|
17
21
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
self.api.query(query, {"id": work_id, "message": message})
|
|
22
|
+
"""
|
|
23
|
+
self.api.query(query, {"id": work_id, "message": message})
|
|
21
24
|
|
|
22
25
|
def to_processed(self, work_id: str, message: str, in_error: bool = False):
|
|
23
|
-
self.api.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
if self.api.bundle_send_to_queue:
|
|
27
|
+
self.api.app_logger.info(
|
|
28
|
+
"Reporting work update_processed", {"work_id": work_id}
|
|
29
|
+
)
|
|
30
|
+
query = """
|
|
31
|
+
mutation workToProcessed($id: ID!, $message: String, $inError: Boolean) {
|
|
32
|
+
workEdit(id: $id) {
|
|
33
|
+
toProcessed (message: $message, inError: $inError)
|
|
34
|
+
}
|
|
30
35
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
"""
|
|
37
|
+
self.api.query(
|
|
38
|
+
query, {"id": work_id, "message": message, "inError": in_error}
|
|
39
|
+
)
|
|
34
40
|
|
|
35
41
|
def ping(self, work_id: str):
|
|
36
42
|
self.api.app_logger.info("Ping work", {"work_id": work_id})
|
|
@@ -44,49 +50,52 @@ class OpenCTIApiWork:
|
|
|
44
50
|
self.api.query(query, {"id": work_id})
|
|
45
51
|
|
|
46
52
|
def report_expectation(self, work_id: str, error):
|
|
47
|
-
self.api.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
if self.api.bundle_send_to_queue:
|
|
54
|
+
self.api.app_logger.info("Report expectation", {"work_id": work_id})
|
|
55
|
+
query = """
|
|
56
|
+
mutation reportExpectation($id: ID!, $error: WorkErrorInput) {
|
|
57
|
+
workEdit(id: $id) {
|
|
58
|
+
reportExpectation(error: $error)
|
|
59
|
+
}
|
|
52
60
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
self.api.app_logger.error("Cannot report expectation")
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
self.api.query(query, {"id": work_id, "error": error})
|
|
64
|
+
except:
|
|
65
|
+
self.api.app_logger.error("Cannot report expectation")
|
|
59
66
|
|
|
60
67
|
def add_expectations(self, work_id: str, expectations: int):
|
|
61
|
-
self.api.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
if self.api.bundle_send_to_queue:
|
|
69
|
+
self.api.app_logger.info(
|
|
70
|
+
"Update action expectations",
|
|
71
|
+
{"work_id": work_id, "expectations": expectations},
|
|
72
|
+
)
|
|
73
|
+
query = """
|
|
74
|
+
mutation addExpectations($id: ID!, $expectations: Int) {
|
|
75
|
+
workEdit(id: $id) {
|
|
76
|
+
addExpectations(expectations: $expectations)
|
|
77
|
+
}
|
|
69
78
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
self.api.app_logger.error("Cannot report expectation")
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
self.api.query(query, {"id": work_id, "expectations": expectations})
|
|
82
|
+
except:
|
|
83
|
+
self.api.app_logger.error("Cannot report expectation")
|
|
76
84
|
|
|
77
85
|
def initiate_work(self, connector_id: str, friendly_name: str) -> str:
|
|
78
|
-
self.api.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
workAdd(connectorId: $
|
|
82
|
-
|
|
86
|
+
if self.api.bundle_send_to_queue:
|
|
87
|
+
self.api.app_logger.info("Initiate work", {"connector_id": connector_id})
|
|
88
|
+
query = """
|
|
89
|
+
mutation workAdd($connectorId: String!, $friendlyName: String) {
|
|
90
|
+
workAdd(connectorId: $connectorId, friendlyName: $friendlyName) {
|
|
91
|
+
id
|
|
92
|
+
}
|
|
83
93
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return work["data"]["workAdd"]["id"]
|
|
94
|
+
"""
|
|
95
|
+
work = self.api.query(
|
|
96
|
+
query, {"connectorId": connector_id, "friendlyName": friendly_name}
|
|
97
|
+
)
|
|
98
|
+
return work["data"]["workAdd"]["id"]
|
|
90
99
|
|
|
91
100
|
def delete_work(self, work_id: str):
|
|
92
101
|
query = """
|
|
@@ -728,6 +728,32 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
728
728
|
self.connect_auto = get_config_variable(
|
|
729
729
|
"CONNECTOR_AUTO", ["connector", "auto"], config, False, False
|
|
730
730
|
)
|
|
731
|
+
self.bundle_send_to_queue = get_config_variable(
|
|
732
|
+
"CONNECTOR_SEND_TO_QUEUE",
|
|
733
|
+
["connector", "send_to_queue"],
|
|
734
|
+
config,
|
|
735
|
+
False,
|
|
736
|
+
True,
|
|
737
|
+
)
|
|
738
|
+
self.bundle_send_to_directory = get_config_variable(
|
|
739
|
+
"CONNECTOR_SEND_TO_DIRECTORY",
|
|
740
|
+
["connector", "send_to_directory"],
|
|
741
|
+
config,
|
|
742
|
+
False,
|
|
743
|
+
False,
|
|
744
|
+
)
|
|
745
|
+
self.bundle_send_to_directory_path = get_config_variable(
|
|
746
|
+
"CONNECTOR_SEND_TO_DIRECTORY_PATH",
|
|
747
|
+
["connector", "send_to_directory_path"],
|
|
748
|
+
config,
|
|
749
|
+
)
|
|
750
|
+
self.bundle_send_to_directory_retention = get_config_variable(
|
|
751
|
+
"CONNECTOR_SEND_TO_DIRECTORY_RETENTION",
|
|
752
|
+
["connector", "send_to_directory_retention"],
|
|
753
|
+
config,
|
|
754
|
+
True,
|
|
755
|
+
7,
|
|
756
|
+
)
|
|
731
757
|
self.connect_only_contextual = get_config_variable(
|
|
732
758
|
"CONNECTOR_ONLY_CONTEXTUAL",
|
|
733
759
|
["connector", "only_contextual"],
|
|
@@ -749,6 +775,8 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
749
775
|
"CONNECTOR_VALIDATE_BEFORE_IMPORT",
|
|
750
776
|
["connector", "validate_before_import"],
|
|
751
777
|
config,
|
|
778
|
+
False,
|
|
779
|
+
False,
|
|
752
780
|
)
|
|
753
781
|
# Start up the server to expose the metrics.
|
|
754
782
|
expose_metrics = get_config_variable(
|
|
@@ -769,6 +797,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
769
797
|
self.opencti_token,
|
|
770
798
|
self.log_level,
|
|
771
799
|
json_logging=self.opencti_json_logging,
|
|
800
|
+
bundle_send_to_queue=self.bundle_send_to_queue,
|
|
772
801
|
)
|
|
773
802
|
# - Impersonate API that will use applicant id
|
|
774
803
|
# Behave like standard api if applicant not found
|
|
@@ -777,6 +806,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
777
806
|
self.opencti_token,
|
|
778
807
|
self.log_level,
|
|
779
808
|
json_logging=self.opencti_json_logging,
|
|
809
|
+
bundle_send_to_queue=self.bundle_send_to_queue,
|
|
780
810
|
)
|
|
781
811
|
self.connector_logger = self.api.logger_class(self.connect_name)
|
|
782
812
|
# For retro compatibility
|
|
@@ -1075,6 +1105,16 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1075
1105
|
bypass_validation = kwargs.get("bypass_validation", False)
|
|
1076
1106
|
entity_id = kwargs.get("entity_id", None)
|
|
1077
1107
|
file_name = kwargs.get("file_name", None)
|
|
1108
|
+
bundle_send_to_queue = kwargs.get("send_to_queue", self.bundle_send_to_queue)
|
|
1109
|
+
bundle_send_to_directory = kwargs.get(
|
|
1110
|
+
"send_to_directory", self.bundle_send_to_directory
|
|
1111
|
+
)
|
|
1112
|
+
bundle_send_to_directory_path = kwargs.get(
|
|
1113
|
+
"send_to_directory_path", self.bundle_send_to_directory_path
|
|
1114
|
+
)
|
|
1115
|
+
bundle_send_to_directory_retention = kwargs.get(
|
|
1116
|
+
"send_to_directory_retention", self.bundle_send_to_directory_retention
|
|
1117
|
+
)
|
|
1078
1118
|
|
|
1079
1119
|
# In case of enrichment ingestion, ensure the sharing if needed
|
|
1080
1120
|
if self.enrichment_shared_organizations is not None:
|
|
@@ -1114,13 +1154,14 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1114
1154
|
)
|
|
1115
1155
|
bundle = json.dumps(bundle_data)
|
|
1116
1156
|
|
|
1157
|
+
# If execution in playbook, callback the api
|
|
1117
1158
|
if self.playbook is not None:
|
|
1118
1159
|
self.api.playbook.playbook_step_execution(self.playbook, bundle)
|
|
1119
1160
|
return [bundle]
|
|
1120
1161
|
|
|
1162
|
+
# Upload workbench in case of pending validation
|
|
1121
1163
|
if not file_name and work_id:
|
|
1122
1164
|
file_name = f"{work_id}.json"
|
|
1123
|
-
|
|
1124
1165
|
if self.connect_validate_before_import and not bypass_validation and file_name:
|
|
1125
1166
|
self.api.upload_pending_file(
|
|
1126
1167
|
file_name=file_name,
|
|
@@ -1130,8 +1171,61 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1130
1171
|
)
|
|
1131
1172
|
return []
|
|
1132
1173
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1174
|
+
# If directory setup, write the bundle to the target directory
|
|
1175
|
+
if bundle_send_to_directory and bundle_send_to_directory_path is not None:
|
|
1176
|
+
self.connector_logger.info(
|
|
1177
|
+
"The connector sending bundle to directory",
|
|
1178
|
+
{
|
|
1179
|
+
"connector": self.connect_name,
|
|
1180
|
+
"directory": bundle_send_to_directory_path,
|
|
1181
|
+
"also_queuing": bundle_send_to_queue,
|
|
1182
|
+
},
|
|
1183
|
+
)
|
|
1184
|
+
bundle_file = (
|
|
1185
|
+
self.connect_name.lower().replace(" ", "_")
|
|
1186
|
+
+ "-"
|
|
1187
|
+
+ time.strftime("%Y%m%d-%H%M%S-")
|
|
1188
|
+
+ str(time.time())
|
|
1189
|
+
+ ".json"
|
|
1190
|
+
)
|
|
1191
|
+
write_file = os.path.join(
|
|
1192
|
+
bundle_send_to_directory_path, bundle_file + ".tmp"
|
|
1193
|
+
)
|
|
1194
|
+
message_bundle = {
|
|
1195
|
+
"bundle_type": "DIRECTORY_BUNDLE",
|
|
1196
|
+
"applicant_id": self.applicant_id,
|
|
1197
|
+
"connector": {
|
|
1198
|
+
"id": self.connect_id,
|
|
1199
|
+
"name": self.connect_name,
|
|
1200
|
+
"type": self.connect_type,
|
|
1201
|
+
"scope": self.connect_scope,
|
|
1202
|
+
"auto": self.connect_auto,
|
|
1203
|
+
"validate_before_import": self.connect_validate_before_import,
|
|
1204
|
+
},
|
|
1205
|
+
"entities_types": entities_types,
|
|
1206
|
+
"bundle": json.loads(bundle),
|
|
1207
|
+
"update": update,
|
|
1208
|
+
}
|
|
1209
|
+
# Maintains the list of files under control
|
|
1210
|
+
if bundle_send_to_directory_retention > 0: # If 0, disable the auto remove
|
|
1211
|
+
current_time = time.time()
|
|
1212
|
+
for f in os.listdir(bundle_send_to_directory_path):
|
|
1213
|
+
if f.endswith(".json"):
|
|
1214
|
+
file_location = os.path.join(bundle_send_to_directory_path, f)
|
|
1215
|
+
file_time = os.stat(file_location).st_mtime
|
|
1216
|
+
is_expired_file = (
|
|
1217
|
+
file_time
|
|
1218
|
+
< current_time - 86400 * bundle_send_to_directory_retention
|
|
1219
|
+
) # 86400 = 1 day
|
|
1220
|
+
if is_expired_file:
|
|
1221
|
+
os.remove(file_location)
|
|
1222
|
+
# Write the bundle to target directory
|
|
1223
|
+
with open(write_file, "w") as f:
|
|
1224
|
+
str_bundle = json.dumps(message_bundle)
|
|
1225
|
+
f.write(str_bundle)
|
|
1226
|
+
# Rename the file after full write
|
|
1227
|
+
final_write_file = os.path.join(bundle_send_to_directory_path, bundle_file)
|
|
1228
|
+
os.rename(write_file, final_write_file)
|
|
1135
1229
|
|
|
1136
1230
|
if bypass_split:
|
|
1137
1231
|
bundles = [bundle]
|
|
@@ -1143,44 +1237,48 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1143
1237
|
self.metric.inc("error_count")
|
|
1144
1238
|
raise ValueError("Nothing to import")
|
|
1145
1239
|
|
|
1146
|
-
if
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
channel = pika_connection.channel()
|
|
1169
|
-
try:
|
|
1170
|
-
channel.confirm_delivery()
|
|
1171
|
-
except Exception as err: # pylint: disable=broad-except
|
|
1172
|
-
self.connector_logger.warning(str(err))
|
|
1173
|
-
for sequence, bundle in enumerate(bundles, start=1):
|
|
1174
|
-
self._send_bundle(
|
|
1175
|
-
channel,
|
|
1176
|
-
bundle,
|
|
1177
|
-
work_id=work_id,
|
|
1178
|
-
entities_types=entities_types,
|
|
1179
|
-
sequence=sequence,
|
|
1180
|
-
update=update,
|
|
1240
|
+
if bundle_send_to_queue:
|
|
1241
|
+
if work_id:
|
|
1242
|
+
self.api.work.add_expectations(work_id, len(bundles))
|
|
1243
|
+
if entities_types is None:
|
|
1244
|
+
entities_types = []
|
|
1245
|
+
pika_credentials = pika.PlainCredentials(
|
|
1246
|
+
self.connector_config["connection"]["user"],
|
|
1247
|
+
self.connector_config["connection"]["pass"],
|
|
1248
|
+
)
|
|
1249
|
+
pika_parameters = pika.ConnectionParameters(
|
|
1250
|
+
host=self.connector_config["connection"]["host"],
|
|
1251
|
+
port=self.connector_config["connection"]["port"],
|
|
1252
|
+
virtual_host=self.connector_config["connection"]["vhost"],
|
|
1253
|
+
credentials=pika_credentials,
|
|
1254
|
+
ssl_options=(
|
|
1255
|
+
pika.SSLOptions(
|
|
1256
|
+
create_mq_ssl_context(self.config),
|
|
1257
|
+
self.connector_config["connection"]["host"],
|
|
1258
|
+
)
|
|
1259
|
+
if self.connector_config["connection"]["use_ssl"]
|
|
1260
|
+
else None
|
|
1261
|
+
),
|
|
1181
1262
|
)
|
|
1182
|
-
|
|
1183
|
-
|
|
1263
|
+
pika_connection = pika.BlockingConnection(pika_parameters)
|
|
1264
|
+
channel = pika_connection.channel()
|
|
1265
|
+
try:
|
|
1266
|
+
channel.confirm_delivery()
|
|
1267
|
+
except Exception as err: # pylint: disable=broad-except
|
|
1268
|
+
self.connector_logger.warning(str(err))
|
|
1269
|
+
self.connector_logger.info(self.connect_name + " sending bundle to queue")
|
|
1270
|
+
for sequence, bundle in enumerate(bundles, start=1):
|
|
1271
|
+
self._send_bundle(
|
|
1272
|
+
channel,
|
|
1273
|
+
bundle,
|
|
1274
|
+
work_id=work_id,
|
|
1275
|
+
entities_types=entities_types,
|
|
1276
|
+
sequence=sequence,
|
|
1277
|
+
update=update,
|
|
1278
|
+
)
|
|
1279
|
+
channel.close()
|
|
1280
|
+
pika_connection.close()
|
|
1281
|
+
|
|
1184
1282
|
return bundles
|
|
1185
1283
|
|
|
1186
1284
|
def _send_bundle(self, channel, bundle, **kwargs) -> None:
|
|
@@ -1212,6 +1310,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1212
1310
|
# if self.current_work_id is None:
|
|
1213
1311
|
# raise ValueError('The job id must be specified')
|
|
1214
1312
|
message = {
|
|
1313
|
+
"bundle_type": "QUEUE_BUNDLE",
|
|
1215
1314
|
"applicant_id": self.applicant_id,
|
|
1216
1315
|
"action_sequence": sequence,
|
|
1217
1316
|
"entities_types": entities_types,
|
|
@@ -458,6 +458,10 @@ class Incident:
|
|
|
458
458
|
stix_object["x_opencti_granted_refs"] = (
|
|
459
459
|
self.opencti.get_attribute_in_extension("granted_refs", stix_object)
|
|
460
460
|
)
|
|
461
|
+
if "x_opencti_workflow_id" not in stix_object:
|
|
462
|
+
stix_object["x_opencti_workflow_id"] = (
|
|
463
|
+
self.opencti.get_attribute_in_extension("workflow_id", stix_object)
|
|
464
|
+
)
|
|
461
465
|
|
|
462
466
|
return self.create(
|
|
463
467
|
stix_id=stix_object["id"],
|
|
@@ -468,6 +468,7 @@ class Indicator:
|
|
|
468
468
|
x_opencti_stix_ids = kwargs.get("x_opencti_stix_ids", None)
|
|
469
469
|
create_observables = kwargs.get("x_opencti_create_observables", False)
|
|
470
470
|
granted_refs = kwargs.get("objectOrganization", None)
|
|
471
|
+
x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None)
|
|
471
472
|
update = kwargs.get("update", False)
|
|
472
473
|
|
|
473
474
|
if (
|
|
@@ -528,6 +529,7 @@ class Indicator:
|
|
|
528
529
|
"x_opencti_stix_ids": x_opencti_stix_ids,
|
|
529
530
|
"killChainPhases": kill_chain_phases,
|
|
530
531
|
"createObservables": create_observables,
|
|
532
|
+
"x_opencti_workflow_id": x_opencti_workflow_id,
|
|
531
533
|
"update": update,
|
|
532
534
|
}
|
|
533
535
|
},
|
|
@@ -642,6 +644,10 @@ class Indicator:
|
|
|
642
644
|
stix_object["x_opencti_granted_refs"] = (
|
|
643
645
|
self.opencti.get_attribute_in_extension("granted_refs", stix_object)
|
|
644
646
|
)
|
|
647
|
+
if "x_opencti_workflow_id" not in stix_object:
|
|
648
|
+
stix_object["x_opencti_workflow_id"] = (
|
|
649
|
+
self.opencti.get_attribute_in_extension("workflow_id", stix_object)
|
|
650
|
+
)
|
|
645
651
|
|
|
646
652
|
return self.create(
|
|
647
653
|
stix_id=stix_object["id"],
|
|
@@ -740,6 +746,11 @@ class Indicator:
|
|
|
740
746
|
if "x_opencti_granted_refs" in stix_object
|
|
741
747
|
else None
|
|
742
748
|
),
|
|
749
|
+
x_opencti_workflow_id=(
|
|
750
|
+
stix_object["x_opencti_workflow_id"]
|
|
751
|
+
if "x_opencti_workflow_id" in stix_object
|
|
752
|
+
else None
|
|
753
|
+
),
|
|
743
754
|
update=update,
|
|
744
755
|
)
|
|
745
756
|
else:
|
|
@@ -363,6 +363,7 @@ class IntrusionSet:
|
|
|
363
363
|
secondary_motivations = kwargs.get("secondary_motivations", None)
|
|
364
364
|
x_opencti_stix_ids = kwargs.get("x_opencti_stix_ids", None)
|
|
365
365
|
granted_refs = kwargs.get("objectOrganization", None)
|
|
366
|
+
x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None)
|
|
366
367
|
update = kwargs.get("update", False)
|
|
367
368
|
|
|
368
369
|
if name is not None:
|
|
@@ -402,6 +403,7 @@ class IntrusionSet:
|
|
|
402
403
|
"primary_motivation": primary_motivation,
|
|
403
404
|
"secondary_motivations": secondary_motivations,
|
|
404
405
|
"x_opencti_stix_ids": x_opencti_stix_ids,
|
|
406
|
+
"x_opencti_workflow_id": x_opencti_workflow_id,
|
|
405
407
|
"update": update,
|
|
406
408
|
}
|
|
407
409
|
},
|
|
@@ -435,7 +437,11 @@ class IntrusionSet:
|
|
|
435
437
|
stix_object["x_opencti_granted_refs"] = (
|
|
436
438
|
self.opencti.get_attribute_in_extension("granted_refs", stix_object)
|
|
437
439
|
)
|
|
438
|
-
|
|
440
|
+
if "x_opencti_workflow_id" not in stix_object:
|
|
441
|
+
stix_object["x_opencti_workflow_id"] = (
|
|
442
|
+
self.opencti.get_attribute_in_extension("workflow_id", stix_object)
|
|
443
|
+
)
|
|
444
|
+
7
|
|
439
445
|
return self.create(
|
|
440
446
|
stix_id=stix_object["id"],
|
|
441
447
|
createdBy=(
|
|
@@ -500,6 +506,11 @@ class IntrusionSet:
|
|
|
500
506
|
if "x_opencti_granted_refs" in stix_object
|
|
501
507
|
else None
|
|
502
508
|
),
|
|
509
|
+
x_opencti_workflow_id=(
|
|
510
|
+
stix_object["x_opencti_workflow_id"]
|
|
511
|
+
if "x_opencti_workflow_id" in stix_object
|
|
512
|
+
else None
|
|
513
|
+
),
|
|
503
514
|
update=update,
|
|
504
515
|
)
|
|
505
516
|
else:
|
|
@@ -401,6 +401,7 @@ class MalwareAnalysis:
|
|
|
401
401
|
analysisSco = kwargs.get("analysisSco", None)
|
|
402
402
|
x_opencti_stix_ids = kwargs.get("x_opencti_stix_ids", None)
|
|
403
403
|
granted_refs = kwargs.get("objectOrganization", None)
|
|
404
|
+
x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None)
|
|
404
405
|
update = kwargs.get("update", False)
|
|
405
406
|
|
|
406
407
|
if product is not None and result_name is not None:
|
|
@@ -449,6 +450,7 @@ class MalwareAnalysis:
|
|
|
449
450
|
"analysisSample": sample,
|
|
450
451
|
"analysisSco": analysisSco,
|
|
451
452
|
"x_opencti_stix_ids": x_opencti_stix_ids,
|
|
453
|
+
"x_opencti_workflow_id": x_opencti_workflow_id,
|
|
452
454
|
"update": update,
|
|
453
455
|
}
|
|
454
456
|
},
|
|
@@ -482,6 +484,10 @@ class MalwareAnalysis:
|
|
|
482
484
|
stix_object["x_opencti_granted_refs"] = (
|
|
483
485
|
self.opencti.get_attribute_in_extension("granted_refs", stix_object)
|
|
484
486
|
)
|
|
487
|
+
if "x_opencti_workflow_id" not in stix_object:
|
|
488
|
+
stix_object["x_opencti_workflow_id"] = (
|
|
489
|
+
self.opencti.get_attribute_in_extension("workflow_id", stix_object)
|
|
490
|
+
)
|
|
485
491
|
|
|
486
492
|
return self.create(
|
|
487
493
|
stix_id=stix_object["id"],
|
|
@@ -574,6 +580,11 @@ class MalwareAnalysis:
|
|
|
574
580
|
if "x_opencti_granted_refs" in stix_object
|
|
575
581
|
else None
|
|
576
582
|
),
|
|
583
|
+
x_opencti_workflow_id=(
|
|
584
|
+
stix_object["x_opencti_workflow_id"]
|
|
585
|
+
if "x_opencti_workflow_id" in stix_object
|
|
586
|
+
else None
|
|
587
|
+
),
|
|
577
588
|
update=update,
|
|
578
589
|
)
|
|
579
590
|
else:
|
|
@@ -282,6 +282,12 @@ class StixCyberObservable:
|
|
|
282
282
|
... on PhoneNumber {
|
|
283
283
|
value
|
|
284
284
|
}
|
|
285
|
+
... on TrackingNumber {
|
|
286
|
+
value
|
|
287
|
+
}
|
|
288
|
+
... on Credential {
|
|
289
|
+
value
|
|
290
|
+
}
|
|
285
291
|
... on PaymentCard {
|
|
286
292
|
card_number
|
|
287
293
|
expiration_date
|
|
@@ -576,6 +582,12 @@ class StixCyberObservable:
|
|
|
576
582
|
... on PhoneNumber {
|
|
577
583
|
value
|
|
578
584
|
}
|
|
585
|
+
... on TrackingNumber {
|
|
586
|
+
value
|
|
587
|
+
}
|
|
588
|
+
... on Credential {
|
|
589
|
+
value
|
|
590
|
+
}
|
|
579
591
|
... on PaymentCard {
|
|
580
592
|
card_number
|
|
581
593
|
expiration_date
|
|
@@ -857,6 +869,15 @@ class StixCyberObservable:
|
|
|
857
869
|
type = "IPv6-Addr"
|
|
858
870
|
elif type.lower() == "hostname" or type.lower() == "x-opencti-hostname":
|
|
859
871
|
type = "Hostname"
|
|
872
|
+
elif type.lower() == "payment-card" or type.lower() == "x-opencti-payment-card":
|
|
873
|
+
type = "Payment-Card"
|
|
874
|
+
elif type.lower() == "credential" or type.lower() == "x-opencti-credential":
|
|
875
|
+
type = "Credential"
|
|
876
|
+
elif (
|
|
877
|
+
type.lower() == "tracking-number"
|
|
878
|
+
or type.lower() == "x-opencti-tracking-number"
|
|
879
|
+
):
|
|
880
|
+
type = "Tracking-Number"
|
|
860
881
|
elif (
|
|
861
882
|
type.lower() == "cryptocurrency-wallet"
|
|
862
883
|
or type.lower() == "x-opencti-cryptocurrency-wallet"
|
|
@@ -974,6 +995,8 @@ class StixCyberObservable:
|
|
|
974
995
|
$UserAgent: UserAgentAddInput
|
|
975
996
|
$BankAccount: BankAccountAddInput
|
|
976
997
|
$PhoneNumber: PhoneNumberAddInput
|
|
998
|
+
$Credential: CredentialAddInput
|
|
999
|
+
$TrackingNumber: TrackingNumberAddInput
|
|
977
1000
|
$PaymentCard: PaymentCardAddInput
|
|
978
1001
|
$MediaContent: MediaContentAddInput
|
|
979
1002
|
) {
|
|
@@ -1016,6 +1039,8 @@ class StixCyberObservable:
|
|
|
1016
1039
|
UserAgent: $UserAgent
|
|
1017
1040
|
BankAccount: $BankAccount
|
|
1018
1041
|
PhoneNumber: $PhoneNumber
|
|
1042
|
+
Credential: $Credential
|
|
1043
|
+
TrackingNumber: $TrackingNumber
|
|
1019
1044
|
PaymentCard: $PaymentCard
|
|
1020
1045
|
MediaContent: $MediaContent
|
|
1021
1046
|
) {
|
|
@@ -1508,15 +1533,6 @@ class StixCyberObservable:
|
|
|
1508
1533
|
observable_data["value"] if "value" in observable_data else None
|
|
1509
1534
|
),
|
|
1510
1535
|
}
|
|
1511
|
-
elif (
|
|
1512
|
-
type == "Cryptocurrency-Wallet"
|
|
1513
|
-
or type == "X-OpenCTI-Cryptocurrency-Wallet"
|
|
1514
|
-
):
|
|
1515
|
-
input_variables["CryptocurrencyWallet"] = {
|
|
1516
|
-
"value": (
|
|
1517
|
-
observable_data["value"] if "value" in observable_data else None
|
|
1518
|
-
),
|
|
1519
|
-
}
|
|
1520
1536
|
elif type == "Hostname":
|
|
1521
1537
|
input_variables["Hostname"] = {
|
|
1522
1538
|
"value": (
|
|
@@ -1588,8 +1604,50 @@ class StixCyberObservable:
|
|
|
1588
1604
|
else None
|
|
1589
1605
|
),
|
|
1590
1606
|
}
|
|
1607
|
+
elif type == "Payment-Card" or type.lower() == "x-opencti-payment-card":
|
|
1608
|
+
input_variables["PaymentCard"] = {
|
|
1609
|
+
"card_number": (
|
|
1610
|
+
observable_data["card_number"]
|
|
1611
|
+
if "card_number" in observable_data
|
|
1612
|
+
else None
|
|
1613
|
+
),
|
|
1614
|
+
"expiration_date": (
|
|
1615
|
+
observable_data["expiration_date"]
|
|
1616
|
+
if "expiration_date" in observable_data
|
|
1617
|
+
else None
|
|
1618
|
+
),
|
|
1619
|
+
"cvv": observable_data["cvv"] if "cvv" in observable_data else None,
|
|
1620
|
+
"holder_name": (
|
|
1621
|
+
observable_data["holder_name"]
|
|
1622
|
+
if "holder_name" in observable_data
|
|
1623
|
+
else None
|
|
1624
|
+
),
|
|
1625
|
+
}
|
|
1626
|
+
elif (
|
|
1627
|
+
type == "Cryptocurrency-Wallet"
|
|
1628
|
+
or type.lower() == "x-opencti-cryptocurrency-wallet"
|
|
1629
|
+
):
|
|
1630
|
+
input_variables["CryptocurrencyWallet"] = {
|
|
1631
|
+
"value": (
|
|
1632
|
+
observable_data["value"] if "value" in observable_data else None
|
|
1633
|
+
),
|
|
1634
|
+
}
|
|
1635
|
+
elif type == "Credential" or type.lower() == "x-opencti-credential":
|
|
1636
|
+
input_variables["Credential"] = {
|
|
1637
|
+
"value": (
|
|
1638
|
+
observable_data["value"] if "value" in observable_data else None
|
|
1639
|
+
),
|
|
1640
|
+
}
|
|
1641
|
+
elif (
|
|
1642
|
+
type == "Tracking-Number" or type.lower() == "x-opencti-tracking-number"
|
|
1643
|
+
):
|
|
1644
|
+
input_variables["TrackingNumber"] = {
|
|
1645
|
+
"value": (
|
|
1646
|
+
observable_data["value"] if "value" in observable_data else None
|
|
1647
|
+
),
|
|
1648
|
+
}
|
|
1591
1649
|
result = self.opencti.query(query, input_variables)
|
|
1592
|
-
if "payload_bin" in observable_data and "
|
|
1650
|
+
if "payload_bin" in observable_data and "mime_type" in observable_data:
|
|
1593
1651
|
self.add_file(
|
|
1594
1652
|
id=result["data"]["stixCyberObservableAdd"]["id"],
|
|
1595
1653
|
file_name=(
|
|
@@ -42,6 +42,8 @@ class StixCyberObservableTypes(Enum):
|
|
|
42
42
|
USER_AGENT = "User-Agent"
|
|
43
43
|
BANK_ACCOUNT = "Bank-Account"
|
|
44
44
|
PHONE_NUMBER = "Phone-Number"
|
|
45
|
+
CREDENTIAL = "Credential"
|
|
46
|
+
TRACKING_NUMBER = "Tracking-Number"
|
|
45
47
|
PAYMENT_CARD = "Payment-Card"
|
|
46
48
|
MEDIA_CONTENT = "Media-Content"
|
|
47
49
|
SIMPLE_OBSERVABLE = "Simple-Observable"
|
|
@@ -66,6 +68,7 @@ class IdentityTypes(Enum):
|
|
|
66
68
|
|
|
67
69
|
class ThreatActorTypes(Enum):
|
|
68
70
|
THREAT_ACTOR_GROUP = "Threat-Actor-Group"
|
|
71
|
+
THREAT_ACTOR_INDIVIDUAL = "Threat-Actor-Individual"
|
|
69
72
|
|
|
70
73
|
@classmethod
|
|
71
74
|
def has_value(cls, value):
|
|
@@ -263,6 +266,73 @@ class CustomObservableText:
|
|
|
263
266
|
pass
|
|
264
267
|
|
|
265
268
|
|
|
269
|
+
@CustomObservable(
|
|
270
|
+
"payment-card",
|
|
271
|
+
[
|
|
272
|
+
("value", StringProperty(required=True)),
|
|
273
|
+
("card_number", StringProperty(required=True)),
|
|
274
|
+
("expiration_date", StringProperty(required=False)),
|
|
275
|
+
("cvv", StringProperty(required=False)),
|
|
276
|
+
("holder_name", StringProperty(required=False)),
|
|
277
|
+
("spec_version", StringProperty(fixed="2.1")),
|
|
278
|
+
(
|
|
279
|
+
"object_marking_refs",
|
|
280
|
+
ListProperty(
|
|
281
|
+
ReferenceProperty(valid_types="marking-definition", spec_version="2.1")
|
|
282
|
+
),
|
|
283
|
+
),
|
|
284
|
+
],
|
|
285
|
+
["card_number"],
|
|
286
|
+
)
|
|
287
|
+
class CustomObservablePaymentCard:
|
|
288
|
+
"""Payment card observable."""
|
|
289
|
+
|
|
290
|
+
pass
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@CustomObservable(
|
|
294
|
+
"bank-account",
|
|
295
|
+
[
|
|
296
|
+
("value", StringProperty(required=True)),
|
|
297
|
+
("iban", StringProperty(required=True)),
|
|
298
|
+
("bic", StringProperty(required=False)),
|
|
299
|
+
("account_number", StringProperty(required=False)),
|
|
300
|
+
("spec_version", StringProperty(fixed="2.1")),
|
|
301
|
+
(
|
|
302
|
+
"object_marking_refs",
|
|
303
|
+
ListProperty(
|
|
304
|
+
ReferenceProperty(valid_types="marking-definition", spec_version="2.1")
|
|
305
|
+
),
|
|
306
|
+
),
|
|
307
|
+
],
|
|
308
|
+
["iban"],
|
|
309
|
+
)
|
|
310
|
+
class CustomObservableBankAccount:
|
|
311
|
+
"""Bank Account observable."""
|
|
312
|
+
|
|
313
|
+
pass
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@CustomObservable(
|
|
317
|
+
"credential",
|
|
318
|
+
[
|
|
319
|
+
("value", StringProperty(required=True)),
|
|
320
|
+
("spec_version", StringProperty(fixed="2.1")),
|
|
321
|
+
(
|
|
322
|
+
"object_marking_refs",
|
|
323
|
+
ListProperty(
|
|
324
|
+
ReferenceProperty(valid_types="marking-definition", spec_version="2.1")
|
|
325
|
+
),
|
|
326
|
+
),
|
|
327
|
+
],
|
|
328
|
+
["value"],
|
|
329
|
+
)
|
|
330
|
+
class CustomObservableCredential:
|
|
331
|
+
"""Credential observable."""
|
|
332
|
+
|
|
333
|
+
pass
|
|
334
|
+
|
|
335
|
+
|
|
266
336
|
@CustomObservable(
|
|
267
337
|
"cryptocurrency-wallet",
|
|
268
338
|
[
|
|
@@ -283,6 +353,46 @@ class CustomObservableCryptocurrencyWallet:
|
|
|
283
353
|
pass
|
|
284
354
|
|
|
285
355
|
|
|
356
|
+
@CustomObservable(
|
|
357
|
+
"phone-number",
|
|
358
|
+
[
|
|
359
|
+
("value", StringProperty(required=True)),
|
|
360
|
+
("spec_version", StringProperty(fixed="2.1")),
|
|
361
|
+
(
|
|
362
|
+
"object_marking_refs",
|
|
363
|
+
ListProperty(
|
|
364
|
+
ReferenceProperty(valid_types="marking-definition", spec_version="2.1")
|
|
365
|
+
),
|
|
366
|
+
),
|
|
367
|
+
],
|
|
368
|
+
["value"],
|
|
369
|
+
)
|
|
370
|
+
class CustomObservablePhoneNumber:
|
|
371
|
+
"""Phone number observable."""
|
|
372
|
+
|
|
373
|
+
pass
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
@CustomObservable(
|
|
377
|
+
"tracking-number",
|
|
378
|
+
[
|
|
379
|
+
("value", StringProperty(required=True)),
|
|
380
|
+
("spec_version", StringProperty(fixed="2.1")),
|
|
381
|
+
(
|
|
382
|
+
"object_marking_refs",
|
|
383
|
+
ListProperty(
|
|
384
|
+
ReferenceProperty(valid_types="marking-definition", spec_version="2.1")
|
|
385
|
+
),
|
|
386
|
+
),
|
|
387
|
+
],
|
|
388
|
+
["value"],
|
|
389
|
+
)
|
|
390
|
+
class CustomObservableTrackingNumber:
|
|
391
|
+
"""Tracking number observable."""
|
|
392
|
+
|
|
393
|
+
pass
|
|
394
|
+
|
|
395
|
+
|
|
286
396
|
@CustomObservable(
|
|
287
397
|
"user-agent",
|
|
288
398
|
[
|
|
@@ -835,6 +835,21 @@ class OpenCTIStix2:
|
|
|
835
835
|
"Vulnerability": self.opencti.vulnerability.read,
|
|
836
836
|
}
|
|
837
837
|
|
|
838
|
+
def get_reader(self, entity_type: str):
|
|
839
|
+
# Map types
|
|
840
|
+
if entity_type == "StixFile":
|
|
841
|
+
entity_type = "File"
|
|
842
|
+
if IdentityTypes.has_value(entity_type):
|
|
843
|
+
entity_type = "Identity"
|
|
844
|
+
if LocationTypes.has_value(entity_type):
|
|
845
|
+
entity_type = "Location"
|
|
846
|
+
if StixCyberObservableTypes.has_value(entity_type):
|
|
847
|
+
entity_type = "Stix-Cyber-Observable"
|
|
848
|
+
readers = self.get_readers()
|
|
849
|
+
return readers.get(
|
|
850
|
+
entity_type, lambda **kwargs: self.unknown_type({"type": entity_type})
|
|
851
|
+
)
|
|
852
|
+
|
|
838
853
|
# endregion
|
|
839
854
|
|
|
840
855
|
# region import
|
|
@@ -7,8 +7,11 @@ STIX_CYBER_OBSERVABLE_MAPPING = {
|
|
|
7
7
|
"directory": "Directory",
|
|
8
8
|
"domain-name": "Domain-Name",
|
|
9
9
|
"email-addr": "Email-Addr",
|
|
10
|
-
"file": "StixFile",
|
|
11
10
|
"email-message": "Email-Message",
|
|
11
|
+
"email-mime-part-type": "Email-Mime-Part-Type",
|
|
12
|
+
"artifact": "Artifact",
|
|
13
|
+
"file": "StixFile",
|
|
14
|
+
"x509-certificate": "X509-Certificate",
|
|
12
15
|
"ipv4-addr": "IPv4-Addr",
|
|
13
16
|
"ipv6-addr": "IPv6-Addr",
|
|
14
17
|
"mac-addr": "Mac-Addr",
|
|
@@ -21,8 +24,14 @@ STIX_CYBER_OBSERVABLE_MAPPING = {
|
|
|
21
24
|
"windows-registry-key": "Windows-Registry-Key",
|
|
22
25
|
"windows-registry-value-type": "Windows-Registry-Value-Type",
|
|
23
26
|
"hostname": "Hostname",
|
|
27
|
+
"cryptographic-key": "Cryptographic-Key",
|
|
28
|
+
"cryptocurrency-wallet": "Cryptocurrency-Wallet",
|
|
29
|
+
"text": "Text",
|
|
30
|
+
"user-agent": "User-Agent",
|
|
24
31
|
"bank-account": "Bank-Account",
|
|
25
32
|
"phone-number": "Phone-Number",
|
|
33
|
+
"credential": "Credential",
|
|
34
|
+
"tracking-number": "Tracking-Number",
|
|
26
35
|
"payment-card": "Payment-Card",
|
|
27
36
|
"media-content": "Media-Content",
|
|
28
37
|
}
|
|
@@ -54,6 +63,8 @@ PATTERN_MAPPING = {
|
|
|
54
63
|
"Bank-Account": ["iban"],
|
|
55
64
|
"Phone-Number": ["value"],
|
|
56
65
|
"Payment-Card": ["card_number"],
|
|
66
|
+
"Tracking-Number": ["value"],
|
|
67
|
+
"Credential": ["value"],
|
|
57
68
|
"Media-Content": ["url"],
|
|
58
69
|
}
|
|
59
70
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pycti
|
|
3
|
-
Version: 6.0.
|
|
3
|
+
Version: 6.0.10
|
|
4
4
|
Summary: Python API client for OpenCTI.
|
|
5
5
|
Home-page: https://github.com/OpenCTI-Platform/client-python
|
|
6
6
|
Author: Filigran
|
|
@@ -29,12 +29,12 @@ Requires-Dist: python-magic-bin~=0.4.14; sys_platform == "win32"
|
|
|
29
29
|
Requires-Dist: python_json_logger~=2.0.4
|
|
30
30
|
Requires-Dist: pyyaml~=6.0
|
|
31
31
|
Requires-Dist: requests~=2.31.0
|
|
32
|
-
Requires-Dist: setuptools~=69.
|
|
32
|
+
Requires-Dist: setuptools~=69.5.1
|
|
33
33
|
Requires-Dist: filigran-sseclient~=1.0.0
|
|
34
34
|
Requires-Dist: stix2~=3.0.1
|
|
35
35
|
Requires-Dist: cachetools~=5.3.0
|
|
36
36
|
Provides-Extra: dev
|
|
37
|
-
Requires-Dist: black~=24.
|
|
37
|
+
Requires-Dist: black~=24.4.0; extra == "dev"
|
|
38
38
|
Requires-Dist: build~=1.2.1; extra == "dev"
|
|
39
39
|
Requires-Dist: isort~=5.13.0; extra == "dev"
|
|
40
40
|
Requires-Dist: types-pytz~=2024.1.0.20240203; extra == "dev"
|
|
@@ -47,7 +47,7 @@ Requires-Dist: types-python-dateutil~=2.9.0; extra == "dev"
|
|
|
47
47
|
Requires-Dist: wheel~=0.43.0; extra == "dev"
|
|
48
48
|
Provides-Extra: doc
|
|
49
49
|
Requires-Dist: autoapi~=2.0.1; extra == "doc"
|
|
50
|
-
Requires-Dist: sphinx-autodoc-typehints~=2.
|
|
50
|
+
Requires-Dist: sphinx-autodoc-typehints~=2.1.0; extra == "doc"
|
|
51
51
|
Requires-Dist: sphinx-rtd-theme~=2.0.0; extra == "doc"
|
|
52
52
|
|
|
53
53
|
# OpenCTI client for Python
|
|
@@ -4,7 +4,7 @@ prometheus-client~=0.20.0
|
|
|
4
4
|
python_json_logger~=2.0.4
|
|
5
5
|
pyyaml~=6.0
|
|
6
6
|
requests~=2.31.0
|
|
7
|
-
setuptools~=69.
|
|
7
|
+
setuptools~=69.5.1
|
|
8
8
|
filigran-sseclient~=1.0.0
|
|
9
9
|
stix2~=3.0.1
|
|
10
10
|
cachetools~=5.3.0
|
|
@@ -16,7 +16,7 @@ python-magic~=0.4.27
|
|
|
16
16
|
python-magic-bin~=0.4.14
|
|
17
17
|
|
|
18
18
|
[dev]
|
|
19
|
-
black~=24.
|
|
19
|
+
black~=24.4.0
|
|
20
20
|
build~=1.2.1
|
|
21
21
|
isort~=5.13.0
|
|
22
22
|
types-pytz~=2024.1.0.20240203
|
|
@@ -30,5 +30,5 @@ wheel~=0.43.0
|
|
|
30
30
|
|
|
31
31
|
[doc]
|
|
32
32
|
autoapi~=2.0.1
|
|
33
|
-
sphinx-autodoc-typehints~=2.
|
|
33
|
+
sphinx-autodoc-typehints~=2.1.0
|
|
34
34
|
sphinx-rtd-theme~=2.0.0
|
|
@@ -40,14 +40,14 @@ install_requires =
|
|
|
40
40
|
python_json_logger~=2.0.4
|
|
41
41
|
pyyaml~=6.0
|
|
42
42
|
requests~=2.31.0
|
|
43
|
-
setuptools~=69.
|
|
43
|
+
setuptools~=69.5.1
|
|
44
44
|
filigran-sseclient~=1.0.0
|
|
45
45
|
stix2~=3.0.1
|
|
46
46
|
cachetools~=5.3.0
|
|
47
47
|
|
|
48
48
|
[options.extras_require]
|
|
49
49
|
dev =
|
|
50
|
-
black~=24.
|
|
50
|
+
black~=24.4.0
|
|
51
51
|
build~=1.2.1
|
|
52
52
|
isort~=5.13.0
|
|
53
53
|
types-pytz~=2024.1.0.20240203
|
|
@@ -60,7 +60,7 @@ dev =
|
|
|
60
60
|
wheel~=0.43.0
|
|
61
61
|
doc =
|
|
62
62
|
autoapi~=2.0.1
|
|
63
|
-
sphinx-autodoc-typehints~=2.
|
|
63
|
+
sphinx-autodoc-typehints~=2.1.0
|
|
64
64
|
sphinx-rtd-theme~=2.0.0
|
|
65
65
|
|
|
66
66
|
[egg_info]
|
|
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
|