Skip to content
This repository was archived by the owner on Dec 5, 2025. It is now read-only.

Commit 77a0da5

Browse files
author
Samuel Hassine
committed
Enhance existing methods for creating STIX 2 entities
1 parent 1f67999 commit 77a0da5

File tree

5 files changed

+76
-69
lines changed

5 files changed

+76
-69
lines changed

examples/stix2/export.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import yaml
55
import json
66

7-
from python.pycti.opencti import OpenCTI
7+
from opencti import OpenCTI
88

99
# Load configuration
1010
config = yaml.load(open(os.path.dirname(__file__) + '/config.yml'))
@@ -16,7 +16,7 @@
1616
opencti = OpenCTI(config['opencti']['api_url'], config['opencti']['api_key'], config['opencti']['log_file'], config['opencti']['verbose'])
1717

1818
# Import the bundle
19-
bundle = opencti.stix2_export_entity('report', '466872bd-41d0-3d38-af2f-0b964b7ff993', 'full')
19+
bundle = opencti.stix2_export_entity('report', '{ENTITY_ID}', 'full')
2020

2121
with open(export_file, 'w') as file:
2222
json.dump(bundle, file, indent=4)

examples/stix2/import.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import os
44
import yaml
55

6-
from pycti.opencti import OpenCTI
6+
from opencti import OpenCTI
77

88
# Load configuration
99
config = yaml.load(open(os.path.dirname(__file__) + '/config.yml'))
@@ -15,4 +15,4 @@
1515
opencti = OpenCTI(config['opencti']['api_url'], config['opencti']['api_key'], config['opencti']['log_file'], config['opencti']['verbose'])
1616

1717
# Import the bundle
18-
opencti.stix2_import_bundle_from_file(file_to_import, False)
18+
opencti.stix2_import_bundle_from_file(file_to_import)

pycti/opencti.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import uuid
99
import base64
1010

11-
from .stix2 import Stix2
11+
from pycti.stix2 import Stix2
1212

1313

1414
class OpenCTI:
@@ -919,8 +919,15 @@ def create_identity(self, type, name, description, id=None, stix_id=None, create
919919
})
920920
return result['data']['identityAdd']
921921

922-
def create_identity_if_not_exists(self, type, name, description, id=None, stix_id=None, created=None,
923-
modified=None):
922+
def create_identity_if_not_exists(self,
923+
type,
924+
name,
925+
description,
926+
id=None,
927+
stix_id=None,
928+
created=None,
929+
modified=None
930+
):
924931
object_result = self.check_existing_stix_domain_entity(stix_id, name, type)
925932
if object_result is not None:
926933
return object_result

pycti/stix2.py

Lines changed: 61 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def __init__(self, opencti):
1717
self.opencti = opencti
1818
self.mapping_cache = {}
1919

20-
def unknown_type(self, stix_object):
20+
def unknown_type(self, stix_object, update=False):
2121
self.opencti.log('Unknown object type "' + stix_object['type'] + '", doing nothing...')
2222

2323
def convert_markdown(self, text):
@@ -311,8 +311,10 @@ def import_object(self, stix_object, update=False):
311311
else:
312312
published = datetime.datetime.today().strftime('%Y-%m-%dT%H:%M:%SZ')
313313

314-
if 'mitre-attack (' in source_name and 'name' in stix_object:
314+
if 'mitre' in source_name and 'name' in stix_object:
315315
title = '[MITRE ATT&CK] ' + stix_object['name']
316+
if 'modified' in stix_object:
317+
published = stix_object['modified']
316318
else:
317319
title = source_name
318320

@@ -392,7 +394,7 @@ def import_object(self, stix_object, update=False):
392394
'course-of-action': self.create_course_of_action,
393395
'report': self.create_report,
394396
}
395-
do_import = importer.get(stix_object['type'], lambda stix_object: self.unknown_type(stix_object))
397+
do_import = importer.get(stix_object['type'], lambda stix_object, update: self.unknown_type(stix_object, update))
396398
stix_object_result = do_import(stix_object, update)
397399

398400
# Add embedded relationships
@@ -465,6 +467,8 @@ def export_identity(self, entity):
465467
'x_opencti_id': entity['id'],
466468
'x_opencti_aliases': entity['alias'],
467469
}
470+
if entity['entity_type'] == 'organization' and 'organization_class' in entity:
471+
identity['x_opencti_organization_class'] = entity['organization_class']
468472
return self.prepare_export(entity, identity)
469473

470474
def create_identity(self, stix_object, update=False):
@@ -667,7 +671,7 @@ def export_tool(self, entity):
667671
}
668672
return self.prepare_export(entity, tool)
669673

670-
def create_tool(self, stix_object):
674+
def create_tool(self, stix_object, update=False):
671675
return self.opencti.create_tool_if_not_exists(
672676
stix_object['name'],
673677
self.convert_markdown(stix_object['description']) if 'description' in stix_object else '',
@@ -735,7 +739,7 @@ def create_attack_pattern(self, stix_object, update=False):
735739
if 'x_mitre_platforms' in stix_object:
736740
self.opencti.update_stix_domain_entity_field(attack_pattern['id'], 'platform', stix_object['x_mitre_platforms'])
737741
if 'x_mitre_permissions_required' in stix_object:
738-
self.opencti.update_stix_domain_entity_field(attack_pattern['id'], 'required_permission', stix_object['x_mitre_permissions_required'])
742+
self.opencti.update_stix_domain_entity_field(attack_pattern['id'], 'required_permission', stix_object['x_mitre_permissions_required'])
739743
return attack_pattern
740744

741745
def export_course_of_action(self, entity):
@@ -823,7 +827,7 @@ def export_stix_relation(self, entity):
823827
}
824828
return self.prepare_export(entity, stix_relation)
825829

826-
def import_relationship(self, stix_relation):
830+
def import_relationship(self, stix_relation, update=False):
827831
# Check relation
828832
stix_relation_result = self.opencti.get_stix_relation_by_stix_id(stix_relation['id'])
829833
if stix_relation_result is not None:
@@ -854,6 +858,20 @@ def import_relationship(self, stix_relation):
854858
self.opencti.log('Target ref of the relationship not found, doing nothing...')
855859
return None
856860

861+
date = None
862+
if 'external_references' in stix_relation:
863+
for external_reference in stix_relation['external_references']:
864+
if 'description' in external_reference:
865+
matches = list(datefinder.find_dates(external_reference['description']))
866+
else:
867+
matches = list(datefinder.find_dates(external_reference['source_name']))
868+
if len(matches) > 0:
869+
date = matches[0].strftime('%Y-%m-%dT%H:%M:%SZ')
870+
else:
871+
date = datetime.datetime.today().strftime('%Y-%m-%dT%H:%M:%SZ')
872+
if date is None:
873+
date = datetime.datetime.utcnow().replace(microsecond=0, tzinfo=datetime.timezone.utc).isoformat()
874+
857875
stix_relation_id = self.opencti.create_relation_if_not_exists(
858876
source_id,
859877
source_type,
@@ -862,11 +880,9 @@ def import_relationship(self, stix_relation):
862880
stix_relation['relationship_type'],
863881
stix_relation['description'] if 'description' in stix_relation else '',
864882
stix_relation[
865-
'x_opencti_first_seen'] if 'x_opencti_first_seen' in stix_relation else datetime.datetime.utcnow().replace(
866-
microsecond=0, tzinfo=datetime.timezone.utc).isoformat(),
883+
'x_opencti_first_seen'] if 'x_opencti_first_seen' in stix_relation else date,
867884
stix_relation[
868-
'x_opencti_last_seen'] if 'x_opencti_last_seen' in stix_relation else datetime.datetime.utcnow().replace(
869-
microsecond=0, tzinfo=datetime.timezone.utc).isoformat(),
885+
'x_opencti_last_seen'] if 'x_opencti_last_seen' in stix_relation else date,
870886
stix_relation['x_opencti_weight'] if 'x_opencti_weight' in stix_relation else 4,
871887
stix_relation['x_opencti_role_played'] if 'x_opencti_role_played' in stix_relation else None,
872888
stix_relation['x_opencti_id'] if 'x_opencti_id' in stix_relation else None,
@@ -947,64 +963,48 @@ def import_relationship(self, stix_relation):
947963
self.opencti.add_object_ref_to_report_if_not_exists(report_id, stix_relation_id)
948964

949965
def resolve_author(self, title):
950-
if 'fireeye' in title.lower():
951-
if 'FireEye' in self.mapping_cache:
952-
return self.mapping_cache['FireEye']
953-
else:
954-
author_id = self.opencti.create_identity_if_not_exists('Organization', 'FireEye', '')['id']
955-
self.mapping_cache['FireEye'] = author_id
956-
return author_id
966+
if 'fireeye' in title.lower() or 'mandiant' in title.lower():
967+
return self.get_author('FireEye')
957968
if 'eset' in title.lower():
958-
if 'ESET' in self.mapping_cache:
959-
return self.mapping_cache['ESET']
960-
else:
961-
author_id = self.opencti.create_identity_if_not_exists('Organization', 'ESET', '')['id']
962-
self.mapping_cache['ESET'] = author_id
963-
return author_id
964-
if 'unit 42' in title.lower():
965-
if 'PaloAlto' in self.mapping_cache:
966-
return self.mapping_cache['PaloAlto']
967-
else:
968-
author_id = self.opencti.create_identity_if_not_exists('Organization', 'Palo Alto Networks', '')['id']
969-
self.mapping_cache['PaloAlto'] = author_id
970-
return author_id
969+
return self.get_author('ESET')
970+
if 'dragos' in title.lower():
971+
return self.get_author('Dragos')
972+
if 'us-cert' in title.lower():
973+
return self.get_author('US-CERT')
974+
if 'unit 42' in title.lower() or 'unit42' in title.lower() or 'palo alto' in title.lower():
975+
return self.get_author('Palo Alto Networks')
971976
if 'accenture' in title.lower():
972-
if 'Accenture' in self.mapping_cache:
973-
return self.mapping_cache['Accenture']
974-
else:
975-
author_id = self.opencti.create_identity_if_not_exists('Organization', 'Accenture', '')['id']
976-
self.mapping_cache['Accenture'] = author_id
977-
return author_id
977+
return self.get_author('Accenture')
978978
if 'symantec' in title.lower():
979-
if 'Symantec' in self.mapping_cache:
980-
return self.mapping_cache['Symantec']
981-
else:
982-
author_id = self.opencti.create_identity_if_not_exists('Organization', 'Symantec', '')['id']
983-
self.mapping_cache['Symantec'] = author_id
984-
return author_id
979+
return self.get_author('Symantec')
980+
if 'trendmicro' in title.lower() or 'trend micro' in title.lower():
981+
return self.get_author('Trend Micro')
985982
if 'mcafee' in title.lower():
986-
if 'McAfee' in self.mapping_cache:
987-
return self.mapping_cache['McAfee']
988-
else:
989-
author_id = self.opencti.create_identity_if_not_exists('Organization', 'McAfee', '')['id']
990-
self.mapping_cache['McAfee'] = author_id
991-
return author_id
983+
return self.get_author('McAfee')
992984
if 'crowdstrike' in title.lower():
993-
if 'CrowdStrike' in self.mapping_cache:
994-
return self.mapping_cache['CrowdStrike']
995-
else:
996-
author_id = self.opencti.create_identity_if_not_exists('Organization', 'CrowdStrike', '')['id']
997-
self.mapping_cache['CrowdStrike'] = author_id
998-
return author_id
999-
if 'mitre atta&ck' in title.lower():
1000-
if 'Mitre' in self.mapping_cache:
1001-
return self.mapping_cache['Mitre']
1002-
else:
1003-
author_id = self.opencti.create_identity_if_not_exists('Organization', 'The MITRE Corporation', '')['id']
1004-
self.mapping_cache['Mitre'] = author_id
1005-
return author_id
985+
return self.get_author('CrowdStrike')
986+
if 'securelist' in title.lower() or 'kaspersky' in title.lower():
987+
return self.get_author('Kaspersky')
988+
if 'f-secure' in title.lower():
989+
return self.get_author('F-Secure')
990+
if 'checkpoint' in title.lower():
991+
return self.get_author('CheckPoint')
992+
if 'talos' in title.lower():
993+
return self.get_author('Cisco Talos')
994+
if 'secureworks' in title.lower():
995+
return self.get_author('Dell SecureWorks')
996+
if 'mitre att&ck' in title.lower():
997+
return self.get_author('The MITRE Corporation')
1006998
return None
1007999

1000+
def get_author(self, name):
1001+
if name in self.mapping_cache:
1002+
return self.mapping_cache[name]
1003+
else:
1004+
author_id = self.opencti.create_identity_if_not_exists('Organization', name, '')['id']
1005+
self.mapping_cache[name] = author_id
1006+
return author_id
1007+
10081008
def import_bundle(self, stix_bundle, update=False, types=[]):
10091009
# Check if the bundle is correctly formated
10101010
if 'type' not in stix_bundle or stix_bundle['type'] != 'bundle':

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
print("warning: pypandoc module not found, could not convert Markdown to RST")
1313
read_md = lambda f: open(f, 'r').read()
1414

15-
VERSION = "1.0.10"
15+
VERSION = "1.0.11"
1616

1717
class VerifyVersionCommand(install):
1818
description = 'verify that the git tag matches our version'

0 commit comments

Comments
 (0)