2017-07-24 13:26:22 +02:00
2016-09-26 00:26:09 +02:00
# -*- coding: utf-8 -*-
import datetime
import json
2016-09-27 19:47:22 +02:00
import os
2016-11-17 17:07:29 +01:00
import base64
2016-12-07 18:37:35 +01:00
from io import BytesIO
from zipfile import ZipFile
import hashlib
2017-08-30 12:47:32 +02:00
import sys
import uuid
2018-01-22 10:33:34 +01:00
from collections import defaultdict
2017-08-30 12:47:32 +02:00
2017-12-22 14:49:14 +01:00
from . import deprecated
2017-08-30 12:47:32 +02:00
from . abstract import AbstractMISP
from . exceptions import UnknownMISPObjectTemplate , InvalidMISPObject , PyMISPError , NewEventError , NewAttributeError
2017-11-09 02:33:55 +01:00
import six # Remove that import when discarding python2 support.
2017-11-08 03:10:04 +01:00
import logging
logger = logging . getLogger ( ' pymisp ' )
2017-08-30 12:47:32 +02:00
if six . PY2 :
2017-11-08 03:10:04 +01:00
logger . warning ( " You ' re using python 2, it is strongly recommended to use python >=3.5 " )
2016-12-07 18:37:35 +01:00
2018-01-03 14:36:10 +01:00
# This is required because Python 2 is a pain.
from datetime import tzinfo , timedelta
class UTC ( tzinfo ) :
""" UTC """
def utcoffset ( self , dt ) :
return timedelta ( 0 )
def tzname ( self , dt ) :
return " UTC "
def dst ( self , dt ) :
return timedelta ( 0 )
2016-09-27 19:47:22 +02:00
try :
from dateutil . parser import parse
except ImportError :
2018-01-24 15:21:08 +01:00
logger . exception ( " Cannot import dateutil " )
2016-09-27 19:47:22 +02:00
pass
try :
import jsonschema
except ImportError :
2018-01-24 15:21:08 +01:00
logger . exception ( " Cannot import jsonschema " )
2016-09-27 19:47:22 +02:00
pass
2016-09-26 00:26:09 +02:00
2016-11-17 17:07:29 +01:00
try :
# pyme renamed to gpg the 2016-10-28
import gpg
from gpg . constants . sig import mode
has_pyme = True
except ImportError :
2016-11-17 17:30:17 +01:00
try :
# pyme renamed to gpg the 2016-10-28
import pyme as gpg
from pyme . constants . sig import mode
has_pyme = True
except ImportError :
has_pyme = False
2016-11-17 17:07:29 +01:00
2016-09-28 18:50:05 +02:00
# Least dirty way to support python 2 and 3
try :
basestring
2016-10-27 23:04:23 +02:00
unicode
2016-09-28 18:50:05 +02:00
except NameError :
basestring = str
2016-10-27 21:58:58 +02:00
unicode = str
2016-09-28 18:50:05 +02:00
2016-09-26 00:26:09 +02:00
2017-09-12 16:46:06 +02:00
def _int_to_str ( d ) :
# transform all integer back to string
for k , v in d . items ( ) :
if isinstance ( v , ( int , float ) ) and not isinstance ( v , bool ) :
d [ k ] = str ( v )
return d
class MISPAttribute ( AbstractMISP ) :
2016-09-26 00:26:09 +02:00
2018-01-04 10:58:24 +01:00
def __init__ ( self , describe_types = None , strict = False ) :
""" Represents an Attribute
: describe_type : Use it is you want to overwrite the defualt describeTypes . json file ( you don ' t)
: strict : If false , fallback to sane defaults for the attribute type if the ones passed by the user are incorrect
"""
2017-12-20 16:59:52 +01:00
super ( MISPAttribute , self ) . __init__ ( )
2017-07-26 10:10:12 +02:00
if not describe_types :
2017-09-12 16:46:06 +02:00
ressources_path = os . path . join ( os . path . abspath ( os . path . dirname ( __file__ ) ) , ' data ' )
with open ( os . path . join ( ressources_path , ' describeTypes.json ' ) , ' r ' ) as f :
2017-07-26 10:10:12 +02:00
t = json . load ( f )
describe_types = t [ ' result ' ]
2017-09-12 16:46:06 +02:00
self . __categories = describe_types [ ' categories ' ]
2017-09-18 16:37:55 +02:00
self . _types = describe_types [ ' types ' ]
2017-09-12 16:46:06 +02:00
self . __category_type_mapping = describe_types [ ' category_type_mappings ' ]
self . __sane_default = describe_types [ ' sane_defaults ' ]
2018-01-04 10:58:24 +01:00
self . __strict = strict
2018-05-03 20:51:04 +02:00
self . uuid = str ( uuid . uuid4 ( ) )
2018-01-03 14:36:10 +01:00
self . ShadowAttribute = [ ]
2016-09-26 00:26:09 +02:00
2017-12-21 18:46:28 +01:00
@property
def known_types ( self ) :
2018-01-03 14:36:10 +01:00
""" Returns a list of all the known MISP attributes types """
2017-09-18 16:37:55 +02:00
return self . _types
2017-12-21 18:46:28 +01:00
@property
def malware_binary ( self ) :
2018-01-03 14:36:10 +01:00
""" Returns a BytesIO of the malware (if the attribute has one, obvs). """
2017-12-21 18:46:28 +01:00
if hasattr ( self , ' _malware_binary ' ) :
return self . _malware_binary
return None
2016-11-17 17:07:29 +01:00
2018-01-03 14:36:10 +01:00
@property
def shadow_attributes ( self ) :
return self . ShadowAttribute
@shadow_attributes.setter
def shadow_attributes ( self , shadow_attributes ) :
""" Set a list of prepared MISPShadowAttribute. """
if all ( isinstance ( x , MISPShadowAttribute ) for x in shadow_attributes ) :
self . ShadowAttribute = shadow_attributes
else :
raise PyMISPError ( ' All the attributes have to be of type MISPShadowAttribute. ' )
2016-11-17 17:07:29 +01:00
2017-01-06 22:24:39 +01:00
def delete ( self ) :
2017-12-22 14:49:14 +01:00
""" Mark the attribute as deleted (soft delete) """
2017-01-06 22:24:39 +01:00
self . deleted = True
2018-01-03 14:36:10 +01:00
def add_proposal ( self , shadow_attribute = None , * * kwargs ) :
""" Alias for add_shadow_attribute """
2018-05-03 21:36:40 +02:00
return self . add_shadow_attribute ( shadow_attribute , * * kwargs )
2018-01-03 14:36:10 +01:00
def add_shadow_attribute ( self , shadow_attribute = None , * * kwargs ) :
""" Add a tag to the attribute (by name or a MISPTag object) """
if isinstance ( shadow_attribute , MISPShadowAttribute ) :
misp_shadow_attribute = shadow_attribute
elif isinstance ( shadow_attribute , dict ) :
misp_shadow_attribute = MISPShadowAttribute ( )
misp_shadow_attribute . from_dict ( * * shadow_attribute )
elif kwargs :
misp_shadow_attribute = MISPShadowAttribute ( )
misp_shadow_attribute . from_dict ( * * kwargs )
else :
raise PyMISPError ( " The shadow_attribute is in an invalid format (can be either string, MISPShadowAttribute, or an expanded dict): {} " . format ( shadow_attribute ) )
self . shadow_attributes . append ( misp_shadow_attribute )
self . edited = True
2018-05-03 21:36:40 +02:00
return misp_shadow_attribute
2018-01-03 14:36:10 +01:00
2017-08-30 12:47:32 +02:00
def from_dict ( self , * * kwargs ) :
2016-09-28 18:20:37 +02:00
if kwargs . get ( ' type ' ) and kwargs . get ( ' category ' ) :
2017-09-12 16:46:06 +02:00
if kwargs [ ' type ' ] not in self . __category_type_mapping [ kwargs [ ' category ' ] ] :
2018-01-04 10:58:24 +01:00
if self . __strict :
raise NewAttributeError ( ' {} and {} is an invalid combination, type for this category has to be in {} ' . format (
kwargs . get ( ' type ' ) , kwargs . get ( ' category ' ) , ( ' , ' . join ( self . __category_type_mapping [ kwargs [ ' category ' ] ] ) ) ) )
else :
kwargs . pop ( ' category ' , None )
2017-12-21 18:46:28 +01:00
self . type = kwargs . pop ( ' type ' , None ) # Required
2017-09-12 16:46:06 +02:00
if self . type is None :
2016-09-28 18:20:37 +02:00
raise NewAttributeError ( ' The type of the attribute is required. ' )
2017-12-22 14:49:14 +01:00
if self . type not in self . known_types :
2017-09-18 16:37:55 +02:00
raise NewAttributeError ( ' {} is invalid, type has to be in {} ' . format ( self . type , ( ' , ' . join ( self . _types ) ) ) )
2016-09-28 18:20:37 +02:00
2017-09-12 16:46:06 +02:00
type_defaults = self . __sane_default [ self . type ]
2017-03-12 23:05:13 +01:00
2017-09-12 16:46:06 +02:00
self . value = kwargs . pop ( ' value ' , None )
2017-03-12 23:05:13 +01:00
if self . value is None :
2016-09-28 18:20:37 +02:00
raise NewAttributeError ( ' The value of the attribute is required. ' )
2016-09-27 19:47:22 +02:00
# Default values
2017-09-12 16:46:06 +02:00
self . category = kwargs . pop ( ' category ' , type_defaults [ ' default_category ' ] )
2017-10-22 18:17:48 +02:00
if self . category is None :
# In case the category key is passed, but None
self . category = type_defaults [ ' default_category ' ]
2017-09-12 16:46:06 +02:00
if self . category not in self . __categories :
raise NewAttributeError ( ' {} is invalid, category has to be in {} ' . format ( self . category , ( ' , ' . join ( self . __categories ) ) ) )
2016-09-28 18:20:37 +02:00
2017-09-12 16:46:06 +02:00
self . to_ids = kwargs . pop ( ' to_ids ' , bool ( int ( type_defaults [ ' to_ids ' ] ) ) )
2017-10-22 18:17:48 +02:00
if self . to_ids is None :
self . to_ids = bool ( int ( type_defaults [ ' to_ids ' ] ) )
2017-03-14 15:58:54 +01:00
if not isinstance ( self . to_ids , bool ) :
raise NewAttributeError ( ' {} is invalid, to_ids has to be True or False ' . format ( self . to_ids ) )
2017-10-26 18:05:51 +02:00
self . distribution = kwargs . pop ( ' distribution ' , None )
if self . distribution is not None :
self . distribution = int ( self . distribution )
2016-12-14 15:17:33 +01:00
if self . distribution not in [ 0 , 1 , 2 , 3 , 4 , 5 ] :
raise NewAttributeError ( ' {} is invalid, the distribution has to be in 0, 1, 2, 3, 4, 5 ' . format ( self . distribution ) )
2016-09-27 19:47:22 +02:00
# other possible values
2016-12-07 18:37:35 +01:00
if kwargs . get ( ' data ' ) :
2017-09-12 16:46:06 +02:00
self . data = kwargs . pop ( ' data ' )
2016-12-07 18:37:35 +01:00
self . _load_data ( )
2016-09-28 18:20:37 +02:00
if kwargs . get ( ' id ' ) :
2017-09-12 16:46:06 +02:00
self . id = int ( kwargs . pop ( ' id ' ) )
2017-07-27 15:42:56 +02:00
if kwargs . get ( ' event_id ' ) :
2017-09-18 12:43:48 +02:00
self . event_id = int ( kwargs . pop ( ' event_id ' ) )
2016-09-28 18:20:37 +02:00
if kwargs . get ( ' timestamp ' ) :
2018-01-03 14:36:10 +01:00
if sys . version_info > = ( 3 , 3 ) :
self . timestamp = datetime . datetime . fromtimestamp ( int ( kwargs . pop ( ' timestamp ' ) ) , datetime . timezone . utc )
else :
self . timestamp = datetime . datetime . fromtimestamp ( int ( kwargs . pop ( ' timestamp ' ) ) , UTC ( ) )
2016-09-28 18:20:37 +02:00
if kwargs . get ( ' sharing_group_id ' ) :
2017-09-12 16:46:06 +02:00
self . sharing_group_id = int ( kwargs . pop ( ' sharing_group_id ' ) )
2018-01-16 12:15:30 +01:00
if self . distribution == 4 :
# The distribution is set to sharing group, a sharing_group_id is required.
if not hasattr ( self , ' sharing_group_id ' ) :
raise NewAttributeError ( ' If the distribution is set to sharing group, a sharing group ID is required. ' )
elif not self . sharing_group_id :
# Cannot be None or 0 either.
raise NewAttributeError ( ' If the distribution is set to sharing group, a sharing group ID is required (cannot be {} ). ' . format ( self . sharing_group_id ) )
2017-01-18 00:20:24 +01:00
if kwargs . get ( ' Tag ' ) :
2017-12-20 10:53:46 +01:00
for tag in kwargs . pop ( ' Tag ' ) :
2018-01-03 14:36:10 +01:00
self . add_tag ( tag )
if kwargs . get ( ' ShadowAttribute ' ) :
for s_attr in kwargs . pop ( ' ShadowAttribute ' ) :
self . add_shadow_attribute ( s_attr )
2016-09-27 19:47:22 +02:00
2017-01-16 20:41:32 +01:00
# If the user wants to disable correlation, let them. Defaults to False.
2017-09-12 16:46:06 +02:00
self . disable_correlation = kwargs . pop ( " disable_correlation " , False )
2017-03-14 15:58:54 +01:00
if self . disable_correlation is None :
self . disable_correlation = False
2017-01-16 10:52:35 +01:00
2017-12-20 12:43:31 +01:00
super ( MISPAttribute , self ) . from_dict ( * * kwargs )
2017-09-12 16:46:06 +02:00
2017-12-22 14:49:14 +01:00
def to_dict ( self ) :
to_return = super ( MISPAttribute , self ) . to_dict ( )
if to_return . get ( ' data ' ) :
to_return [ ' data ' ] = base64 . b64encode ( self . data . getvalue ( ) ) . decode ( )
to_return = _int_to_str ( to_return )
return to_return
2016-12-07 18:37:35 +01:00
def _prepare_new_malware_sample ( self ) :
if ' | ' in self . value :
# Get the filename, ignore the md5, because humans.
self . malware_filename , md5 = self . value . split ( ' | ' )
else :
# Assuming the user only passed the filename
self . malware_filename = self . value
m = hashlib . md5 ( )
m . update ( self . data . getvalue ( ) )
2017-08-23 15:36:13 +02:00
self . value = self . malware_filename
2017-09-18 16:37:55 +02:00
md5 = m . hexdigest ( )
self . value = ' {} | {} ' . format ( self . malware_filename , md5 )
self . _malware_binary = self . data
2016-12-07 18:37:35 +01:00
self . encrypt = True
2017-11-02 17:26:05 +01:00
def __is_misp_encrypted_file ( self , f ) :
files_list = f . namelist ( )
if len ( files_list ) != 2 :
return False
md5_from_filename = ' '
md5_from_file = ' '
for name in files_list :
if name . endswith ( ' .filename.txt ' ) :
md5_from_filename = name . replace ( ' .filename.txt ' , ' ' )
else :
md5_from_file = name
if not md5_from_filename or not md5_from_file or md5_from_filename != md5_from_file :
return False
return True
2016-12-07 18:37:35 +01:00
def _load_data ( self ) :
if not isinstance ( self . data , BytesIO ) :
self . data = BytesIO ( base64 . b64decode ( self . data ) )
if self . type == ' malware-sample ' :
try :
with ZipFile ( self . data ) as f :
2017-11-02 17:26:05 +01:00
if not self . __is_misp_encrypted_file ( f ) :
raise Exception ( ' Not an existing malware sample ' )
2016-12-07 18:37:35 +01:00
for name in f . namelist ( ) :
2017-11-02 17:26:05 +01:00
if name . endswith ( ' .filename.txt ' ) :
2016-12-07 18:37:35 +01:00
with f . open ( name , pwd = b ' infected ' ) as unpacked :
2017-11-02 17:26:05 +01:00
self . malware_filename = unpacked . read ( ) . decode ( ) . strip ( )
2016-12-07 18:37:35 +01:00
else :
with f . open ( name , pwd = b ' infected ' ) as unpacked :
2017-09-18 16:37:55 +02:00
self . _malware_binary = BytesIO ( unpacked . read ( ) )
2017-11-09 02:33:55 +01:00
except Exception :
2016-12-07 18:37:35 +01:00
# not a encrypted zip file, assuming it is a new malware sample
self . _prepare_new_malware_sample ( )
2017-12-22 14:49:14 +01:00
def __repr__ ( self ) :
if hasattr ( self , ' value ' ) :
return ' < {self.__class__.__name__} (type= {self.type} , value= {self.value} ) ' . format ( self = self )
return ' < {self.__class__.__name__} (NotInitialized) ' . format ( self = self )
2017-12-21 18:46:28 +01:00
2018-01-03 14:36:10 +01:00
def verify ( self , gpg_uid ) : # pragma: no cover
2017-12-21 18:46:28 +01:00
# Not used
if not has_pyme :
raise PyMISPError ( ' pyme is required, please install: pip install --pre pyme3. You will also need libgpg-error-dev and libgpgme11-dev. ' )
signed_data = self . _serialize ( )
with gpg . Context ( ) as c :
keys = list ( c . keylist ( gpg_uid ) )
try :
c . verify ( signed_data , signature = base64 . b64decode ( self . sig ) , verify = keys [ : 1 ] )
return { self . uuid : True }
except Exception :
return { self . uuid : False }
2018-01-03 14:36:10 +01:00
def _serialize ( self ) : # pragma: no cover
2017-12-21 18:46:28 +01:00
# Not used
return ' {type} {category} {to_ids} {uuid} {timestamp} {comment} {deleted} {value} ' . format (
type = self . type , category = self . category , to_ids = self . to_ids , uuid = self . uuid , timestamp = self . timestamp ,
comment = self . comment , deleted = self . deleted , value = self . value ) . encode ( )
2018-01-03 14:36:10 +01:00
def sign ( self , gpg_uid , passphrase = None ) : # pragma: no cover
2017-12-21 18:46:28 +01:00
# Not used
if not has_pyme :
raise PyMISPError ( ' pyme is required, please install: pip install --pre pyme3. You will also need libgpg-error-dev and libgpgme11-dev. ' )
to_sign = self . _serialize ( )
with gpg . Context ( ) as c :
keys = list ( c . keylist ( gpg_uid ) )
c . signers = keys [ : 1 ]
if passphrase :
c . set_passphrase_cb ( lambda * args : passphrase )
signed , _ = c . sign ( to_sign , mode = mode . DETACH )
self . sig = base64 . b64encode ( signed ) . decode ( )
2017-12-22 14:49:14 +01:00
@deprecated
2018-01-03 14:36:10 +01:00
def get_known_types ( self ) : # pragma: no cover
2017-12-21 18:46:28 +01:00
return self . known_types
2017-12-22 14:49:14 +01:00
@deprecated
2018-01-03 14:36:10 +01:00
def get_malware_binary ( self ) : # pragma: no cover
2017-12-21 18:46:28 +01:00
return self . malware_binary
2017-09-18 16:37:55 +02:00
2017-12-22 14:49:14 +01:00
@deprecated
2018-01-03 14:36:10 +01:00
def _json ( self ) : # pragma: no cover
2017-08-24 17:09:16 +02:00
return self . to_dict ( )
2016-09-27 19:47:22 +02:00
2017-12-22 14:49:14 +01:00
@deprecated
2018-01-03 14:36:10 +01:00
def _json_full ( self ) : # pragma: no cover
2017-09-18 12:43:48 +02:00
return self . to_dict ( )
2017-12-22 14:49:14 +01:00
@deprecated
2018-01-03 14:36:10 +01:00
def set_all_values ( self , * * kwargs ) : # pragma: no cover
2017-12-21 18:46:28 +01:00
self . from_dict ( * * kwargs )
2016-09-26 00:26:09 +02:00
2017-09-12 16:46:06 +02:00
class MISPEvent ( AbstractMISP ) :
2016-09-26 00:26:09 +02:00
2017-04-09 00:02:02 +02:00
def __init__ ( self , describe_types = None , strict_validation = False ) :
2017-12-20 16:59:52 +01:00
super ( MISPEvent , self ) . __init__ ( )
2017-09-12 16:46:06 +02:00
ressources_path = os . path . join ( os . path . abspath ( os . path . dirname ( __file__ ) ) , ' data ' )
2017-04-09 00:02:02 +02:00
if strict_validation :
2017-09-12 16:46:06 +02:00
with open ( os . path . join ( ressources_path , ' schema.json ' ) , ' r ' ) as f :
self . __json_schema = json . load ( f )
2017-04-09 00:02:02 +02:00
else :
2017-09-12 16:46:06 +02:00
with open ( os . path . join ( ressources_path , ' schema-lax.json ' ) , ' r ' ) as f :
self . __json_schema = json . load ( f )
2016-09-27 19:47:22 +02:00
if not describe_types :
2017-09-12 16:46:06 +02:00
with open ( os . path . join ( ressources_path , ' describeTypes.json ' ) , ' r ' ) as f :
2017-04-09 00:02:02 +02:00
t = json . load ( f )
2016-09-27 19:47:22 +02:00
describe_types = t [ ' result ' ]
2016-09-26 00:26:09 +02:00
2017-09-18 16:37:55 +02:00
self . _types = describe_types [ ' types ' ]
2017-12-19 17:10:52 +01:00
self . Attribute = [ ]
self . Object = [ ]
self . RelatedEvent = [ ]
2018-01-03 14:36:10 +01:00
self . ShadowAttribute = [ ]
2016-09-27 19:47:22 +02:00
2017-12-21 18:46:28 +01:00
@property
def known_types ( self ) :
2017-09-18 16:37:55 +02:00
return self . _types
2016-09-26 00:26:09 +02:00
2017-12-22 14:49:14 +01:00
@property
def attributes ( self ) :
return self . Attribute
@attributes.setter
def attributes ( self , attributes ) :
if all ( isinstance ( x , MISPAttribute ) for x in attributes ) :
self . Attribute = attributes
else :
raise PyMISPError ( ' All the attributes have to be of type MISPAttribute. ' )
2018-01-03 14:36:10 +01:00
@property
def shadow_attributes ( self ) :
return self . ShadowAttribute
@shadow_attributes.setter
def shadow_attributes ( self , shadow_attributes ) :
if all ( isinstance ( x , MISPShadowAttribute ) for x in shadow_attributes ) :
self . ShadowAttribute = shadow_attributes
else :
raise PyMISPError ( ' All the attributes have to be of type MISPShadowAttribute. ' )
2017-12-22 14:49:14 +01:00
@property
def related_events ( self ) :
return self . RelatedEvent
@property
def objects ( self ) :
return self . Object
2018-01-03 14:36:10 +01:00
@objects.setter
def objects ( self , objects ) :
if all ( isinstance ( x , MISPObject ) for x in objects ) :
self . Object = objects
else :
raise PyMISPError ( ' All the attributes have to be of type MISPObject. ' )
2017-02-27 11:28:12 +01:00
def load_file ( self , event_path ) :
2017-12-22 14:49:14 +01:00
""" Load a JSON dump from a file on the disk """
2017-02-27 11:28:12 +01:00
if not os . path . exists ( event_path ) :
raise PyMISPError ( ' Invalid path, unable to load the event. ' )
with open ( event_path , ' r ' ) as f :
self . load ( f )
2016-09-26 00:26:09 +02:00
def load ( self , json_event ) :
2017-12-22 14:49:14 +01:00
""" Load a JSON dump from a pseudo file or a JSON string """
2016-10-11 17:45:38 +02:00
if hasattr ( json_event , ' read ' ) :
# python2 and python3 compatible to find if we have a file
json_event = json_event . read ( )
2016-09-28 18:50:05 +02:00
if isinstance ( json_event , basestring ) :
2016-10-11 17:45:38 +02:00
json_event = json . loads ( json_event )
if json_event . get ( ' response ' ) :
event = json_event . get ( ' response ' ) [ 0 ]
2016-09-26 00:26:09 +02:00
else :
2016-09-27 19:47:22 +02:00
event = json_event
2016-10-11 17:45:38 +02:00
if not event :
raise PyMISPError ( ' Invalid event ' )
# Invalid event created by MISP up to 2.4.52 (attribute_count is none instead of '0')
2018-01-03 14:36:10 +01:00
if ( event . get ( ' Event ' ) and
' attribute_count ' in event . get ( ' Event ' ) and
event . get ( ' Event ' ) . get ( ' attribute_count ' ) is None ) :
2016-10-11 17:45:38 +02:00
event [ ' Event ' ] [ ' attribute_count ' ] = ' 0 '
2017-09-12 16:46:06 +02:00
jsonschema . validate ( event , self . __json_schema )
2016-09-27 19:47:22 +02:00
e = event . get ( ' Event ' )
2017-12-22 14:49:14 +01:00
self . from_dict ( * * e )
2016-09-27 19:47:22 +02:00
2016-10-29 21:27:48 +02:00
def set_date ( self , date , ignore_invalid = False ) :
2017-12-22 14:49:14 +01:00
""" Set a date for the event (string, datetime, or date object) """
2016-10-29 21:27:48 +02:00
if isinstance ( date , basestring ) or isinstance ( date , unicode ) :
self . date = parse ( date ) . date ( )
elif isinstance ( date , datetime . datetime ) :
self . date = date . date ( )
elif isinstance ( date , datetime . date ) :
self . date = date
else :
if ignore_invalid :
self . date = datetime . date . today ( )
else :
raise NewEventError ( ' Invalid format for the date: {} - {} ' . format ( date , type ( date ) ) )
2017-09-12 16:46:06 +02:00
def from_dict ( self , * * kwargs ) :
2016-09-28 18:20:37 +02:00
# Required value
2017-09-12 16:46:06 +02:00
self . info = kwargs . pop ( ' info ' , None )
2018-03-26 17:03:16 +02:00
if self . info is None :
2016-09-28 18:20:37 +02:00
raise NewAttributeError ( ' The info field of the new event is required. ' )
2016-09-27 19:47:22 +02:00
# Default values for a valid event to send to a MISP instance
2017-10-28 23:09:11 +02:00
self . distribution = kwargs . pop ( ' distribution ' , None )
if self . distribution is not None :
self . distribution = int ( self . distribution )
2016-12-14 15:17:33 +01:00
if self . distribution not in [ 0 , 1 , 2 , 3 , 4 ] :
2017-09-12 16:46:06 +02:00
raise NewAttributeError ( ' {} is invalid, the distribution has to be in 0, 1, 2, 3, 4 ' . format ( self . distribution ) )
2016-09-28 18:20:37 +02:00
if kwargs . get ( ' threat_level_id ' ) is not None :
2017-09-12 16:46:06 +02:00
self . threat_level_id = int ( kwargs . pop ( ' threat_level_id ' ) )
2016-09-27 19:47:22 +02:00
if self . threat_level_id not in [ 1 , 2 , 3 , 4 ] :
raise NewEventError ( ' {} is invalid, the threat_level has to be in 1, 2, 3, 4 ' . format ( self . threat_level_id ) )
2017-09-12 16:46:06 +02:00
2016-09-28 18:20:37 +02:00
if kwargs . get ( ' analysis ' ) is not None :
2017-09-12 16:46:06 +02:00
self . analysis = int ( kwargs . pop ( ' analysis ' ) )
2016-09-27 19:47:22 +02:00
if self . analysis not in [ 0 , 1 , 2 ] :
raise NewEventError ( ' {} is invalid, the analysis has to be in 0, 1, 2 ' . format ( self . analysis ) )
2017-09-12 16:46:06 +02:00
self . published = kwargs . pop ( ' published ' , None )
if self . published is True :
2016-09-26 00:26:09 +02:00
self . publish ( )
2017-09-12 16:46:06 +02:00
else :
self . unpublish ( )
2016-09-28 18:20:37 +02:00
if kwargs . get ( ' date ' ) :
2017-09-12 16:46:06 +02:00
self . set_date ( kwargs . pop ( ' date ' ) )
2016-09-28 18:20:37 +02:00
if kwargs . get ( ' Attribute ' ) :
2017-09-12 16:46:06 +02:00
for a in kwargs . pop ( ' Attribute ' ) :
2017-12-29 14:42:49 +01:00
self . add_attribute ( * * a )
2016-09-27 19:47:22 +02:00
# All other keys
2016-09-28 18:20:37 +02:00
if kwargs . get ( ' id ' ) :
2017-09-12 16:46:06 +02:00
self . id = int ( kwargs . pop ( ' id ' ) )
2016-09-28 18:20:37 +02:00
if kwargs . get ( ' orgc_id ' ) :
2017-09-12 16:46:06 +02:00
self . orgc_id = int ( kwargs . pop ( ' orgc_id ' ) )
2016-09-28 18:20:37 +02:00
if kwargs . get ( ' org_id ' ) :
2017-09-12 16:46:06 +02:00
self . org_id = int ( kwargs . pop ( ' org_id ' ) )
2016-09-28 18:20:37 +02:00
if kwargs . get ( ' timestamp ' ) :
2018-01-03 14:36:10 +01:00
if sys . version_info > = ( 3 , 3 ) :
self . timestamp = datetime . datetime . fromtimestamp ( int ( kwargs . pop ( ' timestamp ' ) ) , datetime . timezone . utc )
else :
self . timestamp = datetime . datetime . fromtimestamp ( int ( kwargs . pop ( ' timestamp ' ) ) , UTC ( ) )
2016-09-28 18:20:37 +02:00
if kwargs . get ( ' publish_timestamp ' ) :
2018-01-03 14:36:10 +01:00
if sys . version_info > = ( 3 , 3 ) :
self . publish_timestamp = datetime . datetime . fromtimestamp ( int ( kwargs . pop ( ' publish_timestamp ' ) ) , datetime . timezone . utc )
else :
self . publish_timestamp = datetime . datetime . fromtimestamp ( int ( kwargs . pop ( ' publish_timestamp ' ) ) , UTC ( ) )
2016-09-28 18:20:37 +02:00
if kwargs . get ( ' sharing_group_id ' ) :
2017-09-12 16:46:06 +02:00
self . sharing_group_id = int ( kwargs . pop ( ' sharing_group_id ' ) )
2016-09-28 18:20:37 +02:00
if kwargs . get ( ' RelatedEvent ' ) :
2017-09-12 16:46:06 +02:00
for rel_event in kwargs . pop ( ' RelatedEvent ' ) :
2017-01-06 22:24:39 +01:00
sub_event = MISPEvent ( )
sub_event . load ( rel_event )
self . RelatedEvent . append ( sub_event )
2016-09-28 18:20:37 +02:00
if kwargs . get ( ' Tag ' ) :
2017-12-20 10:53:46 +01:00
for tag in kwargs . pop ( ' Tag ' ) :
2018-01-03 14:36:10 +01:00
self . add_tag ( tag )
2017-08-28 19:01:53 +02:00
if kwargs . get ( ' Object ' ) :
2017-09-12 16:46:06 +02:00
for obj in kwargs . pop ( ' Object ' ) :
2018-01-03 14:36:10 +01:00
self . add_object ( obj )
2016-09-27 19:47:22 +02:00
2017-12-20 12:43:31 +01:00
super ( MISPEvent , self ) . from_dict ( * * kwargs )
2016-09-27 19:47:22 +02:00
2017-12-20 12:43:31 +01:00
def to_dict ( self ) :
2017-09-12 16:46:06 +02:00
to_return = super ( MISPEvent , self ) . to_dict ( )
2017-12-20 12:43:31 +01:00
2017-09-12 16:46:06 +02:00
if to_return . get ( ' date ' ) :
2018-01-03 14:36:10 +01:00
if isinstance ( self . date , datetime . datetime ) :
self . date = self . date . date ( )
2017-09-12 16:46:06 +02:00
to_return [ ' date ' ] = self . date . isoformat ( )
2017-12-20 12:43:31 +01:00
if to_return . get ( ' publish_timestamp ' ) :
2017-12-21 18:46:28 +01:00
to_return [ ' publish_timestamp ' ] = self . _datetime_to_timestamp ( self . publish_timestamp )
2017-12-20 12:43:31 +01:00
2017-09-12 16:46:06 +02:00
to_return = _int_to_str ( to_return )
to_return = { ' Event ' : to_return }
2016-09-27 19:47:22 +02:00
return to_return
2016-09-26 00:26:09 +02:00
2018-01-03 14:36:10 +01:00
def add_proposal ( self , shadow_attribute = None , * * kwargs ) :
""" Alias for add_shadow_attribute """
2018-05-03 21:36:40 +02:00
return self . add_shadow_attribute ( shadow_attribute , * * kwargs )
2018-01-03 14:36:10 +01:00
def add_shadow_attribute ( self , shadow_attribute = None , * * kwargs ) :
""" Add a tag to the attribute (by name or a MISPTag object) """
if isinstance ( shadow_attribute , MISPShadowAttribute ) :
misp_shadow_attribute = shadow_attribute
elif isinstance ( shadow_attribute , dict ) :
misp_shadow_attribute = MISPShadowAttribute ( )
misp_shadow_attribute . from_dict ( * * shadow_attribute )
elif kwargs :
misp_shadow_attribute = MISPShadowAttribute ( )
misp_shadow_attribute . from_dict ( * * kwargs )
else :
raise PyMISPError ( " The shadow_attribute is in an invalid format (can be either string, MISPShadowAttribute, or an expanded dict): {} " . format ( shadow_attribute ) )
self . shadow_attributes . append ( misp_shadow_attribute )
self . edited = True
2018-05-03 21:36:40 +02:00
return misp_shadow_attribute
2018-01-03 14:36:10 +01:00
2017-12-20 10:53:46 +01:00
def get_attribute_tag ( self , attribute_identifier ) :
''' Return the tags associated to an attribute or an object attribute.
: attribute_identifier : can be an ID , UUID , or the value .
'''
tags = [ ]
for a in self . attributes + [ attribute for o in self . objects for attribute in o . attributes ] :
if ( ( hasattr ( a , ' id ' ) and a . id == attribute_identifier ) or
( hasattr ( a , ' uuid ' ) and a . uuid == attribute_identifier ) or
( hasattr ( a , ' value ' ) and attribute_identifier == a . value or
attribute_identifier in a . value . split ( ' | ' ) ) ) :
tags + = a . tags
return tags
2017-01-26 14:36:01 +01:00
def add_attribute_tag ( self , tag , attribute_identifier ) :
2017-12-20 10:53:46 +01:00
''' Add a tag to an existing attribute, raise an Exception if the attribute doesn ' t exists.
2018-01-03 14:36:10 +01:00
: tag : Tag name as a string , MISPTag instance , or dictionary
2017-12-20 10:53:46 +01:00
: attribute_identifier : can be an ID , UUID , or the value .
'''
2017-09-26 11:52:38 +02:00
attributes = [ ]
2018-02-16 18:35:45 +01:00
for a in self . attributes + [ attribute for o in self . objects for attribute in o . attributes ] :
2017-09-26 11:52:38 +02:00
if ( ( hasattr ( a , ' id ' ) and a . id == attribute_identifier ) or
( hasattr ( a , ' uuid ' ) and a . uuid == attribute_identifier ) or
( hasattr ( a , ' value ' ) and attribute_identifier == a . value or
attribute_identifier in a . value . split ( ' | ' ) ) ) :
2017-01-26 14:36:01 +01:00
a . add_tag ( tag )
2017-09-26 11:52:38 +02:00
attributes . append ( a )
2018-02-16 18:35:45 +01:00
2017-09-26 11:52:38 +02:00
if not attributes :
2017-01-26 14:36:01 +01:00
raise Exception ( ' No attribute with identifier {} found. ' . format ( attribute_identifier ) )
2017-12-21 18:46:28 +01:00
self . edited = True
2017-09-26 11:52:38 +02:00
return attributes
2017-01-26 14:36:01 +01:00
2016-09-26 00:26:09 +02:00
def publish ( self ) :
2017-12-22 14:49:14 +01:00
""" Mark the attribute as published """
2016-09-27 19:47:22 +02:00
self . published = True
2016-09-26 00:26:09 +02:00
def unpublish ( self ) :
2017-12-22 14:49:14 +01:00
""" Mark the attribute as un-published (set publish flag to false) """
2016-09-27 19:47:22 +02:00
self . published = False
2016-09-26 00:26:09 +02:00
2017-01-02 16:53:23 +01:00
def delete_attribute ( self , attribute_id ) :
2017-12-22 14:49:14 +01:00
""" Delete an attribute, you can search by ID or UUID """
2017-01-02 16:53:23 +01:00
found = False
for a in self . attributes :
2017-09-26 11:52:38 +02:00
if ( ( hasattr ( a , ' id ' ) and a . id == attribute_id ) or
( hasattr ( a , ' uuid ' ) and a . uuid == attribute_id ) ) :
2017-01-06 22:24:39 +01:00
a . delete ( )
2017-01-02 16:53:23 +01:00
found = True
break
if not found :
raise Exception ( ' No attribute with UUID/ID {} found. ' . format ( attribute_id ) )
2017-07-12 11:25:41 +02:00
def add_attribute ( self , type , value , * * kwargs ) :
2017-12-22 14:49:14 +01:00
""" Add an attribute. type and value are required but you can pass all
other parameters supported by MISPAttribute """
2018-05-03 20:51:04 +02:00
attr_list = [ ]
2017-07-12 11:24:21 +02:00
if isinstance ( value , list ) :
2018-05-03 20:51:04 +02:00
attr_list = [ self . add_attribute ( type = type , value = a , * * kwargs ) for a in value ]
2017-06-16 13:25:27 +02:00
else :
2017-12-21 18:46:28 +01:00
attribute = MISPAttribute ( )
2017-12-22 14:49:14 +01:00
attribute . from_dict ( type = type , value = value , * * kwargs )
2017-06-16 13:25:27 +02:00
self . attributes . append ( attribute )
2017-12-21 18:46:28 +01:00
self . edited = True
2018-05-03 20:51:04 +02:00
if attr_list :
return attr_list
else :
return attribute
2017-08-30 12:47:32 +02:00
2017-12-12 17:34:09 +01:00
def get_object_by_id ( self , object_id ) :
2017-12-22 14:49:14 +01:00
""" Get an object by ID (the ID is the one set by the server when creating the new object) """
2017-12-12 17:34:09 +01:00
for obj in self . objects :
2018-01-05 19:17:25 +01:00
if hasattr ( obj , ' id ' ) and int ( obj . id ) == int ( object_id ) :
2017-12-12 17:34:09 +01:00
return obj
2018-02-16 09:47:07 +01:00
raise InvalidMISPObject ( ' Object with {} does not exist in this event ' . format ( object_id ) )
def get_object_by_uuid ( self , object_uuid ) :
""" Get an object by UUID (UUID is set by the server when creating the new object) """
for obj in self . objects :
2018-02-19 09:16:27 +01:00
if hasattr ( obj , ' uuid ' ) and obj . uuid == object_uuid :
2018-02-16 09:47:07 +01:00
return obj
raise InvalidMISPObject ( ' Object with {} does not exist in this event ' . format ( object_uuid ) )
2017-12-12 17:34:09 +01:00
2018-01-03 14:36:10 +01:00
def add_object ( self , obj = None , * * kwargs ) :
2017-12-22 14:49:14 +01:00
""" Add an object to the Event, either by passing a MISPObject, or a dictionary """
2017-12-12 17:34:09 +01:00
if isinstance ( obj , MISPObject ) :
2018-01-03 14:36:10 +01:00
misp_obj = obj
2017-12-12 17:34:09 +01:00
elif isinstance ( obj , dict ) :
2018-01-03 14:36:10 +01:00
misp_obj = MISPObject ( name = obj . pop ( ' name ' ) , strict = obj . pop ( ' strict ' , False ) ,
default_attributes_parameters = obj . pop ( ' default_attributes_parameters ' , { } ) ,
* * obj )
misp_obj . from_dict ( * * obj )
elif kwargs :
misp_obj = MISPObject ( name = kwargs . pop ( ' name ' ) , strict = kwargs . pop ( ' strict ' , False ) ,
default_attributes_parameters = kwargs . pop ( ' default_attributes_parameters ' , { } ) ,
* * kwargs )
misp_obj . from_dict ( * * kwargs )
2017-12-12 17:34:09 +01:00
else :
raise InvalidMISPObject ( " An object to add to an existing Event needs to be either a MISPObject, or a plain python dictionary " )
2018-01-03 14:36:10 +01:00
self . Object . append ( misp_obj )
2017-12-21 18:46:28 +01:00
self . edited = True
2017-12-22 14:49:14 +01:00
def __repr__ ( self ) :
if hasattr ( self , ' info ' ) :
return ' < {self.__class__.__name__} (info= {self.info} ) ' . format ( self = self )
return ' < {self.__class__.__name__} (NotInitialized) ' . format ( self = self )
2017-12-21 18:46:28 +01:00
def _serialize ( self ) :
return ' {date} {threat_level_id} {info} {uuid} {analysis} {timestamp} ' . format (
date = self . date , threat_level_id = self . threat_level_id , info = self . info ,
uuid = self . uuid , analysis = self . analysis , timestamp = self . timestamp ) . encode ( )
2018-01-03 14:36:10 +01:00
def _serialize_sigs ( self ) : # pragma: no cover
2017-12-22 14:49:14 +01:00
# Not used
2017-12-21 18:46:28 +01:00
all_sigs = self . sig
for a in self . attributes :
all_sigs + = a . sig
return all_sigs . encode ( )
2018-01-03 14:36:10 +01:00
def sign ( self , gpg_uid , passphrase = None ) : # pragma: no cover
2017-12-22 14:49:14 +01:00
# Not used
2017-12-21 18:46:28 +01:00
if not has_pyme :
raise PyMISPError ( ' pyme is required, please install: pip install --pre pyme3. You will also need libgpg-error-dev and libgpgme11-dev. ' )
to_sign = self . _serialize ( )
with gpg . Context ( ) as c :
keys = list ( c . keylist ( gpg_uid ) )
c . signers = keys [ : 1 ]
if passphrase :
c . set_passphrase_cb ( lambda * args : passphrase )
signed , _ = c . sign ( to_sign , mode = mode . DETACH )
self . sig = base64 . b64encode ( signed ) . decode ( )
for a in self . attributes :
a . sign ( gpg_uid , passphrase )
to_sign_global = self . _serialize_sigs ( )
with gpg . Context ( ) as c :
keys = list ( c . keylist ( gpg_uid ) )
c . signers = keys [ : 1 ]
if passphrase :
c . set_passphrase_cb ( lambda * args : passphrase )
signed , _ = c . sign ( to_sign_global , mode = mode . DETACH )
self . global_sig = base64 . b64encode ( signed ) . decode ( )
2018-01-03 14:36:10 +01:00
def verify ( self , gpg_uid ) : # pragma: no cover
2017-12-22 14:49:14 +01:00
# Not used
2017-12-21 18:46:28 +01:00
if not has_pyme :
raise PyMISPError ( ' pyme is required, please install: pip install --pre pyme3. You will also need libgpg-error-dev and libgpgme11-dev. ' )
to_return = { }
signed_data = self . _serialize ( )
with gpg . Context ( ) as c :
keys = list ( c . keylist ( gpg_uid ) )
try :
c . verify ( signed_data , signature = base64 . b64decode ( self . sig ) , verify = keys [ : 1 ] )
to_return [ self . uuid ] = True
except Exception :
to_return [ self . uuid ] = False
for a in self . attributes :
to_return . update ( a . verify ( gpg_uid ) )
to_verify_global = self . _serialize_sigs ( )
with gpg . Context ( ) as c :
keys = list ( c . keylist ( gpg_uid ) )
try :
c . verify ( to_verify_global , signature = base64 . b64decode ( self . global_sig ) , verify = keys [ : 1 ] )
to_return [ ' global ' ] = True
except Exception :
to_return [ ' global ' ] = False
return to_return
2017-12-22 14:49:14 +01:00
@deprecated
2018-01-03 14:36:10 +01:00
def get_known_types ( self ) : # pragma: no cover
2017-12-21 18:46:28 +01:00
return self . known_types
2017-12-22 14:49:14 +01:00
@deprecated
2018-01-03 14:36:10 +01:00
def set_all_values ( self , * * kwargs ) : # pragma: no cover
2017-12-21 18:46:28 +01:00
self . from_dict ( * * kwargs )
2017-12-22 14:49:14 +01:00
@deprecated
2018-01-03 14:36:10 +01:00
def _json ( self ) : # pragma: no cover
2017-12-21 18:46:28 +01:00
return self . to_dict ( )
2017-12-12 17:34:09 +01:00
2017-08-30 12:47:32 +02:00
class MISPObjectReference ( AbstractMISP ) :
def __init__ ( self ) :
super ( MISPObjectReference , self ) . __init__ ( )
2017-09-07 16:09:45 +02:00
def from_dict ( self , object_uuid , referenced_uuid , relationship_type , comment = None , * * kwargs ) :
self . object_uuid = object_uuid
2017-09-07 14:01:13 +02:00
self . referenced_uuid = referenced_uuid
2017-08-30 12:47:32 +02:00
self . relationship_type = relationship_type
self . comment = comment
2017-12-20 12:43:31 +01:00
super ( MISPObjectReference , self ) . from_dict ( * * kwargs )
2017-08-30 12:47:32 +02:00
2017-12-22 14:49:14 +01:00
def __repr__ ( self ) :
if hasattr ( self , ' referenced_uuid ' ) :
return ' < {self.__class__.__name__} (object_uuid= {self.object_uuid} , referenced_uuid= {self.referenced_uuid} , relationship_type= {self.relationship_type} ) ' . format ( self = self )
return ' < {self.__class__.__name__} (NotInitialized) ' . format ( self = self )
2017-08-30 12:47:32 +02:00
2017-11-17 16:51:46 +01:00
class MISPUser ( AbstractMISP ) :
def __init__ ( self ) :
super ( MISPUser , self ) . __init__ ( )
class MISPOrganisation ( AbstractMISP ) :
def __init__ ( self ) :
super ( MISPOrganisation , self ) . __init__ ( )
2018-04-25 11:06:03 +02:00
class MISPFeed ( AbstractMISP ) :
def __init__ ( self ) :
super ( MISPFeed , self ) . __init__ ( )
2017-12-26 17:13:57 +01:00
class MISPSighting ( AbstractMISP ) :
def __init__ ( self ) :
super ( MISPSighting , self ) . __init__ ( )
2018-01-25 16:24:24 +01:00
def from_dict ( self , value = None , uuid = None , id = None , source = None , type = None , timestamp = None , * * kwargs ) :
2017-12-26 17:13:57 +01:00
""" Initialize the MISPSighting from a dictionary
2018-01-25 16:24:24 +01:00
: value : Value of the attribute the sighting is related too . Pushing this object
will update the sighting count of each attriutes with thifs value on the instance
: uuid : UUID of the attribute to update
: id : ID of the attriute to update
2017-12-26 17:13:57 +01:00
: source : Source of the sighting
: type : Type of the sighting
: timestamp : Timestamp associated to the sighting
"""
self . value = value
2018-01-25 16:24:24 +01:00
self . uuid = uuid
self . id = id
2017-12-26 17:13:57 +01:00
self . source = source
self . type = type
self . timestamp = timestamp
super ( MISPSighting , self ) . from_dict ( * * kwargs )
def __repr__ ( self ) :
if hasattr ( self , ' value ' ) :
return ' < {self.__class__.__name__} (value= {self.value} ) ' . format ( self = self )
2018-01-25 16:24:24 +01:00
if hasattr ( self , ' id ' ) :
return ' < {self.__class__.__name__} (value= {self.id} ) ' . format ( self = self )
if hasattr ( self , ' uuid ' ) :
return ' < {self.__class__.__name__} (value= {self.uuid} ) ' . format ( self = self )
2017-12-26 17:13:57 +01:00
return ' < {self.__class__.__name__} (NotInitialized) ' . format ( self = self )
2017-09-12 16:46:06 +02:00
class MISPObjectAttribute ( MISPAttribute ) :
2017-08-30 12:47:32 +02:00
def __init__ ( self , definition ) :
2017-09-18 16:37:55 +02:00
super ( MISPObjectAttribute , self ) . __init__ ( )
2018-03-27 14:57:07 +02:00
self . _definition = definition
2017-08-30 12:47:32 +02:00
def from_dict ( self , object_relation , value , * * kwargs ) :
self . object_relation = object_relation
self . value = value
# Initialize the new MISPAttribute
# Get the misp attribute type from the definition
self . type = kwargs . pop ( ' type ' , None )
if self . type is None :
2018-03-27 14:57:07 +02:00
self . type = self . _definition . get ( ' misp-attribute ' )
2018-01-05 11:34:08 +01:00
self . disable_correlation = kwargs . pop ( ' disable_correlation ' , None )
2017-08-30 12:47:32 +02:00
if self . disable_correlation is None :
# The correlation can be disabled by default in the object definition.
# Use this value if it isn't overloaded by the object
2018-03-27 14:57:07 +02:00
self . disable_correlation = self . _definition . get ( ' disable_correlation ' )
2017-08-30 12:47:32 +02:00
self . to_ids = kwargs . pop ( ' to_ids ' , None )
if self . to_ids is None :
# Same for the to_ids flag
2018-03-27 14:57:07 +02:00
self . to_ids = self . _definition . get ( ' to_ids ' )
2017-12-29 16:44:50 +01:00
super ( MISPObjectAttribute , self ) . from_dict ( * * dict ( self , * * kwargs ) )
2017-08-30 12:47:32 +02:00
2017-12-22 14:49:14 +01:00
def __repr__ ( self ) :
if hasattr ( self , ' value ' ) :
return ' < {self.__class__.__name__} (object_relation= {self.object_relation} , value= {self.value} ) ' . format ( self = self )
return ' < {self.__class__.__name__} (NotInitialized) ' . format ( self = self )
2017-08-30 12:47:32 +02:00
2018-01-03 14:36:10 +01:00
class MISPShadowAttribute ( MISPAttribute ) :
def __init__ ( self ) :
super ( MISPShadowAttribute , self ) . __init__ ( )
2017-08-30 12:47:32 +02:00
class MISPObject ( AbstractMISP ) :
2017-12-20 13:29:05 +01:00
def __init__ ( self , name , strict = False , standalone = False , default_attributes_parameters = { } , * * kwargs ) :
2017-12-12 17:34:09 +01:00
''' Master class representing a generic MISP object
: name : Name of the object
: strict : Enforce validation with the object templates
: standalone : The object will be pushed as directly on MISP , not as a part of an event .
In this case the ObjectReference needs to be pushed manually and cannot be in the JSON dump .
2017-12-20 13:29:05 +01:00
: default_attributes_parameters : Used as template for the attributes if they are not overwritten in add_attribute
2018-01-12 00:35:57 +01:00
: misp_objects_path_custom : Path to custom object templates
2017-12-12 17:34:09 +01:00
'''
super ( MISPObject , self ) . __init__ ( * * kwargs )
2018-03-27 14:57:07 +02:00
self . _strict = strict
2017-08-30 12:47:32 +02:00
self . name = name
2018-01-12 00:35:57 +01:00
misp_objects_path = os . path . join (
os . path . abspath ( os . path . dirname ( sys . modules [ ' pymisp ' ] . __file__ ) ) ,
' data ' , ' misp-objects ' , ' objects ' )
misp_objects_path_custom = kwargs . get ( ' misp_objects_path_custom ' )
if misp_objects_path_custom and os . path . exists ( os . path . join ( misp_objects_path_custom , self . name , ' definition.json ' ) ) :
# Use the local object path by default if provided (allows to overwrite a default template)
template_path = os . path . join ( misp_objects_path_custom , self . name , ' definition.json ' )
2018-03-27 14:57:07 +02:00
self . _known_template = True
2018-01-12 00:35:57 +01:00
elif os . path . exists ( os . path . join ( misp_objects_path , self . name , ' definition.json ' ) ) :
template_path = os . path . join ( misp_objects_path , self . name , ' definition.json ' )
2018-03-27 14:57:07 +02:00
self . _known_template = True
2017-08-30 12:47:32 +02:00
else :
2018-03-27 14:57:07 +02:00
if self . _strict :
2017-09-22 19:43:20 +02:00
raise UnknownMISPObjectTemplate ( ' {} is unknown in the MISP object directory. ' . format ( self . name ) )
2017-08-30 12:47:32 +02:00
else :
2018-03-27 14:57:07 +02:00
self . _known_template = False
if self . _known_template :
2018-01-12 00:35:57 +01:00
with open ( template_path , ' r ' ) as f :
2018-03-27 14:57:07 +02:00
self . _definition = json . load ( f )
setattr ( self , ' meta-category ' , self . _definition [ ' meta-category ' ] )
self . template_uuid = self . _definition [ ' uuid ' ]
self . description = self . _definition [ ' description ' ]
self . template_version = self . _definition [ ' version ' ]
2017-08-30 12:47:32 +02:00
else :
2017-12-12 17:34:09 +01:00
# Then we have no meta-category, template_uuid, description and template_version
2017-08-30 12:47:32 +02:00
pass
self . uuid = str ( uuid . uuid4 ( ) )
2018-01-22 10:33:34 +01:00
self . __fast_attribute_access = defaultdict ( list ) # Hashtable object_relation: [attributes]
2017-12-29 14:42:49 +01:00
self . ObjectReference = [ ]
2017-12-21 18:46:28 +01:00
self . Attribute = [ ]
2018-01-03 14:36:10 +01:00
if isinstance ( default_attributes_parameters , MISPAttribute ) :
# Just make sure we're not modifying an existing MISPAttribute
self . _default_attributes_parameters = default_attributes_parameters . to_dict ( )
else :
self . _default_attributes_parameters = default_attributes_parameters
2017-12-20 13:29:05 +01:00
if self . _default_attributes_parameters :
2017-12-12 17:34:09 +01:00
# Let's clean that up
2017-12-20 13:29:05 +01:00
self . _default_attributes_parameters . pop ( ' value ' , None ) # duh
self . _default_attributes_parameters . pop ( ' uuid ' , None ) # duh
self . _default_attributes_parameters . pop ( ' id ' , None ) # duh
self . _default_attributes_parameters . pop ( ' object_id ' , None ) # duh
self . _default_attributes_parameters . pop ( ' type ' , None ) # depends on the value
self . _default_attributes_parameters . pop ( ' object_relation ' , None ) # depends on the value
self . _default_attributes_parameters . pop ( ' disable_correlation ' , None ) # depends on the value
self . _default_attributes_parameters . pop ( ' to_ids ' , None ) # depends on the value
self . _default_attributes_parameters . pop ( ' deleted ' , None ) # doesn't make sense to pre-set it
self . _default_attributes_parameters . pop ( ' data ' , None ) # in case the original in a sample or an attachment
2018-01-04 12:23:32 +01:00
# Those values are set for the current object, if they exist, but not pop'd because they are still useful for the attributes
self . distribution = self . _default_attributes_parameters . get ( ' distribution ' , 5 )
self . sharing_group_id = self . _default_attributes_parameters . get ( ' sharing_group_id ' , 0 )
2017-12-20 13:29:05 +01:00
else :
2017-12-22 14:49:14 +01:00
self . distribution = 5 # Default to inherit
2018-01-04 12:23:32 +01:00
self . sharing_group_id = 0
2017-12-12 17:34:09 +01:00
self . _standalone = standalone
if self . _standalone :
# Mark as non_jsonable because we need to add the references manually after the object(s) have been created
self . update_not_jsonable ( ' ObjectReference ' )
2017-08-30 12:47:32 +02:00
2017-12-22 14:49:14 +01:00
@property
def attributes ( self ) :
return self . Attribute
2018-01-12 16:15:09 +01:00
@attributes.setter
def attributes ( self , attributes ) :
if all ( isinstance ( x , MISPObjectAttribute ) for x in attributes ) :
self . Attribute = attributes
2018-01-22 10:33:34 +01:00
self . __fast_attribute_access = defaultdict ( list )
2018-01-12 16:15:09 +01:00
else :
raise PyMISPError ( ' All the attributes have to be of type MISPObjectAttribute. ' )
2017-12-22 14:49:14 +01:00
@property
def references ( self ) :
return self . ObjectReference
2017-12-14 16:12:54 +01:00
2018-01-12 16:15:09 +01:00
@references.setter
def references ( self , references ) :
if all ( isinstance ( x , MISPObjectReference ) for x in references ) :
self . ObjectReference = references
else :
raise PyMISPError ( ' All the attributes have to be of type MISPObjectReference. ' )
2017-08-30 12:47:32 +02:00
def from_dict ( self , * * kwargs ) :
2018-03-27 14:57:07 +02:00
if self . _known_template :
2017-08-30 12:47:32 +02:00
if kwargs . get ( ' template_uuid ' ) and kwargs [ ' template_uuid ' ] != self . template_uuid :
2018-03-27 14:57:07 +02:00
if self . _strict :
2017-08-30 12:47:32 +02:00
raise UnknownMISPObjectTemplate ( ' UUID of the object is different from the one of the template. ' )
else :
2018-03-27 14:57:07 +02:00
self . _known_template = False
2017-08-30 12:47:32 +02:00
if kwargs . get ( ' template_version ' ) and int ( kwargs [ ' template_version ' ] ) != self . template_version :
2018-03-27 14:57:07 +02:00
if self . _strict :
2017-08-30 12:47:32 +02:00
raise UnknownMISPObjectTemplate ( ' Version of the object ( {} ) is different from the one of the template ( {} ). ' . format ( kwargs [ ' template_version ' ] , self . template_version ) )
else :
2018-03-27 14:57:07 +02:00
self . _known_template = False
2017-08-30 12:47:32 +02:00
2017-12-20 12:43:31 +01:00
if kwargs . get ( ' Attribute ' ) :
for a in kwargs . pop ( ' Attribute ' ) :
self . add_attribute ( * * a )
if kwargs . get ( ' ObjectReference ' ) :
for r in kwargs . pop ( ' ObjectReference ' ) :
self . add_reference ( * * r )
2018-01-03 14:36:10 +01:00
# Not supported yet - https://github.com/MISP/PyMISP/issues/168
# if kwargs.get('Tag'):
# for tag in kwargs.pop('Tag'):
# self.add_tag(tag)
2017-12-20 12:43:31 +01:00
super ( MISPObject , self ) . from_dict ( * * kwargs )
2017-08-30 12:47:32 +02:00
2017-09-07 14:01:13 +02:00
def add_reference ( self , referenced_uuid , relationship_type , comment = None , * * kwargs ) :
2017-08-30 12:47:32 +02:00
""" Add a link (uuid) to an other object """
2017-09-07 16:09:45 +02:00
if kwargs . get ( ' object_uuid ' ) :
2017-08-30 12:47:32 +02:00
# Load existing object
2017-10-22 18:17:48 +02:00
object_uuid = kwargs . pop ( ' object_uuid ' )
2017-08-30 12:47:32 +02:00
else :
# New reference
2017-09-07 16:09:45 +02:00
object_uuid = self . uuid
2017-08-30 12:47:32 +02:00
reference = MISPObjectReference ( )
2017-09-07 16:09:45 +02:00
reference . from_dict ( object_uuid = object_uuid , referenced_uuid = referenced_uuid ,
2017-08-30 12:47:32 +02:00
relationship_type = relationship_type , comment = comment , * * kwargs )
self . ObjectReference . append ( reference )
2017-12-21 18:46:28 +01:00
self . edited = True
2017-08-30 12:47:32 +02:00
2017-12-12 17:34:09 +01:00
def get_attributes_by_relation ( self , object_relation ) :
''' Returns the list of attributes with the given object relation in the object '''
2018-01-12 16:15:09 +01:00
return self . _fast_attribute_access . get ( object_relation , [ ] )
@property
def _fast_attribute_access ( self ) :
if not self . __fast_attribute_access :
for a in self . attributes :
self . __fast_attribute_access [ a . object_relation ] . append ( a )
return self . __fast_attribute_access
2017-12-12 17:34:09 +01:00
def has_attributes_by_relation ( self , list_of_relations ) :
''' True if all the relations in the list are defined in the object '''
2018-01-12 16:15:09 +01:00
return all ( relation in self . _fast_attribute_access for relation in list_of_relations )
2017-12-12 17:34:09 +01:00
2017-08-30 12:47:32 +02:00
def add_attribute ( self , object_relation , * * value ) :
2017-12-22 14:49:14 +01:00
""" Add an attribute. object_relation is required and the value key is a
dictionary with all the keys supported by MISPAttribute """
2017-08-30 12:47:32 +02:00
if value . get ( ' value ' ) is None :
return None
2018-03-27 14:57:07 +02:00
if self . _known_template :
if self . _definition [ ' attributes ' ] . get ( object_relation ) :
attribute = MISPObjectAttribute ( self . _definition [ ' attributes ' ] [ object_relation ] )
2017-11-02 17:57:53 +01:00
else :
# Woopsie, this object_relation is unknown, no sane defaults for you.
2017-11-15 17:37:17 +01:00
logger . warning ( " The template ( {} ) doesn ' t have the object_relation ( {} ) you ' re trying to add. " . format ( self . name , object_relation ) )
2017-11-02 17:57:53 +01:00
attribute = MISPObjectAttribute ( { } )
2017-08-30 12:47:32 +02:00
else :
attribute = MISPObjectAttribute ( { } )
2017-12-20 13:29:05 +01:00
# Overwrite the parameters of self._default_attributes_parameters with the ones of value
attribute . from_dict ( object_relation = object_relation , * * dict ( self . _default_attributes_parameters , * * value ) )
2017-12-12 17:34:09 +01:00
self . __fast_attribute_access [ object_relation ] . append ( attribute )
2018-01-12 16:15:09 +01:00
self . Attribute . append ( attribute )
2017-12-21 18:46:28 +01:00
self . edited = True
2017-08-30 12:47:32 +02:00
return attribute
2017-12-22 14:49:14 +01:00
def to_dict ( self , strict = False ) :
2018-03-27 14:57:07 +02:00
if strict or self . _strict and self . _known_template :
2017-12-22 14:49:14 +01:00
self . _validate ( )
return super ( MISPObject , self ) . to_dict ( )
def to_json ( self , strict = False ) :
2018-03-27 14:57:07 +02:00
if strict or self . _strict and self . _known_template :
2017-12-22 14:49:14 +01:00
self . _validate ( )
return super ( MISPObject , self ) . to_json ( )
def _validate ( self ) :
""" Make sure the object we ' re creating has the required fields """
2018-03-27 14:57:07 +02:00
if self . _definition . get ( ' required ' ) :
required_missing = set ( self . _definition . get ( ' required ' ) ) - set ( self . _fast_attribute_access . keys ( ) )
2018-01-12 00:35:57 +01:00
if required_missing :
raise InvalidMISPObject ( ' {} are required. ' . format ( required_missing ) )
2018-03-27 14:57:07 +02:00
if self . _definition . get ( ' requiredOneOf ' ) :
if not set ( self . _definition [ ' requiredOneOf ' ] ) & set ( self . _fast_attribute_access . keys ( ) ) :
2018-01-12 00:35:57 +01:00
# We ecpect at least one of the object_relation in requiredOneOf, and it isn't the case
2018-03-27 14:57:07 +02:00
raise InvalidMISPObject ( ' At least one of the following attributes is required: {} ' . format ( ' , ' . join ( self . _definition [ ' requiredOneOf ' ] ) ) )
2018-01-12 16:15:09 +01:00
for rel , attrs in self . _fast_attribute_access . items ( ) :
2018-01-12 00:35:57 +01:00
if len ( attrs ) == 1 :
# object_relation's here only once, everything's cool, moving on
continue
2018-03-27 14:57:07 +02:00
if not self . _definition [ ' attributes ' ] [ rel ] . get ( ' multiple ' ) :
2018-01-12 00:35:57 +01:00
# object_relation's here more than once, but it isn't allowed in the template.
raise InvalidMISPObject ( ' Multiple occurrences of {} is not allowed ' . format ( rel ) )
2017-12-22 14:49:14 +01:00
return True
def __repr__ ( self ) :
if hasattr ( self , ' name ' ) :
return ' < {self.__class__.__name__} (name= {self.name} ) ' . format ( self = self )
return ' < {self.__class__.__name__} (NotInitialized) ' . format ( self = self )