2024-01-17 13:13:14 +01:00
from __future__ import annotations
2020-01-27 19:07:40 +01:00
from datetime import timezone , datetime , date
2020-09-15 17:01:56 +02:00
import copy
2016-09-27 19:47:22 +02:00
import os
2016-11-17 17:07:29 +01:00
import base64
2020-01-27 20:14:14 +01:00
import sys
2020-11-25 13:34:13 +01:00
from io import BytesIO , BufferedIOBase , TextIOBase
2016-12-07 18:37:35 +01:00
from zipfile import ZipFile
2017-08-30 12:47:32 +02:00
import uuid
2021-01-30 16:34:29 +01:00
from uuid import UUID
2018-01-22 10:33:34 +01:00
from collections import defaultdict
2019-07-18 14:05:08 +02:00
import logging
2019-11-19 15:53:58 +01:00
import hashlib
2019-12-18 14:45:14 +01:00
from pathlib import Path
2024-01-17 13:13:14 +01:00
from typing import IO , Any
2024-01-04 13:37:24 +01:00
import warnings
2017-08-30 12:47:32 +02:00
2024-01-05 21:18:44 +01:00
try :
# orjson is optional dependency that speedups parsing and encoding JSON
import orjson as json # type: ignore
except ImportError :
import json
2019-12-18 14:45:14 +01:00
from . abstract import AbstractMISP , MISPTag
2022-12-01 10:05:38 +01:00
from . exceptions import ( UnknownMISPObjectTemplate , InvalidMISPGalaxy , InvalidMISPObject ,
PyMISPError , NewEventError , NewAttributeError , NewEventReportError ,
NewGalaxyClusterError , NewGalaxyClusterRelationError )
2017-08-30 12:47:32 +02:00
2017-11-08 03:10:04 +01:00
logger = logging . getLogger ( ' pymisp ' )
2017-08-30 12:47:32 +02:00
2018-08-08 11:19:24 +02:00
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
2016-09-26 00:26:09 +02:00
2024-01-30 12:51:23 +01:00
def _make_datetime ( value : int | float | str | datetime | date ) - > datetime :
2020-01-27 20:14:14 +01:00
if isinstance ( value , ( int , float ) ) :
# Timestamp
value = datetime . fromtimestamp ( value )
elif isinstance ( value , str ) :
2024-01-17 13:13:14 +01:00
try :
# faster
value = datetime . fromisoformat ( value )
except Exception :
2024-01-30 12:51:23 +01:00
value = parse ( value ) # type: ignore[arg-type]
2020-01-27 20:14:14 +01:00
elif isinstance ( value , datetime ) :
pass
2020-01-28 14:12:39 +01:00
elif isinstance ( value , date ) : # NOTE: date has to be *after* datetime, or it will be overwritten
value = datetime . combine ( value , datetime . min . time ( ) )
2020-01-27 20:14:14 +01:00
else :
2020-01-28 14:12:39 +01:00
raise PyMISPError ( f ' Invalid format for { value } : { type ( value ) } . ' )
2020-01-27 20:14:14 +01:00
if not value . tzinfo :
# set localtimezone if not present
value = value . astimezone ( )
return value
2017-09-12 16:46:06 +02:00
2024-01-30 12:51:23 +01:00
def make_bool ( value : bool | int | str | dict [ str , Any ] | list [ Any ] | None ) - > bool :
2020-10-27 16:14:06 +01:00
""" Converts the supplied value to a boolean.
: param value : Value to interpret as a boolean . An empty string , dict
or list is False ; value None is also False .
"""
2019-03-15 10:49:10 +01:00
if isinstance ( value , bool ) :
return value
if isinstance ( value , int ) :
return bool ( value )
2019-03-20 10:44:12 +01:00
if not value : # None, 0, '', {}, []
return False
2019-03-15 10:49:10 +01:00
if isinstance ( value , str ) :
if value == ' 0 ' :
return False
return True
else :
2024-01-17 13:13:14 +01:00
raise PyMISPError ( f ' Unable to convert { value } to a boolean. ' )
2019-03-15 10:49:10 +01:00
2019-12-18 14:45:14 +01:00
class MISPOrganisation ( AbstractMISP ) :
2024-01-30 12:51:23 +01:00
_fields_for_feed : set [ str ] = { ' name ' , ' uuid ' }
2019-12-18 14:45:14 +01:00
2022-11-09 13:29:06 +01:00
def __init__ ( self ) - > None :
2020-05-07 12:17:31 +02:00
super ( ) . __init__ ( )
self . id : int
2021-03-05 12:11:00 +01:00
self . name : str
2020-05-07 12:17:31 +02:00
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
if ' Organisation ' in kwargs :
kwargs = kwargs [ ' Organisation ' ]
2024-01-17 13:13:14 +01:00
super ( ) . from_dict ( * * kwargs )
2019-12-18 14:45:14 +01:00
2021-03-05 11:58:58 +01:00
def __repr__ ( self ) - > str :
if hasattr ( self , ' name ' ) :
return f ' < { self . __class__ . __name__ } (type= { self . name } ) '
return f ' < { self . __class__ . __name__ } (NotInitialized) '
2020-01-28 14:12:39 +01:00
2021-11-30 10:38:41 +01:00
class MISPSharingGroupOrg ( AbstractMISP ) :
2024-01-30 12:51:23 +01:00
_fields_for_feed : set [ str ] = { ' extend ' , ' Organisation ' }
2021-11-30 10:38:41 +01:00
2022-11-09 13:29:06 +01:00
def __init__ ( self ) - > None :
2021-11-30 10:38:41 +01:00
super ( ) . __init__ ( )
self . extend : bool
self . Organisation : MISPOrganisation
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2021-11-30 10:38:41 +01:00
if ' SharingGroupOrg ' in kwargs :
kwargs = kwargs [ ' SharingGroupOrg ' ]
if ' Organisation ' in kwargs :
self . Organisation = MISPOrganisation ( )
self . Organisation . from_dict ( * * kwargs . pop ( ' Organisation ' ) )
super ( ) . from_dict ( * * kwargs )
def __repr__ ( self ) - > str :
if hasattr ( self , ' Organisation ' ) and hasattr ( self , ' extend ' ) :
return f ' < { self . __class__ . __name__ } (Org= { self . Organisation . name } , extend= { self . extend } ) '
return f ' < { self . __class__ . __name__ } (NotInitialized) '
2024-01-30 12:51:23 +01:00
def _to_feed ( self ) - > dict [ str , Any ] :
2021-11-30 10:38:41 +01:00
to_return = super ( ) . _to_feed ( )
to_return [ ' Organisation ' ] = self . Organisation . _to_feed ( )
return to_return
2020-01-23 10:27:40 +01:00
class MISPSharingGroup ( AbstractMISP ) :
2024-01-30 12:51:23 +01:00
_fields_for_feed : set [ str ] = { ' uuid ' , ' name ' , ' roaming ' , ' created ' , ' organisation_uuid ' , ' Organisation ' , ' SharingGroupOrg ' , ' SharingGroupServer ' }
2021-11-29 15:54:34 +01:00
2022-11-09 13:29:06 +01:00
def __init__ ( self ) - > None :
2021-10-04 12:41:36 +02:00
super ( ) . __init__ ( )
2021-10-04 12:56:13 +02:00
self . name : str
2024-01-17 13:13:14 +01:00
self . SharingGroupOrg : list [ MISPSharingGroupOrg ] = [ ]
2021-10-04 12:41:36 +02:00
@property
2024-01-17 13:13:14 +01:00
def sgorgs ( self ) - > list [ MISPSharingGroupOrg ] :
2021-10-04 12:41:36 +02:00
return self . SharingGroupOrg
2021-11-30 10:38:41 +01:00
@sgorgs.setter
2024-01-30 12:51:23 +01:00
def sgorgs ( self , sgorgs : list [ MISPSharingGroupOrg ] ) - > None :
2021-11-30 10:38:41 +01:00
if all ( isinstance ( x , MISPSharingGroupOrg ) for x in sgorgs ) :
self . SharingGroupOrg = sgorgs
2021-10-04 12:41:36 +02:00
else :
2021-11-30 10:38:41 +01:00
raise PyMISPError ( ' All the attributes have to be of type MISPSharingGroupOrg. ' )
2021-10-04 12:41:36 +02:00
2024-01-30 12:51:23 +01:00
def add_sgorg ( self , sgorg : dict [ str , Any ] ) - > MISPSharingGroupOrg :
2021-11-30 10:38:41 +01:00
misp_sgorg = MISPSharingGroupOrg ( )
misp_sgorg . from_dict ( * * sgorg )
self . SharingGroupOrg . append ( misp_sgorg )
return misp_sgorg
2020-01-23 10:27:40 +01:00
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2021-10-04 12:41:36 +02:00
if ' SharingGroupOrg ' in kwargs :
2021-11-30 10:38:41 +01:00
[ self . add_sgorg ( sgorg ) for sgorg in kwargs . pop ( ' SharingGroupOrg ' ) ]
2020-01-23 10:27:40 +01:00
if ' SharingGroup ' in kwargs :
kwargs = kwargs [ ' SharingGroup ' ]
super ( ) . from_dict ( * * kwargs )
2021-10-04 12:41:36 +02:00
def __repr__ ( self ) - > str :
if hasattr ( self , ' name ' ) :
2021-11-30 11:41:53 +01:00
return f ' < { self . __class__ . __name__ } (name= { self . name } )> '
return f ' < { self . __class__ . __name__ } (NotInitialized)> '
2021-10-04 12:41:36 +02:00
2024-01-30 12:51:23 +01:00
def _to_feed ( self ) - > dict [ str , Any ] :
2021-11-29 15:54:34 +01:00
to_return = super ( ) . _to_feed ( )
2021-11-30 10:38:41 +01:00
to_return [ ' SharingGroupOrg ' ] = [ sgorg . _to_feed ( ) for sgorg in self . SharingGroupOrg ]
2021-11-29 15:54:34 +01:00
to_return [ ' Organisation ' ] . pop ( ' id ' , None )
for server in to_return [ ' SharingGroupServer ' ] :
server . pop ( ' id ' , None )
server . pop ( ' sharing_group_id ' , None )
server . pop ( ' server_id ' , None )
server [ ' Server ' ] . pop ( ' id ' , None )
return to_return
2020-01-23 10:27:40 +01:00
2021-11-29 16:35:12 +01:00
2019-12-18 14:45:14 +01:00
class MISPShadowAttribute ( AbstractMISP ) :
2022-11-09 13:29:06 +01:00
def __init__ ( self ) - > None :
2020-01-27 19:07:40 +01:00
super ( ) . __init__ ( )
self . type : str
self . value : str
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
if ' ShadowAttribute ' in kwargs :
kwargs = kwargs [ ' ShadowAttribute ' ]
super ( ) . from_dict ( * * kwargs )
def __repr__ ( self ) - > str :
if hasattr ( self , ' value ' ) :
2020-01-27 19:07:40 +01:00
return f ' < { self . __class__ . __name__ } (type= { self . type } , value= { self . value } ) '
2020-01-02 15:55:00 +01:00
return f ' < { self . __class__ . __name__ } (NotInitialized) '
2019-12-18 14:45:14 +01:00
class MISPSighting ( AbstractMISP ) :
2022-11-09 13:29:06 +01:00
def __init__ ( self ) - > None :
2020-01-27 19:07:40 +01:00
super ( ) . __init__ ( )
self . id : int
self . value : str
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
""" Initialize the MISPSighting from a dictionary
2020-10-27 16:14:06 +01:00
: param value : Value of the attribute the sighting is related too . Pushing this object
will update the sighting count of each attribute with this value on the instance .
: param uuid : UUID of the attribute to update
: param id : ID of the attriute to update
: param source : Source of the sighting
: param type : Type of the sighting
: param timestamp : Timestamp associated to the sighting
2019-12-18 14:45:14 +01:00
"""
if ' Sighting ' in kwargs :
kwargs = kwargs [ ' Sighting ' ]
2024-01-17 13:13:14 +01:00
super ( ) . from_dict ( * * kwargs )
2019-12-18 14:45:14 +01:00
def __repr__ ( self ) - > str :
if hasattr ( self , ' value ' ) :
2020-01-27 19:07:40 +01:00
return ' < {self.__class__.__name__} (value= {self.value} ) ' . format ( self = self )
2019-12-18 14:45:14 +01:00
if hasattr ( self , ' id ' ) :
2020-01-27 19:07:40 +01:00
return ' < {self.__class__.__name__} (id= {self.id} ) ' . format ( self = self )
2019-12-18 14:45:14 +01:00
if hasattr ( self , ' uuid ' ) :
2020-01-27 19:07:40 +01:00
return ' < {self.__class__.__name__} (uuid= {self.uuid} ) ' . format ( self = self )
2024-01-17 13:13:14 +01:00
return f ' < { self . __class__ . __name__ } (NotInitialized) '
2019-12-18 14:45:14 +01:00
2022-12-01 12:06:57 +01:00
class MISPAttribute ( AbstractMISP ) :
2024-01-30 12:51:23 +01:00
_fields_for_feed : set [ str ] = { ' uuid ' , ' value ' , ' category ' , ' type ' , ' comment ' , ' data ' ,
' deleted ' , ' timestamp ' , ' to_ids ' , ' disable_correlation ' ,
' first_seen ' , ' last_seen ' }
2022-12-01 10:05:38 +01:00
2024-01-30 12:51:23 +01:00
def __init__ ( self , describe_types : dict [ str , Any ] | None = None , strict : bool = False ) :
2022-12-01 12:06:57 +01:00
""" Represents an Attribute
2022-12-01 10:05:38 +01:00
2022-12-01 12:06:57 +01:00
: param describe_types : Use it if you want to overwrite the default describeTypes . json file ( you don ' t)
: param strict : If false , fallback to sane defaults for the attribute type if the ones passed by the user are incorrect
"""
2019-12-18 14:45:14 +01:00
super ( ) . __init__ ( )
2022-12-01 12:06:57 +01:00
if describe_types :
2024-01-17 13:13:14 +01:00
self . describe_types : dict [ str , Any ] = describe_types
self . __categories : list [ str ] = self . describe_types [ ' categories ' ]
self . __category_type_mapping : dict [ str , list [ str ] ] = self . describe_types [ ' category_type_mappings ' ]
self . __sane_default : dict [ str , dict [ str , str | int ] ] = self . describe_types [ ' sane_defaults ' ]
2022-12-01 12:06:57 +01:00
self . __strict : bool = strict
2024-01-17 13:13:14 +01:00
self . data : BytesIO | None = None
2022-12-01 12:06:57 +01:00
self . first_seen : datetime
self . last_seen : datetime
self . uuid : str = str ( uuid . uuid4 ( ) )
2024-01-17 13:13:14 +01:00
self . ShadowAttribute : list [ MISPShadowAttribute ] = [ ]
2022-12-01 12:06:57 +01:00
self . SharingGroup : MISPSharingGroup
2024-01-17 13:13:14 +01:00
self . Sighting : list [ MISPSighting ] = [ ]
self . Tag : list [ MISPTag ] = [ ]
self . Galaxy : list [ MISPGalaxy ] = [ ]
2020-01-23 10:27:40 +01:00
2024-01-30 12:51:23 +01:00
self . expand : str
self . timestamp : float | int | datetime
2022-12-01 12:06:57 +01:00
# For search
self . Event : MISPEvent
2024-01-17 13:13:14 +01:00
self . RelatedAttribute : list [ MISPAttribute ]
2022-12-01 10:05:38 +01:00
2022-12-01 12:06:57 +01:00
# For malware sample
2024-01-17 13:13:14 +01:00
self . _malware_binary : BytesIO | None
2020-11-26 13:31:10 +01:00
2024-01-30 12:51:23 +01:00
def add_tag ( self , tag : str | MISPTag | dict [ str , Any ] | None = None , * * kwargs ) - > MISPTag : # type: ignore[no-untyped-def]
2020-01-23 10:27:40 +01:00
return super ( ) . _add_tag ( tag , * * kwargs )
@property
2024-01-17 13:13:14 +01:00
def tags ( self ) - > list [ MISPTag ] :
2020-10-27 16:14:06 +01:00
""" Returns a list of tags associated to this Attribute """
2020-01-23 10:27:40 +01:00
return self . Tag
@tags.setter
2024-01-30 12:51:23 +01:00
def tags ( self , tags : list [ MISPTag ] ) - > None :
2020-01-23 10:27:40 +01:00
""" Set a list of prepared MISPTag. """
super ( ) . _set_tags ( tags )
2016-09-26 00:26:09 +02:00
2024-01-30 12:51:23 +01:00
def add_galaxy ( self , galaxy : MISPGalaxy | dict [ str , Any ] | None = None , * * kwargs ) - > MISPGalaxy : # type: ignore[no-untyped-def]
2022-12-01 12:06:57 +01:00
""" Add a galaxy to the Attribute, either by passing a MISPGalaxy or a dictionary """
if isinstance ( galaxy , MISPGalaxy ) :
self . galaxies . append ( galaxy )
return galaxy
if isinstance ( galaxy , dict ) :
misp_galaxy = MISPGalaxy ( )
misp_galaxy . from_dict ( * * galaxy )
elif kwargs :
misp_galaxy = MISPGalaxy ( )
misp_galaxy . from_dict ( * * kwargs )
else :
raise InvalidMISPGalaxy ( " A Galaxy to add to an existing Attribute needs to be either a MISPGalaxy or a plain python dictionary " )
self . galaxies . append ( misp_galaxy )
return misp_galaxy
2020-02-24 14:13:10 +01:00
2022-12-01 12:06:57 +01:00
@property
2024-01-17 13:13:14 +01:00
def galaxies ( self ) - > list [ MISPGalaxy ] :
2022-12-01 12:06:57 +01:00
""" Returns a list of galaxies associated to this Attribute """
return self . Galaxy
2020-01-27 19:07:40 +01:00
2024-01-30 12:51:23 +01:00
def _prepare_data ( self , data : Path | str | bytes | BytesIO | None ) - > None :
2022-12-01 12:06:57 +01:00
if not data :
super ( ) . __setattr__ ( ' data ' , None )
return
2020-01-27 19:07:40 +01:00
2022-12-01 12:06:57 +01:00
if isinstance ( data , BytesIO ) :
super ( ) . __setattr__ ( ' data ' , data )
elif isinstance ( data , Path ) :
with data . open ( ' rb ' ) as f_temp :
super ( ) . __setattr__ ( ' data ' , BytesIO ( f_temp . read ( ) ) )
elif isinstance ( data , ( str , bytes ) ) :
super ( ) . __setattr__ ( ' data ' , BytesIO ( base64 . b64decode ( data ) ) )
else :
raise PyMISPError ( f ' Invalid type ( { type ( data ) } ) for the data key: { data } ' )
2019-11-22 17:36:24 +01:00
2022-12-01 12:06:57 +01:00
if self . type == ' malware-sample ' :
try :
# Ignore type, if data is None -> exception
with ZipFile ( self . data ) as f : # type: ignore
if not self . __is_misp_encrypted_file ( f ) :
raise PyMISPError ( ' Not an existing malware sample ' )
for name in f . namelist ( ) :
if name . endswith ( ' .filename.txt ' ) :
with f . open ( name , pwd = b ' infected ' ) as unpacked :
self . malware_filename = unpacked . read ( ) . decode ( ) . strip ( )
else :
# decrypting a zipped file is extremely slow. We do it on-demand in self.malware_binary
continue
except Exception :
# not a encrypted zip file, assuming it is a new malware sample
self . _prepare_new_malware_sample ( )
2019-12-11 23:12:14 +01:00
2024-01-30 12:51:23 +01:00
def __setattr__ ( self , name : str , value : Any ) - > None :
2022-12-01 12:06:57 +01:00
if name in [ ' first_seen ' , ' last_seen ' ] :
_datetime = _make_datetime ( value )
2019-11-19 15:53:58 +01:00
2022-12-01 12:06:57 +01:00
# NOTE: the two following should be exceptions, but there are existing events in this state,
# And we cannot dump them if it is there.
if name == ' last_seen ' and hasattr ( self , ' first_seen ' ) and self . first_seen > _datetime :
logger . warning ( f ' last_seen ( { value } ) has to be after first_seen ( { self . first_seen } ) ' )
if name == ' first_seen ' and hasattr ( self , ' last_seen ' ) and self . last_seen < _datetime :
logger . warning ( f ' first_seen ( { value } ) has to be before last_seen ( { self . last_seen } ) ' )
super ( ) . __setattr__ ( name , _datetime )
elif name == ' data ' :
self . _prepare_data ( value )
else :
super ( ) . __setattr__ ( name , value )
2022-12-01 10:05:38 +01:00
2024-01-17 13:13:14 +01:00
def hash_values ( self , algorithm : str = ' sha512 ' ) - > list [ str ] :
2022-12-01 12:06:57 +01:00
""" Compute the hash of every value for fast lookups """
if algorithm not in hashlib . algorithms_available :
2024-01-17 13:13:14 +01:00
raise PyMISPError ( f ' The algorithm { algorithm } is not available for hashing. ' )
2022-12-01 12:06:57 +01:00
if ' | ' in self . type or self . type == ' malware-sample ' :
hashes = [ ]
for v in self . value . split ( ' | ' ) :
h = hashlib . new ( algorithm )
h . update ( v . encode ( " utf-8 " ) )
hashes . append ( h . hexdigest ( ) )
return hashes
else :
h = hashlib . new ( algorithm )
to_encode = self . value
if not isinstance ( to_encode , str ) :
to_encode = str ( to_encode )
h . update ( to_encode . encode ( " utf-8 " ) )
return [ h . hexdigest ( ) ]
2022-12-01 10:05:38 +01:00
2024-01-30 12:51:23 +01:00
def _set_default ( self ) - > None :
2022-12-01 12:06:57 +01:00
if not hasattr ( self , ' comment ' ) :
self . comment = ' '
if not hasattr ( self , ' timestamp ' ) :
self . timestamp = datetime . timestamp ( datetime . now ( ) )
2017-09-18 16:37:55 +02:00
2024-01-30 12:51:23 +01:00
def _to_feed ( self , with_distribution : bool = False ) - > dict [ str , Any ] :
2022-12-01 12:06:57 +01:00
if with_distribution :
self . _fields_for_feed . add ( ' distribution ' )
to_return = super ( ) . _to_feed ( )
if self . data :
to_return [ ' data ' ] = base64 . b64encode ( self . data . getvalue ( ) ) . decode ( )
if self . tags :
to_return [ ' Tag ' ] = list ( filter ( None , [ tag . _to_feed ( ) for tag in self . tags ] ) )
if with_distribution :
try :
to_return [ ' SharingGroup ' ] = self . SharingGroup . _to_feed ( )
except AttributeError :
pass
return to_return
@property
2024-01-17 13:13:14 +01:00
def known_types ( self ) - > list [ str ] :
2022-12-01 12:06:57 +01:00
""" Returns a list of all the known MISP attributes types """
return self . describe_types [ ' types ' ]
@property
2024-01-17 13:13:14 +01:00
def malware_binary ( self ) - > BytesIO | None :
2022-12-01 12:06:57 +01:00
""" Returns a BytesIO of the malware, if the attribute has one.
Decrypts , unpacks and caches the binary on the first invocation ,
which may require some time for large attachments ( ~ 1 s / MB ) .
2020-11-28 02:06:48 +01:00
"""
2022-12-01 12:06:57 +01:00
if self . type != ' malware-sample ' :
# Not a malware sample
return None
if hasattr ( self , ' _malware_binary ' ) :
# Already unpacked
return self . _malware_binary
elif hasattr ( self , ' malware_filename ' ) :
# Have a binary, but didn't decrypt it yet
with ZipFile ( self . data ) as f : # type: ignore
for name in f . namelist ( ) :
if not name . endswith ( ' .filename.txt ' ) :
with f . open ( name , pwd = b ' infected ' ) as unpacked :
self . _malware_binary = BytesIO ( unpacked . read ( ) )
return self . _malware_binary
return None
2019-08-20 15:34:21 +02:00
2022-12-01 12:06:57 +01:00
@property
2024-01-17 13:13:14 +01:00
def shadow_attributes ( self ) - > list [ MISPShadowAttribute ] :
2022-12-01 12:06:57 +01:00
return self . ShadowAttribute
2020-01-27 19:07:40 +01:00
2022-12-01 12:06:57 +01:00
@shadow_attributes.setter
2024-01-30 12:51:23 +01:00
def shadow_attributes ( self , shadow_attributes : list [ MISPShadowAttribute ] ) - > None :
2022-12-01 12:06:57 +01:00
""" 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. ' )
2020-01-27 19:07:40 +01:00
2022-12-01 12:06:57 +01:00
@property
2024-01-17 13:13:14 +01:00
def sightings ( self ) - > list [ MISPSighting ] :
2022-12-01 12:06:57 +01:00
return self . Sighting
2018-01-16 12:15:30 +01:00
2022-12-01 12:06:57 +01:00
@sightings.setter
2024-01-30 12:51:23 +01:00
def sightings ( self , sightings : list [ MISPSighting ] ) - > None :
2022-12-01 12:06:57 +01:00
""" Set a list of prepared MISPSighting. """
if all ( isinstance ( x , MISPSighting ) for x in sightings ) :
self . Sighting = sightings
else :
raise PyMISPError ( ' All the attributes have to be of type MISPSighting. ' )
2016-09-27 19:47:22 +02:00
2024-01-30 12:51:23 +01:00
def delete ( self ) - > None :
2022-12-01 12:06:57 +01:00
""" Mark the attribute as deleted (soft delete) """
self . deleted = True
2017-09-12 16:46:06 +02:00
2024-01-30 12:51:23 +01:00
def add_proposal ( self , shadow_attribute = None , * * kwargs ) - > MISPShadowAttribute : # type: ignore[no-untyped-def]
2022-12-01 12:06:57 +01:00
""" Alias for add_shadow_attribute """
return self . add_shadow_attribute ( shadow_attribute , * * kwargs )
2017-12-22 14:49:14 +01:00
2024-01-30 12:51:23 +01:00
def add_shadow_attribute ( self , shadow_attribute : MISPShadowAttribute | dict [ str , Any ] | None = None , * * kwargs ) - > MISPShadowAttribute : # type: ignore[no-untyped-def]
2022-12-01 12:06:57 +01:00
""" Add a shadow attribute to the attribute (by name or a MISPShadowAttribute 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 :
2024-01-30 12:51:23 +01:00
raise PyMISPError ( f " The shadow_attribute is in an invalid format (can be either string, MISPShadowAttribute, or an expanded dict[str, Any]): { shadow_attribute } " )
2022-12-01 12:06:57 +01:00
self . shadow_attributes . append ( misp_shadow_attribute )
self . edited = True
return misp_shadow_attribute
2016-12-07 18:37:35 +01:00
2024-01-30 12:51:23 +01:00
def add_sighting ( self , sighting : MISPSighting | dict [ str , Any ] | None = None , * * kwargs ) - > MISPSighting : # type: ignore[no-untyped-def]
2022-12-01 12:06:57 +01:00
""" Add a sighting to the attribute (by name or a MISPSighting object) """
if isinstance ( sighting , MISPSighting ) :
misp_sighting = sighting
elif isinstance ( sighting , dict ) :
misp_sighting = MISPSighting ( )
misp_sighting . from_dict ( * * sighting )
elif kwargs :
misp_sighting = MISPSighting ( )
misp_sighting . from_dict ( * * kwargs )
else :
2024-01-30 12:51:23 +01:00
raise PyMISPError ( f " The sighting is in an invalid format (can be either string, MISPShadowAttribute, or an expanded dict[str, Any]): { sighting } " )
2022-12-01 12:06:57 +01:00
self . sightings . append ( misp_sighting )
self . edited = True
return misp_sighting
2017-11-02 17:26:05 +01:00
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2022-12-01 12:06:57 +01:00
if ' Attribute ' in kwargs :
kwargs = kwargs [ ' Attribute ' ]
if kwargs . get ( ' type ' ) and kwargs . get ( ' category ' ) :
if kwargs [ ' type ' ] not in self . __category_type_mapping [ kwargs [ ' category ' ] ] :
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
2022-12-01 12:06:57 +01:00
self . type = kwargs . pop ( ' type ' , None ) # Required
if self . type is None :
raise NewAttributeError ( ' The type of the attribute is required. ' )
if self . type not in self . known_types :
raise NewAttributeError ( ' {} is invalid, type has to be in {} ' . format ( self . type , ( ' , ' . join ( self . known_types ) ) ) )
2017-12-21 18:46:28 +01:00
2022-12-01 12:06:57 +01:00
type_defaults = self . __sane_default [ self . type ]
2017-12-21 18:46:28 +01:00
2022-12-01 12:06:57 +01:00
self . value = kwargs . pop ( ' value ' , None )
if self . value is None :
raise NewAttributeError ( ' The value of the attribute is required. ' )
if self . type == ' datetime ' and isinstance ( self . value , str ) :
try :
# Faster
if sys . version_info > = ( 3 , 7 ) :
self . value = datetime . fromisoformat ( self . value )
else :
if ' + ' in self . value or ' - ' in self . value :
self . value = datetime . strptime ( self . value , " % Y- % m- %d T % H: % M: % S. %f % z " )
elif ' . ' in self . value :
self . value = datetime . strptime ( self . value , " % Y- % m- %d T % H: % M: % S. %f " )
else :
self . value = datetime . strptime ( self . value , " % Y- % m- %d T % H: % M: % S " )
except ValueError :
# Slower, but if the other ones fail, that's a good fallback
self . value = parse ( self . value )
2017-12-21 18:46:28 +01:00
2022-12-01 12:06:57 +01:00
# Default values
self . category = kwargs . pop ( ' category ' , type_defaults [ ' default_category ' ] )
if self . category is None :
# In case the category key is passed, but None
self . category = type_defaults [ ' default_category ' ]
if self . category not in self . __categories :
raise NewAttributeError ( ' {} is invalid, category has to be in {} ' . format ( self . category , ( ' , ' . join ( self . __categories ) ) ) )
2017-12-21 18:46:28 +01:00
2022-12-01 12:06:57 +01:00
self . to_ids = kwargs . pop ( ' to_ids ' , bool ( int ( type_defaults [ ' to_ids ' ] ) ) )
if self . to_ids is None :
self . to_ids = bool ( int ( type_defaults [ ' to_ids ' ] ) )
else :
self . to_ids = make_bool ( self . to_ids )
2019-12-18 14:45:14 +01:00
2022-12-01 12:06:57 +01:00
if not isinstance ( self . to_ids , bool ) :
2024-01-17 13:13:14 +01:00
raise NewAttributeError ( f ' { self . to_ids } is invalid, to_ids has to be True or False ' )
2022-12-01 10:05:38 +01:00
2022-12-01 12:06:57 +01:00
self . distribution = kwargs . pop ( ' distribution ' , None )
if self . distribution is not None :
self . distribution = int ( self . distribution )
if self . distribution not in [ 0 , 1 , 2 , 3 , 4 , 5 ] :
2024-01-17 13:13:14 +01:00
raise NewAttributeError ( f ' { self . distribution } is invalid, the distribution has to be in 0, 1, 2, 3, 4, 5 ' )
2019-12-18 14:45:14 +01:00
2022-12-01 12:06:57 +01:00
# other possible values
if kwargs . get ( ' data ' ) :
self . data = kwargs . pop ( ' data ' )
if kwargs . get ( ' id ' ) :
self . id = int ( kwargs . pop ( ' id ' ) )
if kwargs . get ( ' event_id ' ) :
self . event_id = int ( kwargs . pop ( ' event_id ' ) )
if kwargs . get ( ' timestamp ' ) :
ts = kwargs . pop ( ' timestamp ' )
if isinstance ( ts , datetime ) :
self . timestamp = ts
else :
self . timestamp = datetime . fromtimestamp ( int ( ts ) , timezone . utc )
if kwargs . get ( ' first_seen ' ) :
fs = kwargs . pop ( ' first_seen ' )
try :
# Faster
2024-01-17 13:13:14 +01:00
self . first_seen = datetime . fromisoformat ( fs )
2022-12-01 12:06:57 +01:00
except Exception :
# Use __setattr__
self . first_seen = fs
2019-12-18 14:45:14 +01:00
2022-12-01 12:06:57 +01:00
if kwargs . get ( ' last_seen ' ) :
ls = kwargs . pop ( ' last_seen ' )
try :
# Faster
2024-01-17 13:13:14 +01:00
self . last_seen = datetime . fromisoformat ( ls )
2022-12-01 12:06:57 +01:00
except Exception :
# Use __setattr__
self . last_seen = ls
2022-12-01 10:05:38 +01:00
2022-12-01 12:06:57 +01:00
if kwargs . get ( ' sharing_group_id ' ) :
self . sharing_group_id = int ( kwargs . pop ( ' sharing_group_id ' ) )
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.
2024-01-17 13:13:14 +01:00
raise NewAttributeError ( f ' If the distribution is set to sharing group, a sharing group ID is required (cannot be { self . sharing_group_id } ). ' )
2022-12-01 12:06:57 +01:00
if kwargs . get ( ' Tag ' ) :
[ self . add_tag ( tag ) for tag in kwargs . pop ( ' Tag ' ) ]
if kwargs . get ( ' Galaxy ' ) :
[ self . add_galaxy ( galaxy ) for galaxy in kwargs . pop ( ' Galaxy ' ) ]
if kwargs . get ( ' Sighting ' ) :
[ self . add_sighting ( sighting ) for sighting in kwargs . pop ( ' Sighting ' ) ]
if kwargs . get ( ' ShadowAttribute ' ) :
[ self . add_shadow_attribute ( s_attr ) for s_attr in kwargs . pop ( ' ShadowAttribute ' ) ]
if kwargs . get ( ' SharingGroup ' ) :
self . SharingGroup = MISPSharingGroup ( )
self . SharingGroup . from_dict ( * * kwargs . pop ( ' SharingGroup ' ) )
# If the user wants to disable correlation, let them. Defaults to False.
self . disable_correlation = kwargs . pop ( " disable_correlation " , False )
if self . disable_correlation is None :
self . disable_correlation = False
2022-12-01 10:05:38 +01:00
super ( ) . from_dict ( * * kwargs )
2024-01-30 12:51:23 +01:00
def to_dict ( self , json_format : bool = False ) - > dict [ str , Any ] :
2022-12-01 12:06:57 +01:00
to_return = super ( ) . to_dict ( json_format )
if self . data :
to_return [ ' data ' ] = base64 . b64encode ( self . data . getvalue ( ) ) . decode ( )
return to_return
2022-12-01 10:05:38 +01:00
2024-01-30 12:51:23 +01:00
def _prepare_new_malware_sample ( self ) - > None :
2022-12-01 12:06:57 +01:00
if ' | ' in self . value :
# Get the filename, ignore the md5, because humans.
2023-11-29 12:35:24 +01:00
self . malware_filename , md5 = self . value . rsplit ( ' | ' , 1 )
2022-12-01 12:06:57 +01:00
else :
# Assuming the user only passed the filename
self . malware_filename = self . value
self . value = self . malware_filename
self . _malware_binary = self . data
self . encrypt = True
2022-12-01 10:05:38 +01:00
2024-01-30 12:51:23 +01:00
def __is_misp_encrypted_file ( self , f : ZipFile ) - > bool :
2022-12-01 12:06:57 +01:00
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
2019-12-18 14:45:14 +01:00
2024-01-30 12:51:23 +01:00
def __repr__ ( self ) - > str :
2022-12-01 12:06:57 +01:00
if hasattr ( self , ' value ' ) :
return ' < {self.__class__.__name__} (type= {self.type} , value= {self.value} ) ' . format ( self = self )
2024-01-17 13:13:14 +01:00
return f ' < { self . __class__ . __name__ } (NotInitialized) '
2017-09-18 16:37:55 +02:00
2016-09-26 00:26:09 +02:00
2022-12-01 12:06:57 +01:00
class MISPObjectReference ( AbstractMISP ) :
2024-01-30 12:51:23 +01:00
_fields_for_feed : set [ str ] = { ' uuid ' , ' timestamp ' , ' relationship_type ' , ' comment ' ,
' object_uuid ' , ' referenced_uuid ' }
2022-12-01 12:06:57 +01:00
def __init__ ( self ) - > None :
2022-12-01 10:05:38 +01:00
super ( ) . __init__ ( )
2022-12-01 12:06:57 +01:00
self . uuid = str ( uuid . uuid4 ( ) )
self . object_uuid : str
self . referenced_uuid : str
self . relationship_type : str
2016-09-26 00:26:09 +02:00
2024-01-30 12:51:23 +01:00
def _set_default ( self ) - > None :
2022-12-01 12:06:57 +01:00
if not hasattr ( self , ' comment ' ) :
self . comment = ' '
if not hasattr ( self , ' timestamp ' ) :
self . timestamp = datetime . timestamp ( datetime . now ( ) )
2022-12-01 10:05:38 +01:00
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2022-12-01 12:06:57 +01:00
if ' ObjectReference ' in kwargs :
kwargs = kwargs [ ' ObjectReference ' ]
2024-01-17 13:13:14 +01:00
super ( ) . from_dict ( * * kwargs )
2022-12-01 10:05:38 +01:00
2022-12-01 12:06:57 +01:00
def __repr__ ( self ) - > str :
if hasattr ( self , ' referenced_uuid ' ) and hasattr ( self , ' object_uuid ' ) :
return ' < {self.__class__.__name__} (object_uuid= {self.object_uuid} , referenced_uuid= {self.referenced_uuid} , relationship_type= {self.relationship_type} ) ' . format ( self = self )
2024-01-17 13:13:14 +01:00
return f ' < { self . __class__ . __name__ } (NotInitialized) '
2022-12-01 10:05:38 +01:00
2022-12-01 12:06:57 +01:00
class MISPObject ( AbstractMISP ) :
2022-12-01 10:05:38 +01:00
2024-01-30 12:51:23 +01:00
_fields_for_feed : set [ str ] = { ' name ' , ' meta-category ' , ' description ' , ' template_uuid ' ,
' template_version ' , ' uuid ' , ' timestamp ' , ' comment ' ,
' first_seen ' , ' last_seen ' , ' deleted ' }
2022-12-01 10:05:38 +01:00
2024-01-30 12:51:23 +01:00
def __init__ ( self , name : str , strict : bool = False , standalone : bool = True , # type: ignore[no-untyped-def]
default_attributes_parameters : dict [ str , Any ] = { } , * * kwargs ) - > None :
2022-12-01 12:06:57 +01:00
''' Master class representing a generic MISP object
2022-12-01 10:05:38 +01:00
2022-12-01 12:06:57 +01:00
: param name : Name of the object
: param strict : Enforce validation with the object templates
: param 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 .
: param default_attributes_parameters : Used as template for the attributes if they are not overwritten in add_attribute
: param misp_objects_path_custom : Path to custom object templates
: param misp_objects_template_custom : Template of the object . Expects the content ( dict , loaded with json . load or json . loads ) of a template definition file , see repository MISP / misp - objects .
'''
super ( ) . __init__ ( * * kwargs )
self . _strict : bool = strict
self . name : str = name
self . _known_template : bool = False
self . id : int
2024-01-30 12:51:23 +01:00
self . _definition : dict [ str , Any ] | None
self . timestamp : float | int | datetime
2019-12-18 14:45:14 +01:00
2022-12-01 12:06:57 +01:00
misp_objects_template_custom = kwargs . pop ( ' misp_objects_template_custom ' , None )
misp_objects_path_custom = kwargs . pop ( ' misp_objects_path_custom ' , None )
if misp_objects_template_custom :
self . _set_template ( misp_objects_template_custom = misp_objects_template_custom )
2020-12-01 14:00:43 +01:00
else :
2022-12-01 12:06:57 +01:00
# Fall back to default path if None
self . _set_template ( misp_objects_path_custom = misp_objects_path_custom )
2016-09-26 00:26:09 +02:00
2022-12-01 12:06:57 +01:00
self . uuid : str = str ( uuid . uuid4 ( ) )
self . first_seen : datetime
self . last_seen : datetime
2024-01-30 12:51:23 +01:00
self . __fast_attribute_access : dict [ str , Any ] = defaultdict ( list ) # Hashtable object_relation: [attributes]
2024-01-17 13:13:14 +01:00
self . ObjectReference : list [ MISPObjectReference ] = [ ]
2022-12-01 12:06:57 +01:00
self . _standalone : bool = False
2024-01-17 13:13:14 +01:00
self . Attribute : list [ MISPObjectAttribute ] = [ ]
2022-12-01 12:06:57 +01:00
self . SharingGroup : MISPSharingGroup
2024-01-30 12:51:23 +01:00
self . _default_attributes_parameters : dict [ str , Any ]
2022-12-01 12:06:57 +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 ( )
2022-12-01 10:05:38 +01:00
else :
2022-12-01 12:06:57 +01:00
self . _default_attributes_parameters = copy . copy ( default_attributes_parameters )
if self . _default_attributes_parameters :
# Let's clean that up
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
2020-12-01 14:00:43 +01:00
2022-12-01 12:06:57 +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 : int = self . _default_attributes_parameters . get ( ' distribution ' , 5 )
self . sharing_group_id : int = self . _default_attributes_parameters . get ( ' sharing_group_id ' , 0 )
2022-12-01 10:05:38 +01:00
else :
2022-12-01 12:06:57 +01:00
self . distribution = 5 # Default to inherit
self . sharing_group_id = 0
self . standalone = standalone
2024-01-17 13:13:14 +01:00
def _load_template_path ( self , template_path : Path | str ) - > bool :
2022-12-01 12:06:57 +01:00
template = self . _load_json ( template_path )
if not template :
self . _definition = None
return False
self . _load_template ( template )
return True
2024-01-30 12:51:23 +01:00
def _load_template ( self , template : dict [ str , Any ] ) - > None :
2022-12-01 12:06:57 +01:00
self . _definition = template
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 ' ]
2016-09-27 19:47:22 +02:00
2024-01-30 12:51:23 +01:00
def _set_default ( self ) - > None :
2019-12-18 14:45:14 +01:00
if not hasattr ( self , ' comment ' ) :
self . comment = ' '
2019-11-19 15:53:58 +01:00
if not hasattr ( self , ' timestamp ' ) :
2020-01-27 19:07:40 +01:00
self . timestamp = datetime . timestamp ( datetime . now ( ) )
2019-11-20 12:49:42 +01:00
2024-01-30 12:51:23 +01:00
def _to_feed ( self , with_distribution : bool = False ) - > dict [ str , Any ] :
2021-11-29 15:54:34 +01:00
if with_distribution :
self . _fields_for_feed . add ( ' distribution ' )
2022-12-01 12:06:57 +01:00
if not hasattr ( self , ' template_uuid ' ) : # workaround for old events where the template_uuid was not yet mandatory
self . template_uuid = str ( uuid . uuid5 ( uuid . UUID ( " 9319371e-2504-4128-8410-3741cebbcfd3 " ) , self . name ) )
if not hasattr ( self , ' description ' ) : # workaround for old events where description is not always set
self . description = ' <unknown> '
if not hasattr ( self , ' meta-category ' ) : # workaround for old events where meta-category is not always set
setattr ( self , ' meta-category ' , ' misc ' )
2024-01-17 13:13:14 +01:00
to_return = super ( ) . _to_feed ( )
2022-12-01 12:06:57 +01:00
if self . references :
to_return [ ' ObjectReference ' ] = [ reference . _to_feed ( ) for reference in self . references ]
2021-11-29 15:54:34 +01:00
if with_distribution :
try :
to_return [ ' SharingGroup ' ] = self . SharingGroup . _to_feed ( )
except AttributeError :
pass
2019-11-22 17:36:24 +01:00
return to_return
2024-01-30 12:51:23 +01:00
def __setattr__ ( self , name : str , value : Any ) - > None :
2022-12-01 12:06:57 +01:00
if name in [ ' first_seen ' , ' last_seen ' ] :
value = _make_datetime ( value )
2020-01-27 19:07:40 +01:00
2022-12-01 12:06:57 +01:00
if name == ' last_seen ' and hasattr ( self , ' first_seen ' ) and self . first_seen > value :
logger . warning ( f ' last_seen ( { value } ) has to be after first_seen ( { self . first_seen } ) ' )
if name == ' first_seen ' and hasattr ( self , ' last_seen ' ) and self . last_seen < value :
logger . warning ( f ' first_seen ( { value } ) has to be before last_seen ( { self . last_seen } ) ' )
super ( ) . __setattr__ ( name , value )
2020-01-27 19:07:40 +01:00
2024-01-30 12:51:23 +01:00
def force_misp_objects_path_custom ( self , misp_objects_path_custom : Path | str , object_name : str | None = None ) - > None :
2022-12-01 12:06:57 +01:00
if object_name :
self . name = object_name
self . _set_template ( misp_objects_path_custom )
2019-11-22 17:36:24 +01:00
2024-01-30 12:51:23 +01:00
def _set_template ( self , misp_objects_path_custom : Path | str | None = None , misp_objects_template_custom : dict [ str , Any ] | None = None ) - > None :
2022-12-01 12:06:57 +01:00
if misp_objects_template_custom :
# A complete template was given to the constructor
self . _load_template ( misp_objects_template_custom )
self . _known_template = True
2020-12-01 14:00:43 +01:00
else :
2022-12-01 12:06:57 +01:00
if misp_objects_path_custom :
# If misp_objects_path_custom is given, and an object with the given name exists, use that.
if isinstance ( misp_objects_path_custom , str ) :
self . misp_objects_path = Path ( misp_objects_path_custom )
else :
self . misp_objects_path = misp_objects_path_custom
2019-11-22 17:36:24 +01:00
2022-12-01 12:06:57 +01:00
# Try to get the template
self . _known_template = self . _load_template_path ( self . misp_objects_path / self . name / ' definition.json ' )
2019-11-22 17:36:24 +01:00
2022-12-01 12:06:57 +01:00
if not self . _known_template and self . _strict :
2024-01-17 13:13:14 +01:00
raise UnknownMISPObjectTemplate ( f ' { self . name } is unknown in the MISP object directory. ' )
2019-12-18 14:45:14 +01:00
else :
2022-12-01 12:06:57 +01:00
# Then we have no meta-category, template_uuid, description and template_version
pass
2019-11-19 15:53:58 +01:00
2024-01-30 12:51:23 +01:00
def delete ( self ) - > None :
2022-12-01 12:06:57 +01:00
""" Mark the object as deleted (soft delete) """
2021-01-31 22:37:25 +01:00
self . deleted = True
2024-01-30 12:51:23 +01:00
for a in self . attributes :
a . delete ( )
2017-12-22 14:49:14 +01:00
2022-12-01 12:06:57 +01:00
@property
2024-01-30 12:51:23 +01:00
def disable_validation ( self ) - > None :
2022-12-01 12:06:57 +01:00
self . _strict = False
2022-12-01 10:05:38 +01:00
2022-12-01 12:06:57 +01:00
@property
2024-01-17 13:13:14 +01:00
def attributes ( self ) - > list [ MISPObjectAttribute ] :
2022-12-01 12:06:57 +01:00
return self . Attribute
2022-12-01 10:05:38 +01:00
2022-12-01 12:06:57 +01:00
@attributes.setter
2024-01-30 12:51:23 +01:00
def attributes ( self , attributes : list [ MISPObjectAttribute ] ) - > None :
2022-12-01 12:06:57 +01:00
if all ( isinstance ( x , MISPObjectAttribute ) for x in attributes ) :
self . Attribute = attributes
self . __fast_attribute_access = defaultdict ( list )
else :
raise PyMISPError ( ' All the attributes have to be of type MISPObjectAttribute. ' )
2022-12-01 10:05:38 +01:00
2022-12-01 12:06:57 +01:00
@property
2024-01-17 13:13:14 +01:00
def references ( self ) - > list [ MISPObjectReference ] :
2022-12-01 12:06:57 +01:00
return self . ObjectReference
2017-08-30 12:47:32 +02:00
2022-12-01 12:06:57 +01:00
@references.setter
2024-01-30 12:51:23 +01:00
def references ( self , references : list [ MISPObjectReference ] ) - > None :
2022-12-01 12:06:57 +01:00
if all ( isinstance ( x , MISPObjectReference ) for x in references ) :
self . ObjectReference = references
else :
raise PyMISPError ( ' All the attributes have to be of type MISPObjectReference. ' )
2020-12-01 14:00:43 +01:00
2022-12-01 12:06:57 +01:00
@property
2024-01-30 12:51:23 +01:00
def standalone ( self ) - > bool :
2022-12-01 12:06:57 +01:00
return self . _standalone
@standalone.setter
2024-01-30 12:51:23 +01:00
def standalone ( self , new_standalone : bool ) - > None :
2022-12-01 12:06:57 +01:00
if self . _standalone != new_standalone :
if new_standalone :
self . update_not_jsonable ( " ObjectReference " )
else :
self . _remove_from_not_jsonable ( " ObjectReference " )
self . _standalone = new_standalone
2022-12-01 10:05:38 +01:00
else :
2022-12-01 12:06:57 +01:00
pass
2022-12-01 10:05:38 +01:00
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2022-12-01 12:06:57 +01:00
if ' Object ' in kwargs :
kwargs = kwargs [ ' Object ' ]
if self . _known_template :
if kwargs . get ( ' template_uuid ' ) and kwargs [ ' template_uuid ' ] != self . template_uuid :
if self . _strict :
raise UnknownMISPObjectTemplate ( ' UUID of the object is different from the one of the template. ' )
else :
self . _known_template = False
if kwargs . get ( ' template_version ' ) and int ( kwargs [ ' template_version ' ] ) != self . template_version :
if self . _strict :
raise UnknownMISPObjectTemplate ( ' Version of the object ( {} ) is different from the one of the template ( {} ). ' . format ( kwargs [ ' template_version ' ] , self . template_version ) )
else :
self . _known_template = False
2022-12-01 10:05:38 +01:00
2022-12-01 12:06:57 +01:00
# depending on how the object is initialized, we may have a few keys to pop
kwargs . pop ( ' misp_objects_template_custom ' , None )
kwargs . pop ( ' misp_objects_path_custom ' , None )
if ' distribution ' in kwargs and kwargs [ ' distribution ' ] is not None :
self . distribution = kwargs . pop ( ' distribution ' )
2019-12-18 14:45:14 +01:00
self . distribution = int ( self . distribution )
if self . distribution not in [ 0 , 1 , 2 , 3 , 4 , 5 ] :
2024-01-17 13:13:14 +01:00
raise NewAttributeError ( f ' { self . distribution } is invalid, the distribution has to be in 0, 1, 2, 3, 4, 5 ' )
2019-07-12 17:35:02 +02:00
2019-12-18 14:45:14 +01:00
if kwargs . get ( ' timestamp ' ) :
ts = kwargs . pop ( ' timestamp ' )
2020-01-27 19:07:40 +01:00
if isinstance ( ts , datetime ) :
2019-12-18 14:45:14 +01:00
self . timestamp = ts
else :
2020-01-27 19:07:40 +01:00
self . timestamp = datetime . fromtimestamp ( int ( ts ) , timezone . utc )
2022-12-01 12:06:57 +01:00
2020-01-27 19:07:40 +01:00
if kwargs . get ( ' first_seen ' ) :
fs = kwargs . pop ( ' first_seen ' )
try :
# Faster
2024-01-17 13:13:14 +01:00
self . first_seen = datetime . fromisoformat ( fs )
2020-01-27 20:14:14 +01:00
except Exception :
2020-01-27 19:07:40 +01:00
# Use __setattr__
self . first_seen = fs
if kwargs . get ( ' last_seen ' ) :
ls = kwargs . pop ( ' last_seen ' )
try :
# Faster
2024-01-17 13:13:14 +01:00
self . last_seen = datetime . fromisoformat ( ls )
2020-01-27 20:14:14 +01:00
except Exception :
2020-01-27 19:07:40 +01:00
# Use __setattr__
self . last_seen = ls
2022-12-01 12:06:57 +01:00
if kwargs . get ( ' Attribute ' ) :
[ self . add_attribute ( * * a ) for a in kwargs . pop ( ' Attribute ' ) ]
if kwargs . get ( ' ObjectReference ' ) :
[ self . add_reference ( * * r ) for r in kwargs . pop ( ' ObjectReference ' ) ]
2019-07-12 17:35:02 +02:00
2020-01-23 10:27:40 +01:00
if kwargs . get ( ' SharingGroup ' ) :
2020-02-05 13:00:14 +01:00
self . SharingGroup = MISPSharingGroup ( )
self . SharingGroup . from_dict ( * * kwargs . pop ( ' SharingGroup ' ) )
2022-12-01 12:06:57 +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)
2019-07-12 17:35:02 +02:00
2019-12-18 14:45:14 +01:00
super ( ) . from_dict ( * * kwargs )
2019-07-12 17:35:02 +02:00
2024-01-30 12:51:23 +01:00
def add_reference ( self , referenced_uuid : AbstractMISP | str , relationship_type : str , comment : str | None = None , * * kwargs ) - > MISPObjectReference : # type: ignore[no-untyped-def]
2022-12-01 12:06:57 +01:00
""" Add a link (uuid) to another object """
if isinstance ( referenced_uuid , AbstractMISP ) :
# Allow to pass an object or an attribute instead of its UUID
referenced_uuid = referenced_uuid . uuid
if ' object_uuid ' in kwargs and not kwargs . get ( ' object_uuid ' ) :
# Unexplained None in object_uuid key -> https://github.com/MISP/PyMISP/issues/640
kwargs . pop ( ' object_uuid ' )
object_uuid = self . uuid
elif kwargs . get ( ' object_uuid ' ) :
# Load existing object
object_uuid = kwargs . pop ( ' object_uuid ' )
else :
# New reference
object_uuid = self . uuid
reference = MISPObjectReference ( )
reference . from_dict ( object_uuid = object_uuid , referenced_uuid = referenced_uuid ,
relationship_type = relationship_type , comment = comment , * * kwargs )
self . ObjectReference . append ( reference )
self . edited = True
return reference
2021-01-12 16:13:32 +01:00
2024-01-17 13:13:14 +01:00
def get_attributes_by_relation ( self , object_relation : str ) - > list [ MISPAttribute ] :
2022-12-01 12:06:57 +01:00
''' Returns the list of attributes with the given object relation in the object '''
return self . _fast_attribute_access . get ( object_relation , [ ] )
@property
2024-01-30 12:51:23 +01:00
def _fast_attribute_access ( self ) - > dict [ str , Any ] :
2022-12-01 12:06:57 +01:00
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
2024-01-17 13:13:14 +01:00
def has_attributes_by_relation ( self , list_of_relations : list [ str ] ) - > bool :
2022-12-01 12:06:57 +01:00
''' True if all the relations in the list are defined in the object '''
return all ( relation in self . _fast_attribute_access for relation in list_of_relations )
2024-01-30 12:51:23 +01:00
def add_attribute ( self , object_relation : str , simple_value : str | int | float | None = None , * * value ) - > MISPAttribute | None : # type: ignore[no-untyped-def]
2022-12-01 12:06:57 +01:00
""" Add an attribute.
: param object_relation : The object relation of the attribute you ' re adding to the object
: param simple_value : The value
: param value : dictionary with all the keys supported by MISPAttribute
Note : as long as PyMISP knows about the object template , only the object_relation and the simple_value are required .
If PyMISP doesn ' t know the template, you also **must** pass a type.
All the other options that can be passed along when creating an attribute ( comment , IDS flag , . . . )
will be either taked out of the template , or out of the default setting for the type as defined on the MISP instance .
"""
if simple_value is not None : # /!\ The value *can* be 0
value [ ' value ' ] = simple_value
if value . get ( ' value ' ) is None :
2024-01-17 13:13:14 +01:00
logger . warning ( f " The value of the attribute you ' re trying to add is None, skipping it. Object relation: { object_relation } " )
2022-12-01 12:06:57 +01:00
return None
2022-12-01 10:05:38 +01:00
else :
2022-12-01 12:06:57 +01:00
if isinstance ( value [ ' value ' ] , bytes ) :
# That shouldn't happen, but we live in the real world, and it does.
# So we try to decode (otherwise, MISP barf), and raise a warning if needed.
try :
value [ ' value ' ] = value [ ' value ' ] . decode ( )
except Exception :
logger . warning ( " The value of the attribute you ' re trying to add is a bytestream ( {!r} ), and we ' re unable to make it a string. " . format ( value [ ' value ' ] ) )
return None
2021-01-12 16:13:32 +01:00
2022-12-01 12:06:57 +01:00
# Make sure we're not adding an empty value.
if isinstance ( value [ ' value ' ] , str ) :
value [ ' value ' ] = value [ ' value ' ] . strip ( ) . strip ( ' \x00 ' )
if value [ ' value ' ] == ' ' :
2024-01-17 13:13:14 +01:00
logger . warning ( f " The value of the attribute you ' re trying to add is an empty string, skipping it. Object relation: { object_relation } " )
2022-12-01 12:06:57 +01:00
return None
if self . _known_template and self . _definition :
if object_relation in self . _definition [ ' attributes ' ] :
attribute = MISPObjectAttribute ( self . _definition [ ' attributes ' ] [ object_relation ] )
2022-12-01 10:05:38 +01:00
else :
2022-12-01 12:06:57 +01:00
# Woopsie, this object_relation is unknown, no sane defaults for you.
2024-01-17 13:13:14 +01:00
logger . warning ( f " The template ( { self . name } ) doesn ' t have the object_relation ( { object_relation } ) you ' re trying to add. If you are creating a new event to push to MISP, please review your code so it matches the template. " )
2022-12-01 12:06:57 +01:00
attribute = MISPObjectAttribute ( { } )
else :
attribute = MISPObjectAttribute ( { } )
# Overwrite the parameters of self._default_attributes_parameters with the ones of value
attribute . from_dict ( object_relation = object_relation , * * { * * self . _default_attributes_parameters , * * value } )
self . __fast_attribute_access [ object_relation ] . append ( attribute )
self . Attribute . append ( attribute )
self . edited = True
return attribute
2024-01-30 12:51:23 +01:00
def add_attributes ( self , object_relation : str , * attributes : list [ dict [ str , Any ] | MISPAttribute ] ) - > list [ MISPAttribute | None ] :
2022-12-01 12:06:57 +01:00
''' Add multiple attributes with the same object_relation.
Helper for object_relation when multiple is True in the template .
It is the same as calling multiple times add_attribute with the same object_relation .
'''
to_return = [ ]
for attribute in attributes :
if isinstance ( attribute , dict ) :
a = self . add_attribute ( object_relation , * * attribute )
else :
a = self . add_attribute ( object_relation , value = attribute )
to_return . append ( a )
return to_return
2024-01-30 12:51:23 +01:00
def to_dict ( self , json_format : bool = False , strict : bool = False ) - > dict [ str , Any ] :
2022-12-01 12:06:57 +01:00
if strict or self . _strict and self . _known_template :
self . _validate ( )
2024-01-17 13:13:14 +01:00
return super ( ) . to_dict ( json_format )
2022-12-01 12:06:57 +01:00
2024-01-17 13:13:14 +01:00
def to_json ( self , sort_keys : bool = False , indent : int | None = None , strict : bool = False ) - > str :
2022-12-01 12:06:57 +01:00
if strict or self . _strict and self . _known_template :
self . _validate ( )
2024-01-17 13:13:14 +01:00
return super ( ) . to_json ( sort_keys = sort_keys , indent = indent )
2022-12-01 12:06:57 +01:00
def _validate ( self ) - > bool :
if not self . _definition :
raise PyMISPError ( ' No object definition available, unable to validate. ' )
""" Make sure the object we ' re creating has the required fields """
if self . _definition . get ( ' required ' ) :
required_missing = set ( self . _definition [ ' required ' ] ) - set ( self . _fast_attribute_access . keys ( ) )
if required_missing :
2024-01-17 13:13:14 +01:00
raise InvalidMISPObject ( f ' { required_missing } are required. ' )
2022-12-01 12:06:57 +01:00
if self . _definition . get ( ' requiredOneOf ' ) :
if not set ( self . _definition [ ' requiredOneOf ' ] ) & set ( self . _fast_attribute_access . keys ( ) ) :
# We ecpect at least one of the object_relation in requiredOneOf, and it isn't the case
raise InvalidMISPObject ( ' At least one of the following attributes is required: {} ' . format ( ' , ' . join ( self . _definition [ ' requiredOneOf ' ] ) ) )
for rel , attrs in self . _fast_attribute_access . items ( ) :
if len ( attrs ) == 1 :
# object_relation's here only once, everything's cool, moving on
continue
if not self . _definition [ ' attributes ' ] [ rel ] . get ( ' multiple ' ) :
# object_relation's here more than once, but it isn't allowed in the template.
2024-01-17 13:13:14 +01:00
raise InvalidMISPObject ( f ' Multiple occurrences of { rel } is not allowed ' )
2022-12-01 10:05:38 +01:00
return True
2021-01-12 16:13:32 +01:00
2022-12-01 12:06:57 +01:00
def __repr__ ( self ) - > str :
if hasattr ( self , ' name ' ) :
return ' < {self.__class__.__name__} (name= {self.name} ) ' . format ( self = self )
2024-01-17 13:13:14 +01:00
return f ' < { self . __class__ . __name__ } (NotInitialized) '
2021-01-12 16:13:32 +01:00
2022-12-01 12:06:57 +01:00
class MISPEventReport ( AbstractMISP ) :
2021-01-12 16:13:32 +01:00
2024-01-30 12:51:23 +01:00
_fields_for_feed : set [ str ] = { ' uuid ' , ' name ' , ' content ' , ' timestamp ' , ' deleted ' }
2021-01-12 16:13:32 +01:00
2024-01-30 12:51:23 +01:00
timestamp : float | int | datetime
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2022-12-01 12:06:57 +01:00
if ' EventReport ' in kwargs :
kwargs = kwargs [ ' EventReport ' ]
2021-01-12 16:13:32 +01:00
2022-12-01 12:06:57 +01:00
self . distribution = kwargs . pop ( ' distribution ' , None )
if self . distribution is not None :
self . distribution = int ( self . distribution )
if self . distribution not in [ 0 , 1 , 2 , 3 , 4 , 5 ] :
2024-01-17 13:13:14 +01:00
raise NewEventReportError ( f ' { self . distribution } is invalid, the distribution has to be in 0, 1, 2, 3, 4, 5 ' )
2021-01-12 16:13:32 +01:00
2022-12-01 12:06:57 +01:00
if kwargs . get ( ' sharing_group_id ' ) :
self . sharing_group_id = int ( kwargs . pop ( ' sharing_group_id ' ) )
2021-01-14 19:58:35 +01:00
2022-12-01 12:06:57 +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 NewEventReportError ( ' 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.
2024-01-17 13:13:14 +01:00
raise NewEventReportError ( f ' If the distribution is set to sharing group, a sharing group ID is required (cannot be { self . sharing_group_id } ). ' )
2021-01-12 16:13:32 +01:00
2022-12-01 12:06:57 +01:00
self . name = kwargs . pop ( ' name ' , None )
if self . name is None :
raise NewEventReportError ( ' The name of the event report is required. ' )
2021-01-30 14:56:40 +01:00
2022-12-01 12:06:57 +01:00
self . content = kwargs . pop ( ' content ' , None )
if self . content is None :
raise NewAttributeError ( ' The content of the event report is required. ' )
if kwargs . get ( ' id ' ) :
self . id = int ( kwargs . pop ( ' id ' ) )
if kwargs . get ( ' event_id ' ) :
self . event_id = int ( kwargs . pop ( ' event_id ' ) )
if kwargs . get ( ' timestamp ' ) :
ts = kwargs . pop ( ' timestamp ' )
if isinstance ( ts , datetime ) :
self . timestamp = ts
else :
self . timestamp = datetime . fromtimestamp ( int ( ts ) , timezone . utc )
if kwargs . get ( ' deleted ' ) :
self . deleted = kwargs . pop ( ' deleted ' )
super ( ) . from_dict ( * * kwargs )
2021-03-02 12:21:59 +01:00
2021-01-16 16:56:30 +01:00
def __repr__ ( self ) - > str :
2022-12-01 12:06:57 +01:00
if hasattr ( self , ' name ' ) :
return ' < {self.__class__.__name__} (name= {self.name} ) ' . format ( self = self )
2024-01-17 13:13:14 +01:00
return f ' < { self . __class__ . __name__ } (NotInitialized) '
2021-01-16 16:56:30 +01:00
2024-01-30 12:51:23 +01:00
def _set_default ( self ) - > None :
2022-12-01 12:06:57 +01:00
if not hasattr ( self , ' timestamp ' ) :
self . timestamp = datetime . timestamp ( datetime . now ( ) )
if not hasattr ( self , ' name ' ) :
self . name = ' '
if not hasattr ( self , ' content ' ) :
self . content = ' '
2021-01-30 14:56:40 +01:00
2022-12-01 12:06:57 +01:00
class MISPGalaxyClusterElement ( AbstractMISP ) :
""" A MISP Galaxy cluster element, providing further info on a cluster
2021-01-16 16:56:30 +01:00
2022-12-01 12:06:57 +01:00
Creating a new galaxy cluster element can take the following parameters
2021-01-16 16:56:30 +01:00
2022-12-01 12:06:57 +01:00
: param key : The key / identifier of the element
: type key : str
: param value : The value of the element
: type value : str
"""
2021-01-30 14:56:40 +01:00
2022-12-01 12:06:57 +01:00
key : str
value : str
2021-01-16 16:56:30 +01:00
2022-12-01 12:06:57 +01:00
def __repr__ ( self ) - > str :
if hasattr ( self , ' key ' ) and hasattr ( self , ' value ' ) :
return ' < {self.__class__.__name__} (key= {self.key} , value= {self.value} ) ' . format ( self = self )
2024-01-17 13:13:14 +01:00
return f ' < { self . __class__ . __name__ } (NotInitialized) '
2021-01-16 16:56:30 +01:00
2024-01-30 12:51:23 +01:00
def __setattr__ ( self , key : str , value : Any ) - > None :
2022-12-01 12:06:57 +01:00
if key == " value " and isinstance ( value , list ) :
raise PyMISPError ( " You tried to set a list to a cluster element ' s value. "
" Instead, create seperate elements for each value " )
super ( ) . __setattr__ ( key , value )
2021-01-16 16:56:30 +01:00
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2022-12-01 12:06:57 +01:00
if kwargs . get ( ' id ' ) :
self . id = int ( kwargs . pop ( ' id ' ) )
if kwargs . get ( ' galaxy_cluster_id ' ) :
self . galaxy_cluster_id = int ( kwargs . pop ( ' galaxy_cluster_id ' ) )
2021-01-16 16:56:30 +01:00
2022-12-01 12:06:57 +01:00
super ( ) . from_dict ( * * kwargs )
2021-01-30 14:56:40 +01:00
2022-12-01 12:06:57 +01:00
class MISPGalaxyClusterRelation ( AbstractMISP ) :
""" A MISP Galaxy cluster relation, linking one cluster to another
2021-01-16 16:56:30 +01:00
2022-12-01 12:06:57 +01:00
Creating a new galaxy cluster can take the following parameters
: param galaxy_cluster_uuid : The UUID of the galaxy the relation links to
: param referenced_galaxy_cluster_type : The relation type , e . g . dropped - by
: param referenced_galaxy_cluster_uuid : The UUID of the related galaxy
: param distribution : The distribution of the relation , one of 0 , 1 , 2 , 3 , 4 , default 0
: param sharing_group_id : The sharing group of the relation , only when distribution is 4
"""
def __repr__ ( self ) - > str :
if hasattr ( self , " referenced_galaxy_cluster_type " ) :
return ' < {self.__class__.__name__} (referenced_galaxy_cluster_type= {self.referenced_galaxy_cluster_type} ) ' . format ( self = self )
2024-01-17 13:13:14 +01:00
return f ' < { self . __class__ . __name__ } (NotInitialized) '
2021-01-16 16:56:30 +01:00
2022-12-01 12:06:57 +01:00
def __init__ ( self ) - > None :
super ( ) . __init__ ( )
self . galaxy_cluster_uuid : str
self . referenced_galaxy_cluster_uuid : str
self . distribution : int = 0
self . referenced_galaxy_cluster_type : str
2024-01-17 13:13:14 +01:00
self . Tag : list [ MISPTag ] = [ ]
2021-01-16 16:56:30 +01:00
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2022-12-01 12:06:57 +01:00
# Default values for a valid event to send to a MISP instance
self . distribution = int ( kwargs . pop ( ' distribution ' , 0 ) )
if self . distribution not in [ 0 , 1 , 2 , 3 , 4 , 5 ] :
raise NewGalaxyClusterRelationError ( f ' { self . distribution } is invalid, the distribution has to be in 0, 1, 2, 3, 4 ' )
2021-01-16 16:56:30 +01:00
2022-12-01 12:06:57 +01:00
if kwargs . get ( ' sharing_group_id ' ) :
self . sharing_group_id = int ( kwargs . pop ( ' sharing_group_id ' ) )
2021-01-16 16:56:30 +01:00
2022-12-01 12:06:57 +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 NewGalaxyClusterRelationError ( ' 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.
2024-01-17 13:13:14 +01:00
raise NewGalaxyClusterRelationError ( f ' If the distribution is set to sharing group, a sharing group ID is required (cannot be { self . sharing_group_id } ). ' )
2021-01-16 16:56:30 +01:00
2022-12-01 12:06:57 +01:00
if kwargs . get ( ' id ' ) :
self . id = int ( kwargs . pop ( ' id ' ) )
if kwargs . get ( ' orgc_id ' ) :
self . orgc_id = int ( kwargs . pop ( ' orgc_id ' ) )
if kwargs . get ( ' org_id ' ) :
self . org_id = int ( kwargs . pop ( ' org_id ' ) )
if kwargs . get ( ' galaxy_id ' ) :
self . galaxy_id = int ( kwargs . pop ( ' galaxy_id ' ) )
if kwargs . get ( ' tag_id ' ) :
self . tag_id = int ( kwargs . pop ( ' tag_id ' ) )
if kwargs . get ( ' sharing_group_id ' ) :
self . sharing_group_id = int ( kwargs . pop ( ' sharing_group_id ' ) )
if kwargs . get ( ' Tag ' ) :
[ self . add_tag ( * * t ) for t in kwargs . pop ( ' Tag ' ) ]
if kwargs . get ( ' SharingGroup ' ) :
self . SharingGroup = MISPSharingGroup ( )
self . SharingGroup . from_dict ( * * kwargs . pop ( ' SharingGroup ' ) )
super ( ) . from_dict ( * * kwargs )
2021-01-30 14:56:40 +01:00
2024-01-30 12:51:23 +01:00
def add_tag ( self , tag : str | MISPTag | dict [ str , Any ] | None = None , * * kwargs ) - > MISPTag : # type: ignore[no-untyped-def]
2022-12-01 12:06:57 +01:00
return super ( ) . _add_tag ( tag , * * kwargs )
2021-01-16 16:56:30 +01:00
2022-12-01 10:05:38 +01:00
@property
2024-01-17 13:13:14 +01:00
def tags ( self ) - > list [ MISPTag ] :
2022-12-01 12:06:57 +01:00
""" Returns a list of tags associated to this Attribute """
return self . Tag
2021-01-16 16:56:30 +01:00
2022-12-01 12:06:57 +01:00
@tags.setter
2024-01-30 12:51:23 +01:00
def tags ( self , tags : list [ MISPTag ] ) - > None :
2022-12-01 12:06:57 +01:00
""" Set a list of prepared MISPTag. """
super ( ) . _set_tags ( tags )
2021-01-16 16:56:30 +01:00
2022-12-01 12:06:57 +01:00
class MISPGalaxyCluster ( AbstractMISP ) :
""" A MISP galaxy cluster, storing respective galaxy elements and relations.
Used to view default galaxy clusters and add / edit / update / delete Galaxy 2.0 clusters
2021-01-16 16:56:30 +01:00
2022-12-01 12:06:57 +01:00
Creating a new galaxy cluster can take the following parameters
: param value : The value of the galaxy cluster
: type value : str
: param description : The description of the galaxy cluster
: type description : str
: param distribution : The distribution type , one of 0 , 1 , 2 , 3 , 4
: type distribution : int
: param sharing_group_id : The sharing group ID , if distribution is set to 4
: type sharing_group_id : int , optional
: param authors : A list of authors of the galaxy cluster
: type authors : list [ str ] , optional
: param cluster_elements : List of MISPGalaxyClusterElement
: type cluster_elements : list [ MISPGalaxyClusterElement ] , optional
: param cluster_relations : List of MISPGalaxyClusterRelation
: type cluster_relations : list [ MISPGalaxyClusterRelation ] , optional
"""
def __init__ ( self ) - > None :
super ( ) . __init__ ( )
self . Galaxy : MISPGalaxy
2024-01-17 13:13:14 +01:00
self . GalaxyElement : list [ MISPGalaxyClusterElement ] = [ ]
2024-01-30 12:51:23 +01:00
self . meta : dict [ str , Any ] = { }
2024-01-17 13:13:14 +01:00
self . GalaxyClusterRelation : list [ MISPGalaxyClusterRelation ] = [ ]
2022-12-01 12:06:57 +01:00
self . Org : MISPOrganisation
self . Orgc : MISPOrganisation
self . SharingGroup : MISPSharingGroup
self . value : str
# Set any inititialized cluster to be False
self . default = False
2021-02-08 12:52:08 +01:00
2021-01-30 14:56:40 +01:00
@property
2024-01-17 13:13:14 +01:00
def cluster_elements ( self ) - > list [ MISPGalaxyClusterElement ] :
2022-12-01 12:06:57 +01:00
return self . GalaxyElement
2022-12-01 10:05:38 +01:00
2022-12-01 12:06:57 +01:00
@cluster_elements.setter
2024-01-30 12:51:23 +01:00
def cluster_elements ( self , cluster_elements : list [ MISPGalaxyClusterElement ] ) - > None :
2022-12-01 12:06:57 +01:00
self . GalaxyElement = cluster_elements
2021-01-30 14:56:40 +01:00
2022-12-01 12:06:57 +01:00
@property
2024-01-17 13:13:14 +01:00
def cluster_relations ( self ) - > list [ MISPGalaxyClusterRelation ] :
2022-12-01 12:06:57 +01:00
return self . GalaxyClusterRelation
2021-01-30 14:56:40 +01:00
2022-12-01 12:06:57 +01:00
@cluster_relations.setter
2024-01-30 12:51:23 +01:00
def cluster_relations ( self , cluster_relations : list [ MISPGalaxyClusterRelation ] ) - > None :
2022-12-01 12:06:57 +01:00
self . GalaxyClusterRelation = cluster_relations
2021-01-30 14:56:40 +01:00
2024-01-30 12:51:23 +01:00
def parse_meta_as_elements ( self ) - > None :
2022-12-01 12:06:57 +01:00
""" Function to parse the meta field into GalaxyClusterElements """
# Parse the cluster elements from the kwargs meta fields
for key , value in self . meta . items ( ) :
# The meta will merge fields together, i.e. Two 'countries' will be a list, so split these up
if not isinstance ( value , list ) :
value = [ value ]
for v in value :
self . add_cluster_element ( key = key , value = v )
2021-01-30 14:56:40 +01:00
2022-12-01 12:06:57 +01:00
@property
2024-01-30 12:51:23 +01:00
def elements_meta ( self ) - > dict [ str , Any ] :
2022-12-01 12:06:57 +01:00
""" Function to return the galaxy cluster elements as a dictionary structure of lists
that comes from a MISPGalaxy within a MISPEvent . Lossy , you lose the element ID
"""
response = defaultdict ( list )
for element in self . cluster_elements :
response [ element . key ] . append ( element . value )
return dict ( response )
2022-12-01 10:05:38 +01:00
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2022-12-01 12:06:57 +01:00
if ' GalaxyCluster ' in kwargs :
kwargs = kwargs [ ' GalaxyCluster ' ]
self . default = kwargs . pop ( ' default ' , False )
# If the default field is set, we shouldn't have distribution or sharing group ID set
if self . default :
blocked_fields = [ " distribution " " sharing_group_id " ]
for field in blocked_fields :
if kwargs . get ( field , None ) :
raise NewGalaxyClusterError (
f " The field ' { field } ' cannot be set on a default galaxy cluster "
)
2022-12-01 10:05:38 +01:00
2022-12-01 12:06:57 +01:00
self . distribution = int ( kwargs . pop ( ' distribution ' , 0 ) )
if self . distribution not in [ 0 , 1 , 2 , 3 , 4 ] :
raise NewGalaxyClusterError ( f ' { self . distribution } is invalid, the distribution has to be in 0, 1, 2, 3, 4 ' )
2022-12-01 10:05:38 +01:00
2022-12-01 12:06:57 +01:00
if kwargs . get ( ' sharing_group_id ' ) :
self . sharing_group_id = int ( kwargs . pop ( ' sharing_group_id ' ) )
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 NewGalaxyClusterError ( ' 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.
2024-01-17 13:13:14 +01:00
raise NewGalaxyClusterError ( f ' If the distribution is set to sharing group, a sharing group ID is required (cannot be { self . sharing_group_id } ). ' )
2021-01-30 14:56:40 +01:00
2022-12-01 12:06:57 +01:00
if ' uuid ' in kwargs :
self . uuid = kwargs . pop ( ' uuid ' )
if ' meta ' in kwargs :
self . meta = kwargs . pop ( ' meta ' )
if ' Galaxy ' in kwargs :
self . Galaxy = MISPGalaxy ( )
self . Galaxy . from_dict ( * * kwargs . pop ( ' Galaxy ' ) )
if ' GalaxyElement ' in kwargs :
[ self . add_cluster_element ( * * e ) for e in kwargs . pop ( ' GalaxyElement ' ) ]
if ' Org ' in kwargs :
self . Org = MISPOrganisation ( )
self . Org . from_dict ( * * kwargs . pop ( ' Org ' ) )
if ' Orgc ' in kwargs :
self . Orgc = MISPOrganisation ( )
self . Orgc . from_dict ( * * kwargs . pop ( ' Orgc ' ) )
if ' GalaxyClusterRelation ' in kwargs :
[ self . add_cluster_relation ( * * r ) for r in kwargs . pop ( ' GalaxyClusterRelation ' ) ]
if ' SharingGroup ' in kwargs :
2021-01-16 16:56:30 +01:00
self . SharingGroup = MISPSharingGroup ( )
self . SharingGroup . from_dict ( * * kwargs . pop ( ' SharingGroup ' ) )
super ( ) . from_dict ( * * kwargs )
2024-01-30 12:51:23 +01:00
def add_cluster_element ( self , key : str , value : str , * * kwargs ) - > MISPGalaxyClusterElement : # type: ignore[no-untyped-def]
2022-12-01 12:06:57 +01:00
""" Add a cluster relation to a MISPGalaxyCluster, key and value are required
2021-01-30 14:56:40 +01:00
2022-12-01 12:06:57 +01:00
: param key : The key name of the element
: type key : str
: param value : The value of the element
: type value : str
"""
2021-01-16 16:56:30 +01:00
2022-12-01 12:06:57 +01:00
cluster_element = MISPGalaxyClusterElement ( )
cluster_element . from_dict ( key = key , value = value , * * kwargs )
self . cluster_elements . append ( cluster_element )
return cluster_element
2021-01-16 16:56:30 +01:00
2024-01-30 12:51:23 +01:00
def add_cluster_relation ( self , referenced_galaxy_cluster_uuid : MISPGalaxyCluster | str | UUID , referenced_galaxy_cluster_type : str , galaxy_cluster_uuid : str | None = None , * * kwargs : dict [ str , Any ] ) - > MISPGalaxyClusterRelation :
2022-12-01 12:06:57 +01:00
""" Add a cluster relation to a MISPGalaxyCluster.
2022-12-01 10:05:38 +01:00
2022-12-01 12:06:57 +01:00
: param referenced_galaxy_cluster_uuid : UUID of the related cluster
: type referenced_galaxy_cluster_uuid : uuid
: param referenced_galaxy_cluster_type : Relation type
: type referenced_galaxy_cluster_type : uuid
: param galaxy_cluster_uuid : UUID of this cluster , leave blank to use the stored UUID
: param galaxy_cluster_uuid : uuid , Optional
2021-01-16 16:56:30 +01:00
"""
2021-01-30 16:34:29 +01:00
2022-12-01 12:06:57 +01:00
if not getattr ( self , " uuid " , None ) :
raise PyMISPError ( " The cluster does not have a UUID, make sure it is a valid galaxy cluster " )
cluster_relation = MISPGalaxyClusterRelation ( )
2022-12-01 10:05:38 +01:00
2022-12-01 12:06:57 +01:00
if isinstance ( referenced_galaxy_cluster_uuid , MISPGalaxyCluster ) :
referenced_galaxy_cluster_uuid = referenced_galaxy_cluster_uuid . uuid
2022-12-01 10:05:38 +01:00
2022-12-01 12:06:57 +01:00
cluster_relation . from_dict (
referenced_galaxy_cluster_uuid = referenced_galaxy_cluster_uuid ,
referenced_galaxy_cluster_type = referenced_galaxy_cluster_type ,
galaxy_cluster_uuid = galaxy_cluster_uuid or self . uuid ,
* * kwargs
)
self . cluster_relations . append ( cluster_relation )
return cluster_relation
2021-01-16 16:56:30 +01:00
def __repr__ ( self ) - > str :
2022-12-01 12:06:57 +01:00
if hasattr ( self , ' value ' ) :
return ' < {self.__class__.__name__} (value= {self.value} ) ' . format ( self = self )
2024-01-17 13:13:14 +01:00
return f ' < { self . __class__ . __name__ } (NotInitialized) '
2021-01-16 16:56:30 +01:00
2022-12-01 12:06:57 +01:00
class MISPGalaxy ( AbstractMISP ) :
""" Galaxy class, used to view a galaxy and respective clusters """
2021-01-16 16:56:30 +01:00
2022-12-01 12:06:57 +01:00
def __init__ ( self ) - > None :
super ( ) . __init__ ( )
2024-01-17 13:13:14 +01:00
self . GalaxyCluster : list [ MISPGalaxyCluster ] = [ ]
2022-12-01 12:06:57 +01:00
self . name : str
2021-01-16 16:56:30 +01:00
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2022-12-01 12:06:57 +01:00
""" Galaxy could be in one of the following formats:
{ ' Galaxy ' : { } , ' GalaxyCluster ' : [ ] }
{ ' Galaxy ' : { ' GalaxyCluster ' : [ ] } }
"""
2021-01-16 16:56:30 +01:00
2022-12-01 12:06:57 +01:00
if ' GalaxyCluster ' in kwargs and kwargs . get ( " withCluster " , True ) :
# Parse the cluster from the kwargs
[ self . add_galaxy_cluster ( * * e ) for e in kwargs . pop ( ' GalaxyCluster ' ) ]
2021-01-16 16:56:30 +01:00
2022-12-01 12:06:57 +01:00
if ' Galaxy ' in kwargs :
kwargs = kwargs [ ' Galaxy ' ]
super ( ) . from_dict ( * * kwargs )
2021-01-16 16:56:30 +01:00
2022-12-01 12:06:57 +01:00
@property
2024-01-17 13:13:14 +01:00
def clusters ( self ) - > list [ MISPGalaxyCluster ] :
2022-12-01 12:06:57 +01:00
return self . GalaxyCluster
2022-12-01 10:05:38 +01:00
2024-01-30 12:51:23 +01:00
def add_galaxy_cluster ( self , * * kwargs ) - > MISPGalaxyCluster : # type: ignore[no-untyped-def]
2022-12-01 12:06:57 +01:00
""" Add a MISP galaxy cluster into a MISPGalaxy.
Supports all other parameters supported by MISPGalaxyCluster """
2022-12-01 10:05:38 +01:00
2022-12-01 12:06:57 +01:00
galaxy_cluster = MISPGalaxyCluster ( )
galaxy_cluster . from_dict ( * * kwargs )
self . clusters . append ( galaxy_cluster )
return galaxy_cluster
2021-01-16 16:56:30 +01:00
def __repr__ ( self ) - > str :
if hasattr ( self , ' name ' ) :
return ' < {self.__class__.__name__} (name= {self.name} ) ' . format ( self = self )
2024-01-17 13:13:14 +01:00
return f ' < { self . __class__ . __name__ } (NotInitialized) '
2021-01-16 16:56:30 +01:00
2019-12-18 14:45:14 +01:00
class MISPEvent ( AbstractMISP ) :
2018-04-25 11:06:03 +02:00
2024-01-30 12:51:23 +01:00
_fields_for_feed : set [ str ] = { ' uuid ' , ' info ' , ' threat_level_id ' , ' analysis ' , ' timestamp ' ,
' publish_timestamp ' , ' published ' , ' date ' , ' extends_uuid ' }
2019-07-12 17:35:02 +02:00
2024-01-30 12:51:23 +01:00
def __init__ ( self , describe_types : dict [ str , Any ] | None = None , strict_validation : bool = False , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
super ( ) . __init__ ( * * kwargs )
2021-06-28 18:21:09 +02:00
self . __schema_file = ' schema.json ' if strict_validation else ' schema-lax.json '
2019-12-18 14:45:14 +01:00
if describe_types :
# This variable is used in add_attribute in order to avoid duplicating the structure
self . describe_types = describe_types
2019-07-12 17:35:02 +02:00
2020-02-21 14:12:36 +01:00
self . uuid : str = str ( uuid . uuid4 ( ) )
2020-01-27 19:07:40 +01:00
self . date : date
2024-01-17 13:13:14 +01:00
self . Attribute : list [ MISPAttribute ] = [ ]
self . Object : list [ MISPObject ] = [ ]
self . RelatedEvent : list [ MISPEvent ] = [ ]
self . ShadowAttribute : list [ MISPShadowAttribute ] = [ ]
2020-01-23 10:27:40 +01:00
self . SharingGroup : MISPSharingGroup
2024-01-17 13:13:14 +01:00
self . EventReport : list [ MISPEventReport ] = [ ]
self . Tag : list [ MISPTag ] = [ ]
self . Galaxy : list [ MISPGalaxy ] = [ ]
2020-01-23 10:27:40 +01:00
2024-01-30 12:51:23 +01:00
self . publish_timestamp : float | int | datetime
self . timestamp : float | int | datetime
def add_tag ( self , tag : str | MISPTag | dict [ str , Any ] | None = None , * * kwargs ) - > MISPTag : # type: ignore[no-untyped-def]
2020-01-23 10:27:40 +01:00
return super ( ) . _add_tag ( tag , * * kwargs )
@property
2024-01-17 13:13:14 +01:00
def tags ( self ) - > list [ MISPTag ] :
2020-10-27 16:14:06 +01:00
""" Returns a list of tags associated to this Event """
2020-01-23 10:27:40 +01:00
return self . Tag
@tags.setter
2024-01-30 12:51:23 +01:00
def tags ( self , tags : list [ MISPTag ] ) - > None :
2020-01-23 10:27:40 +01:00
""" Set a list of prepared MISPTag. """
super ( ) . _set_tags ( tags )
2019-07-12 17:35:02 +02:00
2024-01-30 12:51:23 +01:00
def _set_default ( self ) - > None :
2019-12-18 14:45:14 +01:00
""" There are a few keys that could, or need to be set by default for the feed generator """
if not hasattr ( self , ' published ' ) :
self . published = True
if not hasattr ( self , ' uuid ' ) :
self . uuid = str ( uuid . uuid4 ( ) )
if not hasattr ( self , ' extends_uuid ' ) :
self . extends_uuid = ' '
if not hasattr ( self , ' date ' ) :
2020-01-27 19:07:40 +01:00
self . set_date ( date . today ( ) )
2019-12-18 14:45:14 +01:00
if not hasattr ( self , ' timestamp ' ) :
2020-01-27 19:07:40 +01:00
self . timestamp = datetime . timestamp ( datetime . now ( ) )
2019-12-18 14:45:14 +01:00
if not hasattr ( self , ' publish_timestamp ' ) :
2020-01-27 19:07:40 +01:00
self . publish_timestamp = datetime . timestamp ( datetime . now ( ) )
2019-12-18 14:45:14 +01:00
if not hasattr ( self , ' analysis ' ) :
# analysis: 0 means initial, 1 ongoing, 2 completed
self . analysis = 2
if not hasattr ( self , ' threat_level_id ' ) :
# threat_level_id 4 means undefined. Tags are recommended.
self . threat_level_id = 4
2019-07-12 17:35:02 +02:00
2019-12-18 14:45:14 +01:00
@property
2024-01-30 12:51:23 +01:00
def manifest ( self ) - > dict [ str , Any ] :
2019-12-18 14:45:14 +01:00
required = [ ' info ' , ' Orgc ' ]
for r in required :
if not hasattr ( self , r ) :
raise PyMISPError ( ' The field {} is required to generate the event manifest. ' )
2019-07-12 17:35:02 +02:00
2019-12-18 14:45:14 +01:00
self . _set_default ( )
2019-07-12 17:35:02 +02:00
2019-12-18 14:45:14 +01:00
return {
self . uuid : {
' Orgc ' : self . Orgc . _to_feed ( ) ,
' Tag ' : list ( filter ( None , [ tag . _to_feed ( ) for tag in self . tags ] ) ) ,
' info ' : self . info ,
' date ' : self . date . isoformat ( ) ,
' analysis ' : self . analysis ,
' threat_level_id ' : self . threat_level_id ,
' timestamp ' : self . _datetime_to_timestamp ( self . timestamp )
}
}
2019-07-12 17:35:02 +02:00
2024-01-17 13:13:14 +01:00
def attributes_hashes ( self , algorithm : str = ' sha512 ' ) - > list [ str ] :
to_return : list [ str ] = [ ]
2019-12-18 14:45:14 +01:00
for attribute in self . attributes :
to_return + = attribute . hash_values ( algorithm )
for obj in self . objects :
for attribute in obj . attributes :
to_return + = attribute . hash_values ( algorithm )
return to_return
2019-07-12 17:35:02 +02:00
2024-01-30 12:51:23 +01:00
def to_feed ( self , valid_distributions : list [ int ] = [ 0 , 1 , 2 , 3 , 4 , 5 ] , with_meta : bool = False , with_distribution : bool = False , with_local_tags : bool = True , with_event_reports : bool = True ) - > dict [ str , Any ] :
2019-12-18 14:45:14 +01:00
""" Generate a json output for MISP Feed.
2020-10-27 16:14:06 +01:00
: param valid_distributions : only makes sense if the distribution key is set ; i . e . , the event is exported from a MISP instance .
2021-11-29 15:54:34 +01:00
: param with_distribution : exports distribution and Sharing Group info ; otherwise all SharingGroup information is discarded ( protecting privacy )
2022-01-19 22:30:30 +01:00
: param with_local_tags : tag export includes local exportable tags along with global exportable tags
2023-03-15 03:32:45 +01:00
: param with_event_reports : include event reports in the returned MISP event
2019-12-18 14:45:14 +01:00
"""
required = [ ' info ' , ' Orgc ' ]
for r in required :
if not hasattr ( self , r ) :
2020-01-28 14:12:39 +01:00
raise PyMISPError ( f ' The field { r } is required to generate the event feed output. ' )
2019-07-12 17:35:02 +02:00
2019-12-18 14:45:14 +01:00
if ( hasattr ( self , ' distribution ' )
and self . distribution is not None
and int ( self . distribution ) not in valid_distributions ) :
2020-01-02 15:55:00 +01:00
return { }
2019-07-12 17:35:02 +02:00
2021-11-29 15:54:34 +01:00
if with_distribution :
self . _fields_for_feed . add ( ' distribution ' )
2019-12-18 14:45:14 +01:00
to_return = super ( ) . _to_feed ( )
if with_meta :
to_return [ ' _hashes ' ] = [ ]
to_return [ ' _manifest ' ] = self . manifest
2019-07-12 17:35:02 +02:00
2019-12-18 14:45:14 +01:00
to_return [ ' Orgc ' ] = self . Orgc . _to_feed ( )
2022-01-19 22:30:30 +01:00
to_return [ ' Tag ' ] = list ( filter ( None , [ tag . _to_feed ( with_local_tags ) for tag in self . tags ] ) )
2019-12-18 14:45:14 +01:00
if self . attributes :
to_return [ ' Attribute ' ] = [ ]
for attribute in self . attributes :
if ( valid_distributions and attribute . get ( ' distribution ' ) is not None and attribute . distribution not in valid_distributions ) :
continue
2021-11-29 15:54:34 +01:00
to_return [ ' Attribute ' ] . append ( attribute . _to_feed ( with_distribution = with_distribution ) )
2019-12-18 14:45:14 +01:00
if with_meta :
to_return [ ' _hashes ' ] + = attribute . hash_values ( ' md5 ' )
2019-07-12 17:35:02 +02:00
2019-12-18 14:45:14 +01:00
if self . objects :
to_return [ ' Object ' ] = [ ]
for obj in self . objects :
if ( valid_distributions and obj . get ( ' distribution ' ) is not None and obj . distribution not in valid_distributions ) :
continue
2021-11-29 15:54:34 +01:00
if with_distribution :
obj . _fields_for_feed . add ( ' distribution ' )
obj_to_attach = obj . _to_feed ( with_distribution = with_distribution )
2019-12-18 14:45:14 +01:00
obj_to_attach [ ' Attribute ' ] = [ ]
for attribute in obj . attributes :
if ( valid_distributions and attribute . get ( ' distribution ' ) is not None and attribute . distribution not in valid_distributions ) :
continue
2021-11-29 15:54:34 +01:00
obj_to_attach [ ' Attribute ' ] . append ( attribute . _to_feed ( with_distribution = with_distribution ) )
2019-12-18 14:45:14 +01:00
if with_meta :
to_return [ ' _hashes ' ] + = attribute . hash_values ( ' md5 ' )
to_return [ ' Object ' ] . append ( obj_to_attach )
2019-07-12 17:35:02 +02:00
2021-11-29 15:54:34 +01:00
if with_distribution :
try :
to_return [ ' SharingGroup ' ] = self . SharingGroup . _to_feed ( )
except AttributeError :
pass
2023-03-15 03:32:45 +01:00
if with_event_reports and self . event_reports :
2023-03-15 03:27:59 +01:00
to_return [ ' EventReport ' ] = [ ]
for event_report in self . event_reports :
if ( valid_distributions and event_report . get ( ' distribution ' ) is not None and event_report . distribution not in valid_distributions ) :
continue
if not with_distribution :
event_report . pop ( ' distribution ' , None )
event_report . pop ( ' SharingGroup ' , None )
event_report . pop ( ' sharing_group_id ' , None )
to_return [ ' EventReport ' ] . append ( event_report . to_dict ( ) )
2023-03-17 15:31:20 +01:00
2019-12-18 14:45:14 +01:00
return { ' Event ' : to_return }
2019-07-12 17:35:02 +02:00
2019-12-18 14:45:14 +01:00
@property
2024-01-17 13:13:14 +01:00
def known_types ( self ) - > list [ str ] :
2019-12-18 14:45:14 +01:00
return self . describe_types [ ' types ' ]
2019-07-12 17:35:02 +02:00
2019-12-18 14:45:14 +01:00
@property
def org ( self ) - > MISPOrganisation :
return self . Org
2019-07-12 17:35:02 +02:00
2019-12-18 14:45:14 +01:00
@property
def orgc ( self ) - > MISPOrganisation :
return self . Orgc
2019-07-12 17:35:02 +02:00
2019-12-18 14:45:14 +01:00
@orgc.setter
2024-01-30 12:51:23 +01:00
def orgc ( self , orgc : MISPOrganisation ) - > None :
2019-12-18 14:45:14 +01:00
if isinstance ( orgc , MISPOrganisation ) :
self . Orgc = orgc
else :
raise PyMISPError ( ' Orgc must be of type MISPOrganisation. ' )
2019-07-12 17:35:02 +02:00
2019-12-18 14:45:14 +01:00
@property
2024-01-17 13:13:14 +01:00
def attributes ( self ) - > list [ MISPAttribute ] :
2019-12-18 14:45:14 +01:00
return self . Attribute
2019-07-12 17:35:02 +02:00
2019-12-18 14:45:14 +01:00
@attributes.setter
2024-01-30 12:51:23 +01:00
def attributes ( self , attributes : list [ MISPAttribute ] ) - > None :
2019-12-18 14:45:14 +01:00
if all ( isinstance ( x , MISPAttribute ) for x in attributes ) :
self . Attribute = attributes
else :
raise PyMISPError ( ' All the attributes have to be of type MISPAttribute. ' )
2019-07-12 17:35:02 +02:00
2021-01-12 16:13:32 +01:00
@property
2024-01-17 13:13:14 +01:00
def event_reports ( self ) - > list [ MISPEventReport ] :
2021-01-12 16:13:32 +01:00
return self . EventReport
2019-12-18 14:45:14 +01:00
@property
2024-01-17 13:13:14 +01:00
def shadow_attributes ( self ) - > list [ MISPShadowAttribute ] :
2019-12-18 14:45:14 +01:00
return self . ShadowAttribute
2019-07-12 17:35:02 +02:00
2019-12-18 14:45:14 +01:00
@shadow_attributes.setter
2024-01-30 12:51:23 +01:00
def shadow_attributes ( self , shadow_attributes : list [ MISPShadowAttribute ] ) - > None :
2019-12-18 14:45:14 +01:00
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. ' )
2019-07-12 17:35:02 +02:00
2019-12-18 14:45:14 +01:00
@property
2024-01-17 13:13:14 +01:00
def related_events ( self ) - > list [ MISPEvent ] :
2019-12-18 14:45:14 +01:00
return self . RelatedEvent
2019-07-12 17:35:02 +02:00
2021-01-16 16:56:30 +01:00
@property
2024-01-17 13:13:14 +01:00
def galaxies ( self ) - > list [ MISPGalaxy ] :
2021-01-16 16:56:30 +01:00
return self . Galaxy
2023-07-03 04:57:52 +02:00
@galaxies.setter
2024-01-30 12:51:23 +01:00
def galaxies ( self , galaxies : list [ MISPGalaxy ] ) - > None :
2023-07-03 04:57:52 +02:00
if all ( isinstance ( x , MISPGalaxy ) for x in galaxies ) :
self . Galaxy = galaxies
else :
raise PyMISPError ( ' All the attributes have to be of type MISPGalaxy. ' )
2021-03-02 12:21:59 +01:00
@property
2024-01-17 13:13:14 +01:00
def objects ( self ) - > list [ MISPObject ] :
2021-03-02 12:21:59 +01:00
return self . Object
2019-12-18 14:45:14 +01:00
@objects.setter
2024-01-30 12:51:23 +01:00
def objects ( self , objects : list [ MISPObject ] ) - > None :
2019-12-18 14:45:14 +01:00
if all ( isinstance ( x , MISPObject ) for x in objects ) :
self . Object = objects
else :
raise PyMISPError ( ' All the attributes have to be of type MISPObject. ' )
2019-07-12 17:35:02 +02:00
2024-01-30 12:51:23 +01:00
def load_file ( self , event_path : Path | str , validate : bool = False , metadata_only : bool = False ) - > None :
2019-12-18 14:45:14 +01:00
""" Load a JSON dump from a file on the disk """
if not os . path . exists ( event_path ) :
raise PyMISPError ( ' Invalid path, unable to load the event. ' )
with open ( event_path , ' rb ' ) as f :
self . load ( f , validate , metadata_only )
2019-07-12 17:35:02 +02:00
2024-01-30 12:51:23 +01:00
def load ( self , json_event : IO [ bytes ] | IO [ str ] | str | bytes | dict [ str , Any ] , validate : bool = False , metadata_only : bool = False ) - > None :
2019-12-18 14:45:14 +01:00
""" Load a JSON dump from a pseudo file or a JSON string """
2020-11-25 13:34:13 +01:00
if isinstance ( json_event , ( BufferedIOBase , TextIOBase ) ) :
2024-01-17 13:13:14 +01:00
json_event = json_event . read ( )
2020-11-25 13:19:19 +01:00
2019-12-18 14:45:14 +01:00
if isinstance ( json_event , ( str , bytes ) ) :
json_event = json . loads ( json_event )
2020-01-23 10:27:40 +01:00
if isinstance ( json_event , dict ) and ' response ' in json_event and isinstance ( json_event [ ' response ' ] , list ) :
event = json_event [ ' response ' ] [ 0 ]
2019-12-18 14:45:14 +01:00
else :
event = json_event
if not event :
raise PyMISPError ( ' Invalid event ' )
if metadata_only :
event . pop ( ' Attribute ' , None )
event . pop ( ' Object ' , None )
self . from_dict ( * * event )
if validate :
2024-01-05 21:18:44 +01:00
warnings . warn ( ' The validate parameter is deprecated because PyMISP is more flexible at loading event than the schema ' )
2019-07-12 17:35:02 +02:00
2024-01-30 12:51:23 +01:00
def __setattr__ ( self , name : str , value : Any ) - > None :
2020-01-27 19:07:40 +01:00
if name in [ ' date ' ] :
if isinstance ( value , date ) :
pass
elif isinstance ( value , str ) :
2024-01-17 13:13:14 +01:00
try :
# faster
value = date . fromisoformat ( value )
except Exception :
2020-01-27 20:14:14 +01:00
value = parse ( value ) . date ( )
2020-01-27 19:07:40 +01:00
elif isinstance ( value , ( int , float ) ) :
value = date . fromtimestamp ( value )
elif isinstance ( value , datetime ) :
value = value . date ( )
else :
raise NewEventError ( f ' Invalid format for the date: { type ( value ) } - { value } ' )
super ( ) . __setattr__ ( name , value )
2024-01-30 12:51:23 +01:00
def set_date ( self , d : str | int | float | datetime | date | None = None , ignore_invalid : bool = False ) - > None :
2020-10-27 16:14:06 +01:00
""" Set a date for the event
: param d : String , datetime , or date object
: param ignore_invalid : if True , assigns current date if d is not an expected type
"""
2020-01-27 19:07:40 +01:00
if isinstance ( d , ( str , int , float , datetime , date ) ) :
2020-01-28 14:12:39 +01:00
self . date = d # type: ignore
2020-01-27 19:07:40 +01:00
elif ignore_invalid :
self . date = date . today ( )
2019-12-18 14:45:14 +01:00
else :
2020-01-27 19:07:40 +01:00
raise NewEventError ( f ' Invalid format for the date: { type ( d ) } - { d } ' )
2019-07-12 17:35:02 +02:00
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
if ' Event ' in kwargs :
kwargs = kwargs [ ' Event ' ]
# Required value
self . info = kwargs . pop ( ' info ' , None )
if self . info is None :
raise NewEventError ( ' The info field of the new event is required. ' )
2018-04-25 11:06:03 +02:00
2019-12-18 14:45:14 +01:00
# Default values for a valid event to send to a MISP instance
self . distribution = kwargs . pop ( ' distribution ' , None )
if self . distribution is not None :
self . distribution = int ( self . distribution )
if self . distribution not in [ 0 , 1 , 2 , 3 , 4 ] :
2020-02-05 13:00:14 +01:00
raise NewEventError ( f ' { self . info } : { self . distribution } is invalid, the distribution has to be in 0, 1, 2, 3, 4 ' )
2018-11-22 14:29:07 +01:00
2019-12-18 14:45:14 +01:00
if kwargs . get ( ' threat_level_id ' ) is not None :
self . threat_level_id = int ( kwargs . pop ( ' threat_level_id ' ) )
if self . threat_level_id not in [ 1 , 2 , 3 , 4 ] :
2020-02-05 13:00:14 +01:00
raise NewEventError ( f ' { self . info } : { self . threat_level_id } is invalid, the threat_level_id has to be in 1, 2, 3, 4 ' )
2018-11-22 14:29:07 +01:00
2019-12-18 14:45:14 +01:00
if kwargs . get ( ' analysis ' ) is not None :
self . analysis = int ( kwargs . pop ( ' analysis ' ) )
if self . analysis not in [ 0 , 1 , 2 ] :
2020-02-05 13:00:14 +01:00
raise NewEventError ( f ' { self . info } : { self . analysis } is invalid, the analysis has to be in 0, 1, 2 ' )
2019-07-12 17:35:02 +02:00
2019-12-18 14:45:14 +01:00
self . published = kwargs . pop ( ' published ' , None )
if self . published is True :
self . publish ( )
else :
self . unpublish ( )
2018-11-22 14:29:07 +01:00
2019-12-18 14:45:14 +01:00
if kwargs . get ( ' date ' ) :
self . set_date ( kwargs . pop ( ' date ' ) )
if kwargs . get ( ' Attribute ' ) :
[ self . add_attribute ( * * a ) for a in kwargs . pop ( ' Attribute ' ) ]
2021-01-16 16:56:30 +01:00
if kwargs . get ( ' Galaxy ' ) :
[ self . add_galaxy ( * * e ) for e in kwargs . pop ( ' Galaxy ' ) ]
2021-01-12 16:13:32 +01:00
if kwargs . get ( ' EventReport ' ) :
[ self . add_event_report ( * * e ) for e in kwargs . pop ( ' EventReport ' ) ]
2018-11-22 14:29:07 +01:00
2019-12-18 14:45:14 +01:00
# All other keys
if kwargs . get ( ' id ' ) :
self . id = int ( kwargs . pop ( ' id ' ) )
if kwargs . get ( ' orgc_id ' ) :
self . orgc_id = int ( kwargs . pop ( ' orgc_id ' ) )
if kwargs . get ( ' org_id ' ) :
self . org_id = int ( kwargs . pop ( ' org_id ' ) )
if kwargs . get ( ' timestamp ' ) :
2020-01-27 19:07:40 +01:00
self . timestamp = datetime . fromtimestamp ( int ( kwargs . pop ( ' timestamp ' ) ) , timezone . utc )
2019-12-18 14:45:14 +01:00
if kwargs . get ( ' publish_timestamp ' ) :
2020-01-27 19:07:40 +01:00
self . publish_timestamp = datetime . fromtimestamp ( int ( kwargs . pop ( ' publish_timestamp ' ) ) , timezone . utc )
2019-12-18 14:45:14 +01:00
if kwargs . get ( ' sighting_timestamp ' ) :
2020-01-27 19:07:40 +01:00
self . sighting_timestamp = datetime . fromtimestamp ( int ( kwargs . pop ( ' sighting_timestamp ' ) ) , timezone . utc )
2019-12-18 14:45:14 +01:00
if kwargs . get ( ' sharing_group_id ' ) :
self . sharing_group_id = int ( kwargs . pop ( ' sharing_group_id ' ) )
if kwargs . get ( ' RelatedEvent ' ) :
for rel_event in kwargs . pop ( ' RelatedEvent ' ) :
sub_event = MISPEvent ( )
sub_event . load ( rel_event )
2024-01-30 12:51:23 +01:00
self . RelatedEvent . append ( { ' Event ' : sub_event } ) # type: ignore[arg-type]
2019-12-18 14:45:14 +01:00
if kwargs . get ( ' Tag ' ) :
[ self . add_tag ( tag ) for tag in kwargs . pop ( ' Tag ' ) ]
if kwargs . get ( ' Object ' ) :
[ self . add_object ( obj ) for obj in kwargs . pop ( ' Object ' ) ]
if kwargs . get ( ' Org ' ) :
self . Org = MISPOrganisation ( )
self . Org . from_dict ( * * kwargs . pop ( ' Org ' ) )
if kwargs . get ( ' Orgc ' ) :
self . Orgc = MISPOrganisation ( )
self . Orgc . from_dict ( * * kwargs . pop ( ' Orgc ' ) )
2020-01-23 10:27:40 +01:00
if kwargs . get ( ' SharingGroup ' ) :
self . SharingGroup = MISPSharingGroup ( )
self . SharingGroup . from_dict ( * * kwargs . pop ( ' SharingGroup ' ) )
2021-01-14 19:58:35 +01:00
2024-01-17 13:13:14 +01:00
super ( ) . from_dict ( * * kwargs )
2019-08-26 16:24:39 +02:00
2024-01-30 12:51:23 +01:00
def to_dict ( self , json_format : bool = False ) - > dict [ str , Any ] :
2021-06-29 12:38:52 +02:00
to_return = super ( ) . to_dict ( json_format )
2019-08-26 16:24:39 +02:00
2019-12-18 14:45:14 +01:00
if to_return . get ( ' date ' ) :
2020-01-27 19:07:40 +01:00
if isinstance ( self . date , datetime ) :
2019-12-18 14:45:14 +01:00
self . date = self . date . date ( )
to_return [ ' date ' ] = self . date . isoformat ( )
if to_return . get ( ' publish_timestamp ' ) :
2024-01-09 12:40:09 +01:00
to_return [ ' publish_timestamp ' ] = str ( self . _datetime_to_timestamp ( self . publish_timestamp ) )
2019-12-18 14:45:14 +01:00
if to_return . get ( ' sighting_timestamp ' ) :
2024-01-09 12:40:09 +01:00
to_return [ ' sighting_timestamp ' ] = str ( self . _datetime_to_timestamp ( self . sighting_timestamp ) )
2019-08-26 16:24:39 +02:00
2019-12-18 14:45:14 +01:00
return to_return
2019-08-26 16:24:39 +02:00
2024-01-30 12:51:23 +01:00
def add_proposal ( self , shadow_attribute = None , * * kwargs ) - > MISPShadowAttribute : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
""" Alias for add_shadow_attribute """
return self . add_shadow_attribute ( shadow_attribute , * * kwargs )
2017-12-26 17:13:57 +01:00
2024-01-30 12:51:23 +01:00
def add_shadow_attribute ( self , shadow_attribute = None , * * kwargs ) - > MISPShadowAttribute : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
""" 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 :
2024-01-30 12:51:23 +01:00
raise PyMISPError ( f " The shadow_attribute is in an invalid format (can be either string, MISPShadowAttribute, or an expanded dict[str, Any]): { shadow_attribute } " )
2019-12-18 14:45:14 +01:00
self . shadow_attributes . append ( misp_shadow_attribute )
self . edited = True
return misp_shadow_attribute
2017-12-26 17:13:57 +01:00
2024-01-17 13:13:14 +01:00
def get_attribute_tag ( self , attribute_identifier : str ) - > list [ MISPTag ] :
2019-12-18 14:45:14 +01:00
""" Return the tags associated to an attribute or an object attribute.
2020-10-27 16:14:06 +01:00
: param attribute_identifier : can be an ID , UUID , or the value .
2017-12-26 17:13:57 +01:00
"""
2024-01-17 13:13:14 +01:00
tags : list [ MISPTag ] = [ ]
2019-12-18 14:45:14 +01:00
for a in self . attributes + [ attribute for o in self . objects for attribute in o . attributes ] :
2024-01-30 12:51:23 +01:00
if ( ( hasattr ( a , ' id ' ) and str ( a . id ) == attribute_identifier )
2019-12-18 14:45:14 +01:00
or ( hasattr ( a , ' uuid ' ) and a . uuid == attribute_identifier )
or ( hasattr ( a , ' value ' ) and attribute_identifier == a . value
2020-03-13 12:02:03 +01:00
or ( isinstance ( a . value , str ) and attribute_identifier in a . value . split ( ' | ' ) ) ) ) :
2019-12-18 14:45:14 +01:00
tags + = a . tags
return tags
2017-08-30 12:47:32 +02:00
2024-01-17 13:13:14 +01:00
def add_attribute_tag ( self , tag : MISPTag | str , attribute_identifier : str ) - > list [ MISPAttribute ] :
2020-10-27 16:14:06 +01:00
""" Add a tag to an existing attribute. Raise an Exception if the attribute doesn ' t exist.
: param tag : Tag name as a string , MISPTag instance , or dictionary
: param attribute_identifier : can be an ID , UUID , or the value .
2019-12-18 14:45:14 +01:00
"""
attributes = [ ]
for a in self . attributes + [ attribute for o in self . objects for attribute in o . attributes ] :
2024-01-30 12:51:23 +01:00
if ( ( hasattr ( a , ' id ' ) and str ( a . id ) == attribute_identifier )
2019-12-18 14:45:14 +01:00
or ( hasattr ( a , ' uuid ' ) and a . uuid == attribute_identifier )
or ( hasattr ( a , ' value ' ) and attribute_identifier == a . value
2020-03-13 11:02:48 +01:00
or ( isinstance ( a . value , str ) and attribute_identifier in a . value . split ( ' | ' ) ) ) ) :
2019-12-18 14:45:14 +01:00
a . add_tag ( tag )
attributes . append ( a )
2017-08-30 12:47:32 +02:00
2019-12-18 14:45:14 +01:00
if not attributes :
2024-01-17 13:13:14 +01:00
raise PyMISPError ( f ' No attribute with identifier { attribute_identifier } found. ' )
2019-12-18 14:45:14 +01:00
self . edited = True
return attributes
2017-12-22 14:49:14 +01:00
2024-01-17 13:13:14 +01:00
def publish ( self ) - > None :
2019-12-18 14:45:14 +01:00
""" Mark the attribute as published """
self . published = True
2017-08-30 12:47:32 +02:00
2024-01-30 12:51:23 +01:00
def unpublish ( self ) - > None :
2019-12-18 14:45:14 +01:00
""" Mark the attribute as un-published (set publish flag to false) """
self . published = False
2018-01-03 14:36:10 +01:00
2024-01-30 12:51:23 +01:00
def delete_attribute ( self , attribute_id : str ) - > None :
2020-10-27 16:14:06 +01:00
""" Delete an attribute
: param attribute_id : ID or UUID
"""
2019-12-18 14:45:14 +01:00
for a in self . attributes :
2024-01-30 12:51:23 +01:00
if ( ( hasattr ( a , ' id ' ) and str ( a . id ) == attribute_id )
2019-12-18 14:45:14 +01:00
or ( hasattr ( a , ' uuid ' ) and a . uuid == attribute_id ) ) :
a . delete ( )
break
2020-05-14 12:43:10 +02:00
else :
2024-01-17 13:13:14 +01:00
raise PyMISPError ( f ' No attribute with UUID/ID { attribute_id } found. ' )
2018-01-03 14:36:10 +01:00
2024-01-30 12:51:23 +01:00
def add_attribute ( self , type : str , value : str | int | float , * * kwargs ) - > MISPAttribute | list [ MISPAttribute ] : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
""" Add an attribute. type and value are required but you can pass all
other parameters supported by MISPAttribute """
2024-01-17 13:13:14 +01:00
attr_list : list [ MISPAttribute ] = [ ]
2019-12-18 14:45:14 +01:00
if isinstance ( value , list ) :
attr_list = [ self . add_attribute ( type = type , value = a , * * kwargs ) for a in value ]
else :
attribute = MISPAttribute ( describe_types = self . describe_types )
attribute . from_dict ( type = type , value = value , * * kwargs )
self . attributes . append ( attribute )
self . edited = True
if attr_list :
return attr_list
return attribute
2019-04-11 23:13:15 +02:00
2024-01-30 12:51:23 +01:00
def add_event_report ( self , name : str , content : str , * * kwargs ) - > MISPEventReport : # type: ignore[no-untyped-def]
2021-01-12 16:13:32 +01:00
""" Add an event report. name and value are requred but you can pass all
other parameters supported by MISPEventReport """
event_report = MISPEventReport ( )
event_report . from_dict ( name = name , content = content , * * kwargs )
self . event_reports . append ( event_report )
2021-01-14 19:58:35 +01:00
self . edited = True
2021-01-12 16:13:32 +01:00
return event_report
2024-01-30 12:51:23 +01:00
def add_galaxy ( self , galaxy : MISPGalaxy | dict [ str , Any ] | None = None , * * kwargs ) - > MISPGalaxy : # type: ignore[no-untyped-def]
2022-12-01 10:05:38 +01:00
""" Add a galaxy and sub-clusters into an event, either by passing
a MISPGalaxy or a dictionary .
2021-01-16 16:56:30 +01:00
Supports all other parameters supported by MISPGalaxy """
2022-12-01 10:05:38 +01:00
if isinstance ( galaxy , MISPGalaxy ) :
self . galaxies . append ( galaxy )
return galaxy
if isinstance ( galaxy , dict ) :
misp_galaxy = MISPGalaxy ( )
misp_galaxy . from_dict ( * * galaxy )
elif kwargs :
misp_galaxy = MISPGalaxy ( )
misp_galaxy . from_dict ( * * kwargs )
else :
raise InvalidMISPGalaxy ( " A Galaxy to add to an existing Event needs to be either a MISPGalaxy or a plain python dictionary " )
self . galaxies . append ( misp_galaxy )
return misp_galaxy
2021-01-16 16:56:30 +01:00
2024-01-17 13:13:14 +01:00
def get_object_by_id ( self , object_id : str | int ) - > MISPObject :
2020-10-27 16:14:06 +01:00
""" Get an object by ID
: param object_id : the ID is the one set by the server when creating the new object """
2019-12-18 14:45:14 +01:00
for obj in self . objects :
if hasattr ( obj , ' id ' ) and int ( obj . id ) == int ( object_id ) :
return obj
2024-01-17 13:13:14 +01:00
raise InvalidMISPObject ( f ' Object with { object_id } does not exist in this event ' )
2019-07-12 17:35:02 +02:00
2019-12-18 14:45:14 +01:00
def get_object_by_uuid ( self , object_uuid : str ) - > MISPObject :
2020-10-27 16:14:06 +01:00
""" Get an object by UUID
: param object_uuid : the UUID is set by the server when creating the new object """
2019-12-18 14:45:14 +01:00
for obj in self . objects :
if hasattr ( obj , ' uuid ' ) and obj . uuid == object_uuid :
return obj
2024-01-17 13:13:14 +01:00
raise InvalidMISPObject ( f ' Object with { object_uuid } does not exist in this event ' )
2018-01-03 14:36:10 +01:00
2024-01-17 13:13:14 +01:00
def get_objects_by_name ( self , object_name : str ) - > list [ MISPObject ] :
2020-10-27 16:14:06 +01:00
""" Get objects by name
: param object_name : name is set by the server when creating the new object """
2019-12-18 14:45:14 +01:00
objects = [ ]
for obj in self . objects :
if hasattr ( obj , ' uuid ' ) and obj . name == object_name :
objects . append ( obj )
return objects
2019-08-29 18:08:53 +02:00
2024-01-30 12:51:23 +01:00
def add_object ( self , obj : MISPObject | dict [ str , Any ] | None = None , * * kwargs ) - > MISPObject : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
""" Add an object to the Event, either by passing a MISPObject, or a dictionary """
if isinstance ( obj , MISPObject ) :
misp_obj = obj
elif isinstance ( obj , dict ) :
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 )
else :
raise InvalidMISPObject ( " An object to add to an existing Event needs to be either a MISPObject, or a plain python dictionary " )
2020-06-30 12:36:19 +02:00
misp_obj . standalone = False
2019-12-18 14:45:14 +01:00
self . Object . append ( misp_obj )
self . edited = True
return misp_obj
2019-08-29 18:08:53 +02:00
2024-01-30 12:51:23 +01:00
def delete_object ( self , object_id : str ) - > None :
2021-02-26 17:55:08 +01:00
""" Delete an object
: param object_id : ID or UUID
"""
for o in self . objects :
2024-01-17 13:13:14 +01:00
if ( ( hasattr ( o , ' id ' ) and int ( o . id ) == int ( object_id ) )
2021-02-26 17:55:08 +01:00
or ( hasattr ( o , ' uuid ' ) and o . uuid == object_id ) ) :
o . delete ( )
break
else :
2024-01-17 13:13:14 +01:00
raise PyMISPError ( f ' No object with UUID/ID { object_id } found. ' )
2021-02-26 17:55:08 +01:00
2024-01-30 12:51:23 +01:00
def run_expansions ( self ) - > None :
2019-12-18 14:45:14 +01:00
for index , attribute in enumerate ( self . attributes ) :
if ' expand ' not in attribute :
continue
# NOTE: Always make sure the attribute with the expand key is either completely removed,
# of the key is deleted to avoid seeing it processed again on MISP side
elif attribute . expand == ' binary ' :
try :
from . tools import make_binary_objects
except ImportError as e :
2024-01-17 13:13:14 +01:00
logger . info ( f ' Unable to load make_binary_objects: { e } ' )
2019-12-18 14:45:14 +01:00
continue
file_object , bin_type_object , bin_section_objects = make_binary_objects ( pseudofile = attribute . malware_binary , filename = attribute . malware_filename )
self . add_object ( file_object )
if bin_type_object :
self . add_object ( bin_type_object )
if bin_section_objects :
for bin_section_object in bin_section_objects :
self . add_object ( bin_section_object )
self . attributes . pop ( index )
else :
2024-01-17 13:13:14 +01:00
logger . warning ( f ' No expansions for this data type ( { attribute . type } ). Open an issue if needed. ' )
2019-08-29 18:08:53 +02:00
2019-12-18 14:45:14 +01:00
def __repr__ ( self ) - > str :
if hasattr ( self , ' info ' ) :
return ' < {self.__class__.__name__} (info= {self.info} ) ' . format ( self = self )
2024-01-17 13:13:14 +01:00
return f ' < { self . __class__ . __name__ } (NotInitialized) '
2019-08-29 18:08:53 +02:00
2019-10-16 17:22:19 +02:00
2019-12-18 14:45:14 +01:00
class MISPObjectTemplate ( AbstractMISP ) :
2019-10-16 17:22:19 +02:00
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
if ' ObjectTemplate ' in kwargs :
kwargs = kwargs [ ' ObjectTemplate ' ]
super ( ) . from_dict ( * * kwargs )
2017-08-30 12:47:32 +02:00
2019-12-18 14:45:14 +01:00
def __repr__ ( self ) - > str :
2024-01-17 13:13:14 +01:00
return f ' < { self . __class__ . __name__ } (self.name) '
2019-11-19 15:53:58 +01:00
2017-12-12 17:34:09 +01:00
2019-12-18 14:45:14 +01:00
class MISPUser ( AbstractMISP ) :
2017-12-12 17:34:09 +01:00
2024-01-30 12:51:23 +01:00
def __init__ ( self , * * kwargs : dict [ str , Any ] ) - > None :
2020-01-23 10:27:40 +01:00
super ( ) . __init__ ( * * kwargs )
self . email : str
2024-01-30 12:51:23 +01:00
self . password : str | None
2020-01-23 10:27:40 +01:00
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
if ' User ' in kwargs :
kwargs = kwargs [ ' User ' ]
super ( ) . from_dict ( * * kwargs )
2024-01-30 12:51:23 +01:00
if hasattr ( self , ' password ' ) and self . password and set ( self . password ) == { ' * ' , } :
2019-12-18 14:45:14 +01:00
self . password = None
2017-12-12 17:34:09 +01:00
2019-12-18 14:45:14 +01:00
def __repr__ ( self ) - > str :
if hasattr ( self , ' email ' ) :
return ' < {self.__class__.__name__} (email= {self.email} ) ' . format ( self = self )
2024-01-17 13:13:14 +01:00
return f ' < { self . __class__ . __name__ } (NotInitialized) '
2018-01-12 00:35:57 +01:00
2019-07-25 14:53:23 +02:00
2019-12-18 14:45:14 +01:00
class MISPFeed ( AbstractMISP ) :
2019-07-25 14:53:23 +02:00
2024-01-30 12:51:23 +01:00
settings : str
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
if ' Feed ' in kwargs :
kwargs = kwargs [ ' Feed ' ]
super ( ) . from_dict ( * * kwargs )
2020-05-15 11:44:13 +02:00
if hasattr ( self , ' settings ' ) :
2020-09-15 12:31:22 +02:00
try :
self . settings = json . loads ( self . settings )
except json . decoder . JSONDecodeError as e :
2024-01-17 13:13:14 +01:00
logger . error ( f " Failed to parse feed settings: { self . settings } " )
2020-09-15 12:31:22 +02:00
raise e
2018-01-04 12:23:32 +01:00
2017-08-30 12:47:32 +02:00
2019-12-18 14:45:14 +01:00
class MISPWarninglist ( AbstractMISP ) :
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
if ' Warninglist ' in kwargs :
kwargs = kwargs [ ' Warninglist ' ]
super ( ) . from_dict ( * * kwargs )
2019-07-25 14:53:23 +02:00
2019-12-18 14:45:14 +01:00
class MISPTaxonomy ( AbstractMISP ) :
2019-12-11 23:12:14 +01:00
2021-03-09 16:35:00 +01:00
enabled : bool
2021-09-07 14:26:22 +02:00
namespace : str
2021-03-09 16:35:00 +01:00
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
if ' Taxonomy ' in kwargs :
kwargs = kwargs [ ' Taxonomy ' ]
super ( ) . from_dict ( * * kwargs )
2019-11-19 15:53:58 +01:00
2024-01-30 12:51:23 +01:00
def __repr__ ( self ) - > str :
2021-09-07 14:26:22 +02:00
return f ' < { self . __class__ . __name__ } (namespace= { self . namespace } )> '
2019-10-08 08:15:56 +02:00
2019-12-18 14:45:14 +01:00
class MISPNoticelist ( AbstractMISP ) :
2019-07-25 14:53:23 +02:00
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
if ' Noticelist ' in kwargs :
kwargs = kwargs [ ' Noticelist ' ]
super ( ) . from_dict ( * * kwargs )
2017-12-22 14:49:14 +01:00
2018-01-12 16:15:09 +01:00
2021-04-22 10:47:51 +02:00
class MISPCorrelationExclusion ( AbstractMISP ) :
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2021-04-22 10:47:51 +02:00
if ' CorrelationExclusion ' in kwargs :
kwargs = kwargs [ ' CorrelationExclusion ' ]
super ( ) . from_dict ( * * kwargs )
2019-12-18 14:45:14 +01:00
class MISPRole ( AbstractMISP ) :
2017-12-14 16:12:54 +01:00
2024-01-30 12:51:23 +01:00
def __init__ ( self , * * kwargs : dict [ str , Any ] ) - > None :
2020-01-23 10:27:40 +01:00
super ( ) . __init__ ( * * kwargs )
self . perm_admin : int
self . perm_site_admin : int
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
if ' Role ' in kwargs :
kwargs = kwargs [ ' Role ' ]
super ( ) . from_dict ( * * kwargs )
class MISPServer ( AbstractMISP ) :
2018-01-12 16:15:09 +01:00
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
if ' Server ' in kwargs :
kwargs = kwargs [ ' Server ' ]
super ( ) . from_dict ( * * kwargs )
2017-08-30 12:47:32 +02:00
2018-11-29 17:27:48 +01:00
2019-12-18 14:45:14 +01:00
class MISPLog ( AbstractMISP ) :
2017-08-30 12:47:32 +02:00
2024-01-30 12:51:23 +01:00
def __init__ ( self , * * kwargs : dict [ str , Any ] ) - > None :
2020-01-23 10:27:40 +01:00
super ( ) . __init__ ( * * kwargs )
self . model : str
self . action : str
self . title : str
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
if ' Log ' in kwargs :
kwargs = kwargs [ ' Log ' ]
super ( ) . from_dict ( * * kwargs )
2018-01-12 16:15:09 +01:00
2019-12-18 14:45:14 +01:00
def __repr__ ( self ) - > str :
return ' < {self.__class__.__name__} ( {self.model} , {self.action} , {self.title} ) ' . format ( self = self )
2017-12-12 17:34:09 +01:00
2019-12-18 14:45:14 +01:00
class MISPEventDelegation ( AbstractMISP ) :
2017-12-22 14:49:14 +01:00
2024-01-30 12:51:23 +01:00
def __init__ ( self , * * kwargs : dict [ str , Any ] ) - > None :
2020-01-23 10:27:40 +01:00
super ( ) . __init__ ( * * kwargs )
self . org_id : int
self . requester_org_id : int
self . event_id : int
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
if ' EventDelegation ' in kwargs :
kwargs = kwargs [ ' EventDelegation ' ]
super ( ) . from_dict ( * * kwargs )
2019-04-09 17:54:12 +02:00
2019-12-18 14:45:14 +01:00
def __repr__ ( self ) - > str :
return ' < {self.__class__.__name__} (org_id= {self.org_id} , requester_org_id= {self.requester_org_id} , {self.event_id} ) ' . format ( self = self )
2017-12-22 14:49:14 +01:00
2019-12-18 14:45:14 +01:00
class MISPObjectAttribute ( MISPAttribute ) :
2024-01-30 12:51:23 +01:00
_fields_for_feed : set [ str ] = { ' uuid ' , ' value ' , ' category ' , ' type ' , ' comment ' , ' data ' ,
' deleted ' , ' timestamp ' , ' to_ids ' , ' disable_correlation ' ,
' first_seen ' , ' last_seen ' , ' object_relation ' }
2019-12-18 14:45:14 +01:00
2024-01-30 12:51:23 +01:00
def __init__ ( self , definition : dict [ str , Any ] ) - > None :
2019-12-18 14:45:14 +01:00
super ( ) . __init__ ( )
self . _definition = definition
2024-01-17 13:13:14 +01:00
def from_dict ( self , object_relation : str , value : str | int | float , * * kwargs ) : # type: ignore
2020-01-23 10:27:40 +01:00
# NOTE: Signature of "from_dict" incompatible with supertype "MISPAttribute"
2019-12-18 14:45:14 +01:00
self . object_relation = object_relation
self . value = value
if ' Attribute ' in kwargs :
kwargs = kwargs [ ' Attribute ' ]
# Initialize the new MISPAttribute
# Get the misp attribute type from the definition
self . type = kwargs . pop ( ' type ' , None )
if self . type is None :
self . type = self . _definition . get ( ' misp-attribute ' )
if ' category ' not in kwargs and ' categories ' in self . _definition :
# Get first category in the list from the object template as default
self . category = self . _definition [ ' categories ' ] [ 0 ]
self . disable_correlation = kwargs . pop ( ' disable_correlation ' , None )
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
self . disable_correlation = self . _definition . get ( ' disable_correlation ' )
self . to_ids = kwargs . pop ( ' to_ids ' , None )
if self . to_ids is None :
# Same for the to_ids flag
self . to_ids = self . _definition . get ( ' to_ids ' )
if not self . type :
raise NewAttributeError ( " The type of the attribute is required. Is the object template missing? " )
super ( ) . from_dict ( * * { * * self , * * kwargs } )
2017-12-22 14:49:14 +01:00
2024-01-30 12:51:23 +01:00
def __repr__ ( self ) - > str :
2019-12-18 14:45:14 +01:00
if hasattr ( self , ' value ' ) :
return ' < {self.__class__.__name__} (object_relation= {self.object_relation} , value= {self.value} ) ' . format ( self = self )
2024-01-17 13:13:14 +01:00
return f ' < { self . __class__ . __name__ } (NotInitialized) '
2019-12-18 14:45:14 +01:00
class MISPCommunity ( AbstractMISP ) :
2024-01-30 12:51:23 +01:00
def __init__ ( self , * * kwargs : dict [ str , Any ] ) - > None :
super ( ) . __init__ ( * * kwargs )
self . name : str
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
if ' Community ' in kwargs :
kwargs = kwargs [ ' Community ' ]
super ( ) . from_dict ( * * kwargs )
2024-01-30 12:51:23 +01:00
def __repr__ ( self ) - > str :
2020-04-30 10:20:21 +02:00
return f ' < { self . __class__ . __name__ } (name= { self . name } , uuid= { self . uuid } ) '
2019-12-18 14:45:14 +01:00
class MISPUserSetting ( AbstractMISP ) :
2024-01-30 12:51:23 +01:00
def __init__ ( self , * * kwargs : dict [ str , Any ] ) - > None :
super ( ) . __init__ ( * * kwargs )
self . setting : str
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2019-12-18 14:45:14 +01:00
if ' UserSetting ' in kwargs :
kwargs = kwargs [ ' UserSetting ' ]
super ( ) . from_dict ( * * kwargs )
2024-01-30 12:51:23 +01:00
def __repr__ ( self ) - > str :
2020-04-30 10:20:21 +02:00
return f ' < { self . __class__ . __name__ } (name= { self . setting } '
2020-05-07 12:17:31 +02:00
class MISPInbox ( AbstractMISP ) :
2024-01-30 12:51:23 +01:00
def __init__ ( self , * * kwargs : dict [ str , Any ] ) - > None :
2020-05-07 12:17:31 +02:00
super ( ) . __init__ ( * * kwargs )
2024-01-30 12:51:23 +01:00
self . data : dict [ str , Any ]
self . type : str
2020-05-07 12:17:31 +02:00
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2020-05-07 12:17:31 +02:00
if ' Inbox ' in kwargs :
kwargs = kwargs [ ' Inbox ' ]
super ( ) . from_dict ( * * kwargs )
2024-01-30 12:51:23 +01:00
def __repr__ ( self ) - > str :
2020-05-07 12:17:31 +02:00
return f ' < { self . __class__ . __name__ } (name= { self . type } )> '
2020-08-03 15:59:54 +02:00
2020-09-01 19:29:12 +02:00
class MISPEventBlocklist ( AbstractMISP ) :
2020-08-03 15:59:54 +02:00
2024-01-30 12:51:23 +01:00
def __init__ ( self , * * kwargs : dict [ str , Any ] ) - > None :
2020-08-04 12:20:21 +02:00
super ( ) . __init__ ( * * kwargs )
self . event_uuid : str
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2020-09-01 19:29:12 +02:00
if ' EventBlocklist ' in kwargs :
kwargs = kwargs [ ' EventBlocklist ' ]
2020-08-03 15:59:54 +02:00
super ( ) . from_dict ( * * kwargs )
2024-01-30 12:51:23 +01:00
def __repr__ ( self ) - > str :
2020-08-03 15:59:54 +02:00
return f ' < { self . __class__ . __name__ } (event_uuid= { self . event_uuid } '
2020-09-01 19:29:12 +02:00
class MISPOrganisationBlocklist ( AbstractMISP ) :
2020-08-03 15:59:54 +02:00
2024-01-30 12:51:23 +01:00
def __init__ ( self , * * kwargs : dict [ str , Any ] ) - > None :
2020-08-04 12:20:21 +02:00
super ( ) . __init__ ( * * kwargs )
self . org_uuid : str
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2020-09-01 19:29:12 +02:00
if ' OrgBlocklist ' in kwargs :
kwargs = kwargs [ ' OrgBlocklist ' ]
2020-08-03 15:59:54 +02:00
super ( ) . from_dict ( * * kwargs )
2024-01-30 12:51:23 +01:00
def __repr__ ( self ) - > str :
2020-08-03 15:59:54 +02:00
return f ' < { self . __class__ . __name__ } (org_uuid= { self . org_uuid } '
2022-11-22 14:48:23 +01:00
class MISPDecayingModel ( AbstractMISP ) :
2024-01-30 12:51:23 +01:00
def __init__ ( self , * * kwargs : dict [ str , Any ] ) - > None :
2022-11-22 14:48:23 +01:00
super ( ) . __init__ ( * * kwargs )
self . uuid : str
self . id : int
2024-01-30 12:51:23 +01:00
def from_dict ( self , * * kwargs ) - > None : # type: ignore[no-untyped-def]
2022-11-22 14:48:23 +01:00
if ' DecayingModel ' in kwargs :
kwargs = kwargs [ ' DecayingModel ' ]
super ( ) . from_dict ( * * kwargs )
2024-01-30 12:51:23 +01:00
def __repr__ ( self ) - > str :
2022-11-22 14:48:23 +01:00
return f ' < { self . __class__ . __name__ } (uuid= { self . uuid } )> '