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
2015-08-04 16:24:55 +02:00
import os
import base64
2015-09-18 12:03:56 +02:00
import re
2015-09-02 13:56:08 +02:00
try :
from urllib . parse import urljoin
except ImportError :
from urlparse import urljoin
2015-09-12 23:08:06 +02:00
from io import BytesIO
2015-08-07 17:24:03 +02:00
import zipfile
2015-08-28 17:03:35 +02:00
import warnings
import functools
2014-04-14 10:55:20 +02:00
2015-09-18 12:03:56 +02:00
try :
import requests
HAVE_REQUESTS = True
except ImportError :
HAVE_REQUESTS = False
from . import __version__
2015-09-02 13:56:08 +02:00
# Least dirty way to support python 2 and 3
try :
basestring
except NameError :
basestring = str
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
2015-09-18 12:03:56 +02:00
class MissingDependency ( PyMISPError ) :
pass
2015-09-18 17:48:10 +02:00
class NoURL ( PyMISPError ) :
pass
class NoKey ( PyMISPError ) :
pass
2015-08-28 17:03:35 +02:00
def deprecated ( func ) :
''' This is a decorator which can be used to mark functions
as deprecated . It will result in a warning being emitted
when the function is used . '''
@functools.wraps ( func )
def new_func ( * args , * * kwargs ) :
warnings . warn_explicit (
" Call to deprecated function {} . " . format ( func . __name__ ) ,
category = DeprecationWarning ,
2015-09-02 13:56:08 +02:00
filename = func . __code__ . co_filename ,
lineno = func . __code__ . co_firstlineno + 1
2015-08-28 17:03:35 +02:00
)
return func ( * args , * * kwargs )
return new_func
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-09-18 17:48:10 +02:00
if not url :
raise NoURL ( ' Please provide the URL of your MISP instance. ' )
if not key :
raise NoKey ( ' Please provide your authorization key. ' )
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
2015-09-01 18:46:10 +02:00
self . categories = [ ' Internal reference ' , ' Targeting data ' , ' Antivirus detection ' ,
' Payload delivery ' , ' Payload installation ' , ' Artifacts dropped ' ,
' Persistence mechanism ' , ' Network activity ' , ' Payload type ' ,
' Attribution ' , ' External analysis ' , ' Other ' ]
self . types = [ ' md5 ' , ' sha1 ' , ' sha256 ' , ' filename ' , ' filename|md5 ' , ' filename|sha1 ' ,
' filename|sha256 ' , ' ip-src ' , ' ip-dst ' , ' hostname ' , ' domain ' , ' url ' ,
' user-agent ' , ' http-method ' , ' regkey ' , ' regkey|value ' , ' AS ' , ' snort ' ,
2015-09-30 14:37:18 +02:00
' pattern-in-file ' , ' pattern-in-traffic ' , ' pattern-in-memory ' , ' named pipe ' ,
2015-09-30 14:45:15 +02:00
' mutex ' , ' vulnerability ' , ' attachment ' , ' malware-sample ' , ' link ' , ' comment ' ,
' text ' , ' email-src ' , ' email-dst ' , ' email-subject ' , ' email-attachment ' ,
' yara ' , ' target-user ' , ' target-email ' , ' target-machine ' , ' target-org ' ,
2015-09-30 14:46:25 +02:00
' target-location ' , ' target-external ' , ' other ' ]
2015-09-01 18:46:10 +02:00
2015-09-21 14:40:06 +02:00
try :
# Make sure the MISP instance is working and the URL is valid
self . get_version ( )
except Exception as e :
raise PyMISPError ( ' Unable to connect to MISP ( {} ). Please make sure the API key and the URL are correct (http/https is required): {} ' . format ( self . root_url , e ) )
2014-04-11 18:45:52 +02:00
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
"""
2015-09-18 12:03:56 +02:00
if not HAVE_REQUESTS :
raise MissingDependency ( ' Missing dependency, install requests (`pip install requests`) ' )
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-09-23 18:47:47 +02:00
def _check_response ( self , response ) :
if response . status_code > = 500 :
response . raise_for_status ( )
to_return = response . json ( )
if 400 < = response . status_code < 500 :
if to_return . get ( ' error ' ) is None :
to_return [ ' error ' ] = to_return . get ( ' message ' )
return to_return
2015-09-01 10:31:22 +02:00
# ################################################
# ############### Simple REST API ################
# ################################################
2014-04-11 18:45:52 +02:00
2015-09-21 11:52:26 +02:00
def get_index ( self , force_out = None ) :
2014-04-11 18:45:52 +02:00
"""
Return the index .
Warning , there ' s a limit on the number of results
"""
2015-09-21 11:52:26 +02:00
session = self . __prepare_session ( force_out )
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
2015-09-21 11:52:26 +02:00
def get_event ( self , event_id , force_out = None ) :
2014-04-11 18:45:52 +02:00
"""
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
"""
2015-09-21 11:52:26 +02:00
session = self . __prepare_session ( force_out )
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
2015-09-21 11:52:26 +02:00
def add_event ( self , event , force_out = None ) :
2014-04-11 18:45:52 +02:00
"""
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
"""
2015-09-21 11:52:26 +02:00
session = self . __prepare_session ( force_out )
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
2015-09-21 11:52:26 +02:00
def update_event ( self , event_id , event , force_out = None ) :
2014-04-11 18:45:52 +02:00
"""
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
"""
2015-09-21 11:52:26 +02:00
session = self . __prepare_session ( force_out )
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
2015-09-21 11:52:26 +02:00
def delete_event ( self , event_id , force_out = None ) :
2014-04-11 18:45:52 +02:00
"""
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
"""
2015-09-21 11:52:26 +02:00
session = self . __prepare_session ( force_out )
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-09-21 11:52:26 +02:00
def delete_attribute ( self , attribute_id , force_out = None ) :
session = self . __prepare_session ( force_out )
2015-08-28 17:03:35 +02:00
url = urljoin ( self . root_url , ' attributes/ {} ' . format ( attribute_id ) )
return session . delete ( url )
2015-09-01 18:46:10 +02:00
# ##############################################
# ######### Event handling (Json only) #########
# ##############################################
def _prepare_full_event ( self , distribution , threat_level_id , analysis , info , date = None , published = False ) :
to_return = { ' Event ' : { } }
# Setup details of a new event
if distribution not in [ 0 , 1 , 2 , 3 ] :
raise NewEventError ( ' {} is invalid, the distribution has to be in 0, 1, 2, 3 ' . format ( distribution ) )
if threat_level_id not in [ 1 , 2 , 3 , 4 ] :
raise NewEventError ( ' {} is invalid, the threat_level_id has to be in 1, 2, 3, 4 ' . format ( threat_level_id ) )
if analysis not in [ 0 , 1 , 2 ] :
raise NewEventError ( ' {} is invalid, the analysis has to be in 0, 1, 2 ' . format ( analysis ) )
if date is None :
date = datetime . date . today ( ) . isoformat ( )
if published not in [ True , False ] :
raise NewEventError ( ' {} is invalid, published has to be True or False ' . format ( published ) )
to_return [ ' Event ' ] = { ' distribution ' : distribution , ' info ' : info , ' date ' : date , ' published ' : published ,
' threat_level_id ' : threat_level_id , ' analysis ' : analysis }
return to_return
def _prepare_full_attribute ( self , category , type_value , value , to_ids , comment = None , distribution = None ) :
to_return = { }
if category not in self . categories :
raise NewAttributeError ( ' {} is invalid, category has to be in {} ' . format ( category , ( ' , ' . join ( self . categories ) ) ) )
to_return [ ' category ' ] = category
if type_value not in self . types :
raise NewAttributeError ( ' {} is invalid, type_value has to be in {} ' . format ( type_value , ( ' , ' . join ( self . types ) ) ) )
to_return [ ' type ' ] = type_value
if to_ids not in [ True , False ] :
raise NewAttributeError ( ' {} is invalid, to_ids has to be True or False ' . format ( to_ids ) )
to_return [ ' to_ids ' ] = to_ids
if distribution is not None :
distribution = int ( distribution )
# If None: take the default value of the event
if distribution not in [ None , 0 , 1 , 2 , 3 ] :
raise NewAttributeError ( ' {} is invalid, the distribution has to be in 0, 1, 2, 3 or None ' . format ( distribution ) )
if distribution is not None :
to_return [ ' distribution ' ] = distribution
to_return [ ' value ' ] = value
if comment is not None :
to_return [ ' comment ' ] = comment
return to_return
def _prepare_update ( self , event ) :
# Cleanup the received event to make it publishable
event [ ' Event ' ] . pop ( ' locked ' , None )
event [ ' Event ' ] . pop ( ' attribute_count ' , None )
event [ ' Event ' ] . pop ( ' RelatedEvent ' , None )
event [ ' Event ' ] . pop ( ' orgc ' , None )
event [ ' Event ' ] . pop ( ' ShadowAttribute ' , None )
event [ ' Event ' ] . pop ( ' org ' , None )
event [ ' Event ' ] . pop ( ' proposal_email_lock ' , None )
event [ ' Event ' ] . pop ( ' publish_timestamp ' , None )
event [ ' Event ' ] . pop ( ' published ' , None )
2015-09-08 15:21:00 +02:00
event [ ' Event ' ] . pop ( ' timestamp ' , None )
2015-09-01 18:46:10 +02:00
event [ ' Event ' ] [ ' id ' ] = int ( event [ ' Event ' ] [ ' id ' ] )
return event
# ########## Helpers ##########
2015-09-23 18:47:47 +02:00
def get ( self , eid ) :
response = self . get_event ( int ( eid ) , ' json ' )
return self . _check_response ( response )
def update ( self , event ) :
eid = event [ ' Event ' ] [ ' id ' ]
response = self . update_event ( eid , event , ' json ' )
return self . _check_response ( response )
2015-09-01 18:46:10 +02:00
def new_event ( self , distribution = None , threat_level_id = None , analysis = None , info = None , date = None , published = False ) :
data = self . _prepare_full_event ( distribution , threat_level_id , analysis , info , date , published )
2015-09-21 11:52:26 +02:00
response = self . add_event ( data , ' json ' )
2015-09-23 18:47:47 +02:00
return self . _check_response ( response )
2015-09-01 18:46:10 +02:00
2015-09-17 00:51:45 +02:00
def publish ( self , event ) :
if event [ ' Event ' ] [ ' published ' ] :
2015-09-23 18:47:47 +02:00
return { ' error ' : ' Already published ' }
2015-09-17 00:51:45 +02:00
event = self . _prepare_update ( event )
event [ ' Event ' ] [ ' published ' ] = True
2015-09-21 11:52:26 +02:00
response = self . update_event ( event [ ' Event ' ] [ ' id ' ] , event , ' json ' )
2015-09-23 18:47:47 +02:00
return self . _check_response ( response )
2015-09-17 00:51:45 +02:00
2015-09-02 11:50:15 +02:00
# ##### File attributes #####
def _send_attributes ( self , event , attributes ) :
event = self . _prepare_update ( event )
for a in attributes :
if a . get ( ' distribution ' ) is None :
a [ ' distribution ' ] = event [ ' Event ' ] [ ' distribution ' ]
event [ ' Event ' ] [ ' Attribute ' ] = attributes
2015-09-21 11:52:26 +02:00
response = self . update_event ( event [ ' Event ' ] [ ' id ' ] , event , ' json ' )
2015-09-23 18:47:47 +02:00
return self . _check_response ( response )
2015-09-02 11:50:15 +02:00
2015-09-01 18:46:10 +02:00
def add_hashes ( self , event , category = ' Artifacts dropped ' , filename = None , md5 = None , sha1 = None , sha256 = None , comment = None , to_ids = True , distribution = None ) :
2015-09-22 11:48:23 +02:00
categories = [ ' Payload delivery ' , ' Artifacts dropped ' , ' Payload installation ' , ' External analysis ' ]
2015-09-01 18:46:10 +02:00
if category not in categories :
raise NewAttributeError ( ' {} is invalid, category has to be in {} ' . format ( category , ( ' , ' . join ( categories ) ) ) )
attributes = [ ]
type_value = ' {} '
value = ' {} '
if filename :
type_value = ' filename| {} '
value = filename + ' | {} '
if md5 :
attributes . append ( self . _prepare_full_attribute ( category , type_value . format ( ' md5 ' ) , value . format ( md5 ) ,
to_ids , comment , distribution ) )
if sha1 :
attributes . append ( self . _prepare_full_attribute ( category , type_value . format ( ' sha1 ' ) , value . format ( sha1 ) ,
to_ids , comment , distribution ) )
if sha256 :
attributes . append ( self . _prepare_full_attribute ( category , type_value . format ( ' sha256 ' ) , value . format ( sha256 ) ,
to_ids , comment , distribution ) )
2015-09-02 11:50:15 +02:00
return self . _send_attributes ( event , attributes )
2015-09-01 18:46:10 +02:00
2015-09-02 11:50:15 +02:00
def add_regkey ( self , event , regkey , rvalue = None , category = ' Artifacts dropped ' , to_ids = True , comment = None , distribution = None ) :
2015-09-01 18:46:10 +02:00
type_value = ' {} '
value = ' {} '
if rvalue :
type_value = ' regkey|value '
value = ' {} | {} ' . format ( regkey , rvalue )
else :
type_value = ' regkey '
value = regkey
attributes = [ ]
attributes . append ( self . _prepare_full_attribute ( category , type_value , value , to_ids , comment , distribution ) )
2015-09-02 11:50:15 +02:00
return self . _send_attributes ( event , attributes )
2015-09-01 18:46:10 +02:00
2015-09-02 11:50:15 +02:00
def add_pattern ( self , event , pattern , in_file = True , in_memory = False , category = ' Artifacts dropped ' , to_ids = True , comment = None , distribution = None ) :
2015-09-01 18:46:10 +02:00
attributes = [ ]
if in_file :
attributes . append ( self . _prepare_full_attribute ( category , ' pattern-in-file ' , pattern , to_ids , comment , distribution ) )
if in_memory :
attributes . append ( self . _prepare_full_attribute ( category , ' pattern-in-memory ' , pattern , to_ids , comment , distribution ) )
2015-09-02 11:50:15 +02:00
return self . _send_attributes ( event , attributes )
2015-09-01 18:46:10 +02:00
2015-09-02 11:50:15 +02:00
def add_pipe ( self , event , named_pipe , category = ' Artifacts dropped ' , to_ids = True , comment = None , distribution = None ) :
2015-09-01 18:46:10 +02:00
attributes = [ ]
if not named_pipe . startswith ( ' \\ . \\ pipe \\ ' ) :
named_pipe = ' \\ . \\ pipe \\ {} ' . format ( named_pipe )
attributes . append ( self . _prepare_full_attribute ( category , ' named pipe ' , named_pipe , to_ids , comment , distribution ) )
2015-09-02 11:50:15 +02:00
return self . _send_attributes ( event , attributes )
2015-09-01 18:46:10 +02:00
2015-09-02 11:50:15 +02:00
def add_mutex ( self , event , mutex , category = ' Artifacts dropped ' , to_ids = True , comment = None , distribution = None ) :
2015-09-01 18:46:10 +02:00
attributes = [ ]
if not mutex . startswith ( ' \\ BaseNamedObjects \\ ' ) :
mutex = ' \\ BaseNamedObjects \\ {} ' . format ( mutex )
attributes . append ( self . _prepare_full_attribute ( category , ' mutex ' , mutex , to_ids , comment , distribution ) )
2015-09-02 11:50:15 +02:00
return self . _send_attributes ( event , attributes )
# ##### Network attributes #####
def add_ipdst ( self , event , ipdst , category = ' Network activity ' , to_ids = True , comment = None , distribution = None ) :
attributes = [ ]
attributes . append ( self . _prepare_full_attribute ( category , ' ip-dst ' , ipdst , to_ids , comment , distribution ) )
return self . _send_attributes ( event , attributes )
def add_hostname ( self , event , hostname , category = ' Network activity ' , to_ids = True , comment = None , distribution = None ) :
attributes = [ ]
attributes . append ( self . _prepare_full_attribute ( category , ' hostname ' , hostname , to_ids , comment , distribution ) )
return self . _send_attributes ( event , attributes )
def add_domain ( self , event , domain , category = ' Network activity ' , to_ids = True , comment = None , distribution = None ) :
attributes = [ ]
attributes . append ( self . _prepare_full_attribute ( category , ' domain ' , domain , to_ids , comment , distribution ) )
return self . _send_attributes ( event , attributes )
def add_url ( self , event , url , category = ' Network activity ' , to_ids = True , comment = None , distribution = None ) :
attributes = [ ]
attributes . append ( self . _prepare_full_attribute ( category , ' url ' , url , to_ids , comment , distribution ) )
return self . _send_attributes ( event , attributes )
def add_useragent ( self , event , useragent , category = ' Network activity ' , to_ids = True , comment = None , distribution = None ) :
attributes = [ ]
attributes . append ( self . _prepare_full_attribute ( category , ' user-agent ' , useragent , to_ids , comment , distribution ) )
return self . _send_attributes ( event , attributes )
def add_traffic_pattern ( self , event , pattern , category = ' Network activity ' , to_ids = True , comment = None , distribution = None ) :
attributes = [ ]
attributes . append ( self . _prepare_full_attribute ( category , ' pattern-in-traffic ' , pattern , to_ids , comment , distribution ) )
return self . _send_attributes ( event , attributes )
def add_snort ( self , event , snort , category = ' Network activity ' , to_ids = True , comment = None , distribution = None ) :
attributes = [ ]
attributes . append ( self . _prepare_full_attribute ( category , ' snort ' , snort , to_ids , comment , distribution ) )
return self . _send_attributes ( event , attributes )
2015-09-01 18:46:10 +02:00
2015-09-01 10:31:22 +02:00
# ##################################################
# ######### Upload samples through the API #########
# ##################################################
2015-08-04 16:24:55 +02:00
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-09-01 18:46:10 +02:00
authorized_categs = [ ' Payload delivery ' , ' Artifacts dropped ' , ' Payload Installation ' , ' External Analysis ' ]
2015-09-13 00:31:27 +02:00
if event_id is not None :
try :
event_id = int ( event_id )
except :
pass
2015-08-04 16:24:55 +02:00
if not isinstance ( event_id , int ) :
# New event
2015-09-01 18:46:10 +02:00
to_post [ ' request ' ] = self . _create_event ( distribution , threat_level_id , analysis , info )
2015-08-04 16:24:55 +02:00
else :
2015-09-01 18:46:10 +02:00
to_post [ ' request ' ] [ ' event_id ' ] = int ( event_id )
2015-08-04 16:24:55 +02:00
if to_ids not in [ True , False ] :
2015-09-01 18:46:10 +02:00
raise NewAttributeError ( ' {} is invalid, to_ids has to be True or False ' . format ( to_ids ) )
to_post [ ' request ' ] [ ' to_ids ' ] = to_ids
2015-08-04 16:24:55 +02:00
2015-09-01 18:46:10 +02:00
if category not in authorized_categs :
raise NewAttributeError ( ' {} is invalid, category has to be in {} ' . format ( category , ( ' , ' . join ( authorized_categs ) ) ) )
to_post [ ' request ' ] [ ' category ' ] = category
2015-08-04 16:24:55 +02:00
2015-08-06 01:57:59 +02:00
return to_post
2015-08-28 17:03:35 +02:00
def _encode_file_to_upload ( self , path ) :
with open ( path , ' rb ' ) as f :
return base64 . b64encode ( f . read ( ) )
2015-08-06 01:57:59 +02:00
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 )
2015-08-28 17:03:35 +02:00
to_post [ ' request ' ] [ ' files ' ] = [ { ' filename ' : filename , ' data ' : self . _encode_file_to_upload ( filepath ) } ]
2015-08-06 01:57:59 +02:00
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 )
2015-08-28 17:03:35 +02:00
files = [ ]
for path in filepaths :
if not os . path . isfile ( path ) :
continue
files . append ( { ' filename ' : os . path . basename ( path ) , ' data ' : self . _encode_file_to_upload ( path ) } )
to_post [ ' request ' ] [ ' files ' ] = files
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-09-21 11:52:26 +02:00
session = self . __prepare_session ( ' json ' )
2015-08-10 11:58:20 +02:00
url = urljoin ( self . root_url , ' events/upload_sample ' )
2015-09-23 18:47:47 +02:00
response = session . post ( url , data = json . dumps ( to_post ) )
return self . _check_response ( response )
2015-08-04 16:24:55 +02:00
2015-09-01 10:31:22 +02:00
# ##############################
2014-04-14 10:55:20 +02:00
# ######## REST Search #########
2015-09-01 10:31:22 +02:00
# ##############################
2014-04-11 18:45:52 +02:00
2015-09-21 11:52:26 +02:00
def __query ( self , session , path , query ) :
if query . get ( ' error ' ) is not None :
return query
url = urljoin ( self . root_url , ' events/ {} ' . format ( path . lstrip ( ' / ' ) ) )
query = { ' request ' : query }
2015-09-23 18:47:47 +02:00
response = session . post ( url , data = json . dumps ( query ) )
return self . _check_response ( response )
2015-09-21 11:52:26 +02:00
2015-08-06 17:42:41 +02:00
def search_all ( self , value ) :
query = { ' value ' : value , ' searchall ' : 1 }
2015-09-21 11:52:26 +02:00
session = self . __prepare_session ( ' json ' )
2015-08-06 17:42:41 +02:00
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
2015-09-21 11:52:26 +02:00
session = self . __prepare_session ( ' json ' )
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 ) )
2015-09-21 11:52:26 +02:00
session = self . __prepare_session ( ' json ' )
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 ' } }
2015-09-21 11:52:26 +02:00
session = self . __prepare_session ( ' json ' )
2015-08-19 10:42:24 +02:00
response = session . post ( urljoin ( self . root_url , ' attributes/restSearch ' ) , data = json . dumps ( to_post ) )
2015-09-23 18:47:47 +02:00
result = self . _check_response ( response )
if result . get ( ' error ' ) is not None :
return False , result . get ( ' error ' )
if not result . get ( ' response ' ) :
2015-08-19 10:42:24 +02:00
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 } }
2015-09-21 11:52:26 +02:00
session = self . __prepare_session ( ' json ' )
2015-08-07 17:24:03 +02:00
response = session . post ( urljoin ( self . root_url , ' attributes/downloadSample ' ) , data = json . dumps ( to_post ) )
2015-09-23 18:47:47 +02:00
result = self . _check_response ( response )
if result . get ( ' error ' ) is not None :
return False , result . get ( ' error ' )
if not result . get ( ' result ' ) :
2015-08-07 17:24:03 +02:00
return False , result . get ( ' message ' )
details = [ ]
for f in result [ ' result ' ] :
2015-09-12 23:08:06 +02:00
decoded = base64 . b64decode ( f [ ' base64 ' ] )
zipped = BytesIO ( decoded )
2015-08-24 12:05:49 +02:00
try :
2015-09-18 14:38:52 +02:00
archive = zipfile . ZipFile ( zipped )
try :
# New format
unzipped = BytesIO ( archive . open ( f [ ' md5 ' ] , pwd = ' infected ' ) . read ( ) )
except KeyError :
# Old format
unzipped = BytesIO ( archive . open ( f [ ' filename ' ] , pwd = ' infected ' ) . read ( ) )
details . append ( [ f [ ' event_id ' ] , f [ ' filename ' ] , unzipped ] )
except zipfile . BadZipfile :
# In case the sample isn't zipped
details . append ( [ f [ ' event_id ' ] , f [ ' filename ' ] , zipped ] )
2015-08-07 17:24:03 +02:00
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 )
2015-08-28 17:03:35 +02:00
# ############## Suricata ###############
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
2015-09-17 13:51:31 +02:00
# ########## Version ##########
2015-09-18 12:03:56 +02:00
def get_api_version ( self ) :
"""
Returns the current version of PyMISP installed on the system
"""
return { ' version ' : __version__ }
def get_api_version_master ( self ) :
"""
Get the most recent version of PyMISP from github
"""
r = requests . get ( ' https://raw.githubusercontent.com/MISP/PyMISP/master/pymisp/__init__.py ' )
if r . status_code == 200 :
version = re . findall ( " __version__ = ' (.*) ' " , r . text )
return { ' version ' : version [ 0 ] }
else :
2015-09-23 18:47:47 +02:00
return { ' error ' : ' Impossible to retrieve the version of the master branch. ' }
2015-09-18 12:03:56 +02:00
2015-09-17 13:51:31 +02:00
def get_version ( self ) :
"""
Returns the version of the instance .
"""
2015-09-21 11:52:26 +02:00
session = self . __prepare_session ( ' json ' )
2015-09-17 13:51:31 +02:00
url = urljoin ( self . root_url , ' servers/getVersion ' )
2015-09-23 18:47:47 +02:00
response = session . get ( url )
return self . _check_response ( response )
2015-09-17 13:51:31 +02:00
def get_version_master ( self ) :
"""
Get the most recent version from github
"""
r = requests . get ( ' https://raw.githubusercontent.com/MISP/MISP/master/VERSION.json ' )
if r . status_code == 200 :
master_version = json . loads ( r . text )
return { ' version ' : ' {} . {} . {} ' . format ( master_version [ ' major ' ] , master_version [ ' minor ' ] , master_version [ ' hotfix ' ] ) }
else :
2015-09-23 18:47:47 +02:00
return { ' error ' : ' Impossible to retrieve the version of the master branch. ' }
2015-09-17 13:51:31 +02:00
2015-08-28 17:03:35 +02:00
# ############## Deprecated (Pure XML API should not be used) ##################
@deprecated
def download_all ( self ) :
"""
Download all event from the instance
"""
xml = urljoin ( self . root_url , ' events/xml/download ' )
session = self . __prepare_session ( ' xml ' )
return session . get ( xml )
@deprecated
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 )