44import datetime
55import datefinder
66import dateutil .parser
7+ import pytz
78
89datefinder .ValueError = ValueError , OverflowError
10+ from stix2 import ObjectPath , ObservationExpression , EqualityComparisonExpression , HashConstant
911
12+ utc = pytz .UTC
1013
11- class Stix2 :
14+
15+ class OpenCTIStix2 :
1216 """
1317 Python API for Stix2 in OpenCTI
1418 :param opencti: OpenCTI instance
@@ -29,7 +33,7 @@ def convert_markdown(self, text):
2933 def filter_objects (self , uuids , objects ):
3034 result = []
3135 for object in objects :
32- if object ['id' ] not in uuids :
36+ if 'id' in object and object ['id' ] not in uuids :
3337 result .append (object )
3438 return result
3539
@@ -59,6 +63,7 @@ def prepare_export(self, entity, stix_object, mode='simple'):
5963 created_by_ref ['created' ] = self .format_date (entity_created_by_ref ['created' ])
6064 created_by_ref ['modified' ] = self .format_date (entity_created_by_ref ['modified' ])
6165 if self .not_empty (entity_created_by_ref ['alias' ]): created_by_ref ['x_opencti_aliases' ] = entity_created_by_ref ['alias' ]
66+ created_by_ref ['x_opencti_identity_type' ] = entity_created_by_ref ['entity_type' ]
6267 created_by_ref ['x_opencti_id' ] = entity_created_by_ref ['id' ]
6368
6469 stix_object ['created_by_ref' ] = created_by_ref ['id' ]
@@ -129,7 +134,9 @@ def prepare_export(self, entity, stix_object, mode='simple'):
129134
130135 result .append (stix_object )
131136
132- uuids = [x ['id' ] for x in result ]
137+ uuids = []
138+ for x in result :
139+ uuids .append (x ['id' ])
133140 if mode == 'full' and len (objects_to_get ) > 0 :
134141 for entity_object in objects_to_get :
135142 entity_object_data = None
@@ -250,6 +257,7 @@ def prepare_export(self, entity, stix_object, mode='simple'):
250257 return result
251258
252259 def import_object (self , stix_object , update = False ):
260+ self .opencti .log ('Importing a ' + stix_object ['type' ])
253261 # Reports
254262 reports = {}
255263 # Created By Ref
@@ -396,6 +404,7 @@ def import_object(self, stix_object, update=False):
396404 'attack-pattern' : self .create_attack_pattern ,
397405 'course-of-action' : self .create_course_of_action ,
398406 'report' : self .create_report ,
407+ 'indicator' : self .create_indicator ,
399408 }
400409 do_import = importer .get (stix_object ['type' ], lambda stix_object , update : self .unknown_type (stix_object , update ))
401410 stix_object_result = do_import (stix_object , update )
@@ -816,8 +825,7 @@ def create_report(self, stix_object, update=False):
816825 stix_object ['published' ] if 'published' in stix_object else '' ,
817826 stix_object ['x_opencti_report_class' ] if 'x_opencti_report_class' in stix_object else 'external' ,
818827 stix_object ['x_opencti_object_status' ] if 'x_opencti_object_status' in stix_object else 0 ,
819- stix_object [
820- 'x_opencti_source_confidence_level' ] if 'x_opencti_source_confidence_level' in stix_object else 3 ,
828+ stix_object ['x_opencti_source_confidence_level' ] if 'x_opencti_source_confidence_level' in stix_object else 3 ,
821829 stix_object ['x_opencti_graph_data' ] if 'x_opencti_graph_data' in stix_object else '' ,
822830 stix_object ['x_opencti_id' ] if 'x_opencti_id' in stix_object else None ,
823831 stix_object ['id' ] if 'id' in stix_object else None ,
@@ -826,12 +834,39 @@ def create_report(self, stix_object, update=False):
826834 )
827835
828836 def export_stix_observable (self , entity ):
829- stix_observable = {
830- 'id' : entity ['stix_id' ],
831- 'type' : entity ['entity_type' ],
832- 'value' : entity ['observable_value' ]
833- }
834- return self .prepare_export (entity , stix_observable )
837+ stix_observable = dict ()
838+ stix_observable ['id' ] = entity ['stix_id' ]
839+ stix_observable ['type' ] = 'indicator'
840+ stix_observable ['name' ] = 'Indicator'
841+ if self .not_empty (entity ['description' ]): stix_observable ['description' ] = entity ['description' ]
842+ stix_observable ['labels' ] = ['indicator' ]
843+ stix_observable ['created' ] = self .format_date (entity ['created_at' ])
844+ stix_observable ['modified' ] = self .format_date (entity ['updated_at' ])
845+ stix_observable ['x_opencti_observable_type' ] = entity ['entity_type' ]
846+ stix_observable ['x_opencti_observable_value' ] = entity ['observable_value' ]
847+ stix_observable ['x_opencti_id' ] = entity ['id' ]
848+ if len (entity ['stixRelations' ]) > 0 :
849+ first_seen = utc .localize (datetime .datetime .utcnow ())
850+ for relation in entity ['stixRelations' ]:
851+ relation_first_seen = dateutil .parser .parse (relation ['first_seen' ])
852+ if relation_first_seen < first_seen :
853+ first_seen = relation_first_seen
854+ stix_observable ['valid_from' ] = self .format_date (first_seen )
855+ final_stix_observable = self .prepare_observable (entity , stix_observable )
856+ return self .prepare_export (entity , final_stix_observable )
857+
858+ def create_indicator (self , stix_object , update = False ):
859+ if 'x_opencti_observable_type' in stix_object and 'x_opencti_observable_value' in stix_object :
860+ return self .opencti .create_stix_observable_if_not_exists (
861+ stix_object ['x_opencti_observable_type' ],
862+ stix_object ['x_opencti_observable_value' ],
863+ self .convert_markdown (stix_object ['description' ]) if 'description' in stix_object else '' ,
864+ stix_object ['id' ] if 'id' in stix_object else None ,
865+ stix_object ['created' ] if 'created' in stix_object else None ,
866+ stix_object ['modified' ] if 'modified' in stix_object else None ,
867+ )
868+ # TODO: Implement extraction of observables from STIX2 patterns
869+ return None
835870
836871 def export_stix_relation (self , entity ):
837872 stix_relation = dict ()
@@ -851,6 +886,7 @@ def export_stix_relation(self, entity):
851886 if self .not_empty (entity ['weight' ]): stix_relation ['x_opencti_weight' ] = entity ['weight' ]
852887 if self .not_empty (entity ['role_played' ]): stix_relation ['x_opencti_role_played' ] = entity ['role_played' ]
853888 if self .not_empty (entity ['score' ]): stix_relation ['x_opencti_score' ] = entity ['score' ]
889+ stix_relation ['x_opencti_id' ] = entity ['id' ]
854890 return self .prepare_export (entity , stix_relation )
855891
856892 def import_relationship (self , stix_relation , update = False ):
@@ -862,15 +898,15 @@ def import_relationship(self, stix_relation, update=False):
862898 # Check entities
863899 if stix_relation ['source_ref' ] in self .mapping_cache :
864900 source_id = self .mapping_cache [stix_relation ['source_ref' ]]['id' ]
865- source_type = self .mapping_cache [stix_relation ['source_ref' ]]['type' ]
901+ source_type = self .mapping_cache [stix_relation ['source_ref' ]]['type' ] if stix_relation [ 'relationship_type' ] != 'indicates' else 'observable'
866902 else :
867903 if 'x_opencti_source_ref' in stix_relation :
868904 stix_object_result = self .opencti .get_stix_domain_entity_by_id (stix_relation ['x_opencti_source_ref' ])
869905 else :
870906 stix_object_result = self .opencti .get_stix_domain_entity_by_stix_id (stix_relation ['source_ref' ])
871907 if stix_object_result is not None :
872908 source_id = stix_object_result ['id' ]
873- source_type = stix_object_result ['entity_type' ]
909+ source_type = stix_object_result ['entity_type' ] if stix_relation [ 'relationship_type' ] != 'indicates' else 'observable'
874910 else :
875911 self .opencti .log ('Source ref of the relationship not found, doing nothing...' )
876912 return None
@@ -1032,6 +1068,23 @@ def resolve_author(self, title):
10321068 return self .get_author ('The MITRE Corporation' )
10331069 return None
10341070
1071+ def prepare_observable (self , entity , stix_observable ):
1072+ if 'file' in entity ['entity_type' ]:
1073+ observable_type = 'file'
1074+ elif entity ['entity_type' ] == 'domain' :
1075+ observable_type = 'domain-name'
1076+ else :
1077+ observable_type = entity ['entity_type' ]
1078+
1079+ if observable_type == 'file' :
1080+ lhs = ObjectPath (observable_type , ['hashes' , entity ['entity_type' ].split ('-' )[1 ].upper ()])
1081+ ece = ObservationExpression (EqualityComparisonExpression (lhs , HashConstant (entity ['observable_value' ], entity ['entity_type' ].split ('-' )[1 ].upper ())))
1082+ if observable_type == 'ipv4-addr' or observable_type == 'ipv6-addr' or observable_type == 'domain_name' or observable_type == 'url' :
1083+ lhs = ObjectPath (observable_type , ["value" ])
1084+ ece = ObservationExpression (EqualityComparisonExpression (lhs , entity ['observable_value' ]))
1085+ stix_observable ['pattern' ] = str (ece )
1086+ return stix_observable
1087+
10351088 def get_author (self , name ):
10361089 if name in self .mapping_cache :
10371090 return self .mapping_cache [name ]
@@ -1041,6 +1094,8 @@ def get_author(self, name):
10411094 return author_id
10421095
10431096 def format_date (self , date ):
1097+ if isinstance (date , datetime .date ):
1098+ return date .isoformat (timespec = 'milliseconds' ).replace ('+00:00' , 'Z' )
10441099 if date is not None :
10451100 return dateutil .parser .parse (date ).isoformat (timespec = 'milliseconds' ).replace ('+00:00' , 'Z' )
10461101 else :
0 commit comments