2014-03-19 19:10:36 +01:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
2014-04-16 14:09:56 +02:00
""" Python API using the REST interface of MISP """
2014-03-19 19:10:36 +01:00
2015-02-16 14:31:29 +01:00
import json
import datetime
2014-03-19 19:10:36 +01:00
import requests
2015-08-04 16:24:55 +02:00
import os
import base64
2015-08-07 17:24:03 +02:00
from urlparse import urljoin
import StringIO
import zipfile
2014-04-14 10:55:20 +02:00
2015-08-05 16:01:57 +02:00
class PyMISPError ( Exception ) :
def __init__ ( self , message ) :
super ( PyMISPError , self ) . __init__ ( message )
self . message = message
class NewEventError ( PyMISPError ) :
pass
class NewAttributeError ( PyMISPError ) :
pass
2014-04-11 18:45:52 +02:00
class PyMISP ( object ) :
2014-04-16 14:09:56 +02:00
"""
Python API for MISP
: param url : URL of the MISP instance you want to connect to
: param key : API key of the user you want to use
: param ssl : can be True or False ( to check ot not the validity
of the certificate . Or a CA_BUNDLE in case of self
signed certiifcate ( the concatenation of all the
* . crt of the chain )
: param out_type : Type of object ( json or xml )
"""
def __init__ ( self , url , key , ssl = True , out_type = ' json ' ) :
2015-08-07 17:24:03 +02:00
self . root_url = url
2014-04-11 18:45:52 +02:00
self . key = key
2014-04-16 14:09:56 +02:00
self . ssl = ssl
2014-04-11 18:45:52 +02:00
self . out_type = out_type
def __prepare_session ( self , force_out = None ) :
"""
Prepare the headers of the session
2014-04-16 14:09:56 +02:00
: param force_out : force the type of the expect output
( overwrite the constructor )
2014-04-11 18:45:52 +02:00
"""
if force_out is not None :
out = force_out
2014-03-19 19:10:36 +01:00
else :
2014-04-11 18:45:52 +02:00
out = self . out_type
session = requests . Session ( )
2014-04-16 14:09:56 +02:00
session . verify = self . ssl
2014-04-14 10:55:20 +02:00
session . headers . update (
{ ' Authorization ' : self . key ,
' Accept ' : ' application/ ' + out ,
2015-06-02 10:40:14 +02:00
' content-type ' : ' application/ ' + out } )
2014-04-11 18:45:52 +02:00
return session
2015-02-16 14:31:29 +01:00
def __query ( self , session , path , query ) :
if query . get ( ' error ' ) is not None :
return query
2015-08-10 11:58:20 +02:00
url = urljoin ( self . root_url , ' events/ {} ' . format ( path . lstrip ( ' / ' ) ) )
2015-02-16 14:31:29 +01:00
query = { ' request ' : query }
r = session . post ( url , data = json . dumps ( query ) )
return r . json ( )
2014-04-14 10:55:20 +02:00
# ############### REST API ################
2014-04-11 18:45:52 +02:00
def get_index ( self ) :
"""
Return the index .
Warning , there ' s a limit on the number of results
"""
session = self . __prepare_session ( )
2015-08-10 11:58:20 +02:00
url = urljoin ( self . root_url , ' events ' )
return session . get ( url )
2014-04-11 18:45:52 +02:00
def get_event ( self , event_id ) :
"""
Get an event
2014-04-16 14:09:56 +02:00
: param event_id : Event id to get
2014-04-11 18:45:52 +02:00
"""
session = self . __prepare_session ( )
2015-08-12 13:23:38 +02:00
url = urljoin ( self . root_url , ' events/ {} ' . format ( event_id ) )
2015-08-10 11:58:20 +02:00
return session . get ( url )
2014-04-11 18:45:52 +02:00
def add_event ( self , event ) :
"""
Add a new event
2014-04-16 14:09:56 +02:00
2015-07-30 15:53:34 +02:00
: param event : Event as JSON object / string or XML to add
2014-04-11 18:45:52 +02:00
"""
session = self . __prepare_session ( )
2015-08-10 11:58:20 +02:00
url = urljoin ( self . root_url , ' events ' )
2015-07-30 15:53:34 +02:00
if self . out_type == ' json ' :
if isinstance ( event , basestring ) :
2015-08-10 11:58:20 +02:00
return session . post ( url , data = event )
2015-07-30 15:53:34 +02:00
else :
2015-08-10 11:58:20 +02:00
return session . post ( url , data = json . dumps ( event ) )
2015-07-30 15:53:34 +02:00
else :
2015-08-10 11:58:20 +02:00
return session . post ( url , data = event )
2014-04-11 18:45:52 +02:00
def update_event ( self , event_id , event ) :
"""
Update an event
2014-04-16 14:09:56 +02:00
: param event_id : Event id to update
2015-07-30 15:53:34 +02:00
: param event : Event as JSON object / string or XML to add
2014-04-11 18:45:52 +02:00
"""
session = self . __prepare_session ( )
2015-08-12 13:23:38 +02:00
url = urljoin ( self . root_url , ' events/ {} ' . format ( event_id ) )
2015-07-30 15:53:34 +02:00
if self . out_type == ' json ' :
if isinstance ( event , basestring ) :
2015-08-10 11:58:20 +02:00
return session . post ( url , data = event )
2015-07-30 15:53:34 +02:00
else :
2015-08-10 11:58:20 +02:00
return session . post ( url , data = json . dumps ( event ) )
2015-07-30 15:53:34 +02:00
else :
2015-08-10 11:58:20 +02:00
return session . post ( url , data = event )
2014-04-11 18:45:52 +02:00
def delete_event ( self , event_id ) :
"""
Delete an event
2014-04-16 14:09:56 +02:00
: param event_id : Event id to delete
2014-04-11 18:45:52 +02:00
"""
session = self . __prepare_session ( )
2015-08-12 13:23:38 +02:00
url = urljoin ( self . root_url , ' events/ {} ' . format ( event_id ) )
2015-08-10 11:58:20 +02:00
return session . delete ( url )
2014-04-11 18:45:52 +02:00
2015-08-04 16:24:55 +02:00
# ######### Create/update events through the API #########
def _create_event ( self , distribution , threat_level_id , analysis , info ) :
# Setup details of a new event
if distribution not in [ 0 , 1 , 2 , 3 ] :
2015-08-05 16:01:57 +02:00
raise NewEventError ( ' {} is invalid, the distribution has to be in 0, 1, 2, 3 ' . format ( distribution ) )
2015-08-04 16:24:55 +02:00
if threat_level_id not in [ 0 , 1 , 2 , 3 ] :
2015-08-05 16:01:57 +02:00
raise NewEventError ( ' {} is invalid, the threat_level_id has to be in 0, 1, 2, 3 ' . format ( threat_level_id ) )
2015-08-04 16:24:55 +02:00
if analysis not in [ 0 , 1 , 2 ] :
2015-08-05 16:01:57 +02:00
raise NewEventError ( ' {} is invalid, the analysis has to be in 0, 1, 2 ' . format ( analysis ) )
2015-08-04 16:24:55 +02:00
return { ' distribution ' : int ( distribution ) , ' info ' : info ,
' threat_level_id ' : int ( threat_level_id ) , ' analysis ' : analysis }
2015-08-06 01:57:59 +02:00
def prepare_attribute ( self , event_id , distribution , to_ids , category , info ,
analysis , threat_level_id ) :
2015-08-06 09:49:44 +02:00
to_post = { ' request ' : { } }
2015-08-04 16:24:55 +02:00
if not isinstance ( event_id , int ) :
# New event
2015-08-06 09:49:44 +02:00
to_post [ ' request ' ] . update ( self . _create_event ( distribution , threat_level_id ,
analysis , info ) )
2015-08-04 16:24:55 +02:00
else :
to_post [ ' request ' ] . update ( { ' event_id ' : int ( event_id ) } )
if to_ids not in [ True , False ] :
2015-08-05 16:01:57 +02:00
raise NewAttributeError ( ' {} is invalid, to_ids has to be True or False ' . format ( analysis ) )
2015-08-04 16:24:55 +02:00
to_post [ ' request ' ] . update ( { ' to_ids ' : to_ids } )
if category not in [ ' Payload delivery ' , ' Artifacts dropped ' ,
' Payload Installation ' , ' External Analysis ' ] :
2015-08-05 16:01:57 +02:00
raise NewAttributeError ( ' {} is invalid, category has to be in {} ' . format ( analysis , ( ' , ' . join ( [ ' Payload delivery ' , ' Artifacts dropped ' , ' Payload Installation ' , ' External Analysis ' ] ) ) ) )
2015-08-04 16:24:55 +02:00
to_post [ ' request ' ] . update ( { ' category ' : category } )
2015-08-06 01:57:59 +02:00
return to_post
def prepare_sample ( self , filename , filepath ) :
with open ( filepath , ' rb ' ) as f :
return { ' files ' : [ { ' filename ' : filename , ' data ' : base64 . b64encode ( f . read ( ) ) } ] }
def prepare_samplelist ( self , filepaths ) :
2015-08-04 16:24:55 +02:00
files = [ ]
for path in filepaths :
if not os . path . isfile ( path ) :
continue
2015-08-06 01:57:59 +02:00
files . append ( { ' filename ' : os . path . basename ( path ) , ' data ' : path } )
return { ' files ' : files }
def upload_sample ( self , filename , filepath , event_id , distribution , to_ids ,
category , info , analysis , threat_level_id ) :
to_post = self . prepare_attribute ( event_id , distribution , to_ids , category ,
info , analysis , threat_level_id )
to_post [ ' request ' ] . update ( self . prepare_sample ( filename , filepath ) )
return self . _upload_sample ( to_post )
def upload_samplelist ( self , filepaths , event_id , distribution , to_ids , category ,
info , analysis , threat_level_id ) :
to_post = self . prepare_attribute ( event_id , distribution , to_ids , category ,
info , analysis , threat_level_id )
to_post [ ' request ' ] . update ( self . prepare_samplelist ( filepaths ) )
2015-08-04 16:24:55 +02:00
2015-08-06 01:57:59 +02:00
return self . _upload_sample ( to_post )
2015-08-04 16:24:55 +02:00
2015-08-06 01:57:59 +02:00
def _upload_sample ( self , to_post ) :
2015-08-04 16:24:55 +02:00
session = self . __prepare_session ( )
2015-08-10 11:58:20 +02:00
url = urljoin ( self . root_url , ' events/upload_sample ' )
return session . post ( url , data = json . dumps ( to_post ) )
2015-08-04 16:24:55 +02:00
2014-04-14 10:55:20 +02:00
# ######## REST Search #########
2014-04-11 18:45:52 +02:00
2015-08-06 17:42:41 +02:00
def search_all ( self , value ) :
query = { ' value ' : value , ' searchall ' : 1 }
session = self . __prepare_session ( )
return self . __query ( session , ' restSearch/download ' , query )
2014-04-11 18:45:52 +02:00
def __prepare_rest_search ( self , values , not_values ) :
"""
2014-04-16 14:09:56 +02:00
Prepare a search , generate the chain processed by the server
: param values : Values to search
: param not_values : Values that should not be in the response
2014-04-11 18:45:52 +02:00
"""
to_return = ' '
if values is not None :
2014-04-14 10:55:20 +02:00
if not isinstance ( values , list ) :
2014-04-11 18:45:52 +02:00
to_return + = values
else :
to_return + = ' && ' . join ( values )
if not_values is not None :
2014-04-14 10:55:20 +02:00
if len ( to_return ) > 0 :
2014-04-11 18:45:52 +02:00
to_return + = ' &&! '
else :
to_return + = ' ! '
2014-04-14 10:55:20 +02:00
if not isinstance ( values , list ) :
2014-04-11 18:45:52 +02:00
to_return + = not_values
else :
to_return + = ' &&! ' . join ( not_values )
return to_return
def search ( self , values = None , not_values = None , type_attribute = None ,
2015-02-16 14:31:29 +01:00
category = None , org = None , tags = None , not_tags = None , date_from = None ,
2015-08-05 17:20:59 +02:00
date_to = None , last = None ) :
2014-04-11 18:45:52 +02:00
"""
Search via the Rest API
2014-04-16 14:09:56 +02:00
: param values : values to search for
: param not_values : values * not * to search for
: param type_attribute : Type of attribute
: param category : Category to search
: param org : Org reporting the event
: param tags : Tags to search for
: param not_tags : Tags * not * to search for
2015-02-16 14:31:29 +01:00
: param date_from : First date
: param date_to : Last date
2015-08-05 17:20:59 +02:00
: param last : Last updated events ( for example 5 d or 12 h or 30 m )
2014-04-16 14:09:56 +02:00
2014-04-11 18:45:52 +02:00
"""
val = self . __prepare_rest_search ( values , not_values ) . replace ( ' / ' , ' | ' )
tag = self . __prepare_rest_search ( tags , not_tags ) . replace ( ' : ' , ' ; ' )
2015-02-16 14:31:29 +01:00
query = { }
if len ( val ) != 0 :
query [ ' value ' ] = val
if len ( tag ) != 0 :
query [ ' tags ' ] = tag
if type_attribute is not None :
query [ ' type ' ] = type_attribute
if category is not None :
query [ ' category ' ] = category
if org is not None :
query [ ' org ' ] = org
if date_from is not None :
if isinstance ( date_from , datetime . date ) or isinstance ( date_to , datetime . datetime ) :
query [ ' from ' ] = date_from . strftime ( ' % Y- % m- %d ' )
else :
query [ ' from ' ] = date_from
if date_to is not None :
if isinstance ( date_to , datetime . date ) or isinstance ( date_to , datetime . datetime ) :
query [ ' to ' ] = date_to . strftime ( ' % Y- % m- %d ' )
else :
query [ ' to ' ] = date_to
2015-08-05 17:20:59 +02:00
if last is not None :
query [ ' last ' ] = last
2014-04-11 18:45:52 +02:00
session = self . __prepare_session ( )
2015-02-16 14:31:29 +01:00
return self . __query ( session , ' restSearch/download ' , query )
2014-04-11 18:45:52 +02:00
def get_attachement ( self , event_id ) :
"""
Get attachement of an event ( not sample )
2014-04-16 14:09:56 +02:00
: param event_id : Event id from where the attachements will
be fetched
2014-04-11 18:45:52 +02:00
"""
2015-08-12 13:23:38 +02:00
attach = urljoin ( self . root_url , ' attributes/downloadAttachment/download/ {} ' . format ( event_id ) )
2014-04-11 18:45:52 +02:00
session = self . __prepare_session ( )
2015-08-10 11:58:20 +02:00
return session . get ( attach )
2014-04-11 18:45:52 +02:00
2015-08-19 10:42:24 +02:00
def get_yara ( self , event_id ) :
to_post = { ' request ' : { ' eventid ' : event_id , ' type ' : ' yara ' } }
session = self . __prepare_session ( )
response = session . post ( urljoin ( self . root_url , ' attributes/restSearch ' ) , data = json . dumps ( to_post ) )
result = response . json ( )
if response . status_code != 200 :
return False , result . get ( ' message ' )
if not result . get ( ' response ' ) and result . get ( ' message ' ) :
return False , result . get ( ' message ' )
rules = ' \n \n ' . join ( [ a [ ' value ' ] for a in result [ ' response ' ] [ ' Attribute ' ] ] )
return True , rules
2015-08-07 17:24:03 +02:00
def download_samples ( self , sample_hash = None , event_id = None , all_samples = False ) :
to_post = { ' request ' : { ' hash ' : sample_hash , ' eventID ' : event_id , ' allSamples ' : all_samples } }
session = self . __prepare_session ( )
response = session . post ( urljoin ( self . root_url , ' attributes/downloadSample ' ) , data = json . dumps ( to_post ) )
result = response . json ( )
if response . status_code != 200 :
return False , result . get ( ' message ' )
if not result . get ( ' result ' ) and result . get ( ' message ' ) :
return False , result . get ( ' message ' )
details = [ ]
for f in result [ ' result ' ] :
zipped = StringIO . StringIO ( base64 . b64decode ( f [ ' base64 ' ] ) )
archive = zipfile . ZipFile ( zipped )
unzipped = StringIO . StringIO ( archive . open ( f [ ' md5 ' ] , pwd = ' infected ' ) . read ( ) )
details . append ( [ f [ ' event_id ' ] , f [ ' filename ' ] , unzipped ] )
return True , details
2015-08-05 17:20:59 +02:00
def download_last ( self , last ) :
"""
Download the last updated events .
: param last : can be defined in days , hours , minutes ( for example 5 d or 12 h or 30 m )
"""
return self . search ( last = last )
2014-04-14 10:55:20 +02:00
# ############## Export ###############
2014-04-11 18:45:52 +02:00
def download_all ( self ) :
"""
Download all event from the instance
"""
2015-08-10 11:58:20 +02:00
xml = urljoin ( self . root_url , ' events/xml/download ' )
2014-04-11 18:45:52 +02:00
session = self . __prepare_session ( ' xml ' )
2014-04-16 14:09:56 +02:00
return session . get ( xml )
2014-04-11 18:45:52 +02:00
2015-07-29 15:07:37 +02:00
def download_all_suricata ( self ) :
"""
Download all suricata rules events .
"""
2015-08-10 11:58:20 +02:00
suricata_rules = urljoin ( self . root_url , ' events/nids/suricata/download ' )
2015-07-29 15:07:37 +02:00
session = self . __prepare_session ( ' rules ' )
return session . get ( suricata_rules )
def download_suricata_rule_event ( self , event_id ) :
"""
Download one suricata rule event .
: param event_id : ID of the event to download ( same as get )
"""
2015-08-12 13:23:38 +02:00
template = urljoin ( self . root_url , ' events/nids/suricata/download/ {} ' . format ( event_id ) )
2015-07-29 15:07:37 +02:00
session = self . __prepare_session ( ' rules ' )
2015-08-10 11:58:20 +02:00
return session . get ( template )
2015-07-29 15:07:37 +02:00
2014-04-11 18:45:52 +02:00
def download ( self , event_id , with_attachement = False ) :
"""
Download one event in XML
2014-04-16 14:09:56 +02:00
: param event_id : Event id of the event to download ( same as get )
2014-04-11 18:45:52 +02:00
"""
if with_attachement :
2014-04-14 10:55:20 +02:00
attach = ' true '
2014-03-20 11:10:52 +01:00
else :
2014-04-14 10:55:20 +02:00
attach = ' false '
2015-08-12 13:23:38 +02:00
template = urljoin ( self . root_url , ' events/xml/download/ {} / {} ' . format ( event_id , attach ) )
2014-04-11 18:45:52 +02:00
session = self . __prepare_session ( ' xml ' )
2015-08-10 11:58:20 +02:00
return session . get ( template )
2014-04-11 18:45:52 +02:00
##########################################