2019-04-19 14:06:35 +02:00
import base64
import io
2019-04-18 00:23:38 +02:00
import json
2019-04-19 14:06:35 +02:00
import logging
2019-04-18 00:23:38 +02:00
import requests
2019-04-19 14:06:35 +02:00
import sys
import urllib . parse
import zipfile
2019-04-18 00:23:38 +02:00
2019-04-19 14:06:35 +02:00
from requests . exceptions import RequestException
2019-04-19 16:24:30 +02:00
log = logging . getLogger ( " cuckoo_submit " )
2019-04-19 14:06:35 +02:00
log . setLevel ( logging . DEBUG )
sh = logging . StreamHandler ( sys . stdout )
sh . setLevel ( logging . DEBUG )
fmt = logging . Formatter (
2019-04-19 16:24:30 +02:00
" %(asctime)s - %(name)s - %(levelname)s - %(message)s "
2019-04-19 14:06:35 +02:00
)
sh . setFormatter ( fmt )
log . addHandler ( sh )
moduleinfo = {
2024-08-12 11:23:10 +02:00
' version ' : ' 0.1 ' ,
' author ' : ' Evert Kors ' ,
' description ' : ' Submit files and URLs to Cuckoo Sandbox ' ,
' module-type ' : [ ' expansion ' , ' hover ' ] ,
' name ' : ' Cuckoo Submit ' ,
' logo ' : ' cuckoo.png ' ,
' requirements ' : [ ' Access to a Cuckoo Sandbox API and an API key if the API requires it. (api_url and api_key) ' ] ,
' features ' : ' The module takes a malware-sample, attachment, url or domain and submits it to Cuckoo Sandbox. \n The returned task id can be used to retrieve results when the analysis completed. ' ,
' references ' : [ ' https://cuckoosandbox.org/ ' , ' https://cuckoo.sh/docs/ ' ] ,
' input ' : ' A malware-sample or attachment for files. A url or domain for URLs. ' ,
' output ' : " A text field containing ' Cuckoo task id: <id> ' " ,
2019-04-19 14:06:35 +02:00
}
misperrors = { " error " : " Error " }
2019-04-19 16:24:30 +02:00
moduleconfig = [ " api_url " , " api_key " ]
2019-04-18 00:23:38 +02:00
mispattributes = {
2019-04-19 16:24:30 +02:00
" input " : [ " attachment " , " malware-sample " , " url " , " domain " ] ,
2019-04-19 14:06:35 +02:00
" output " : [ " text " ]
2019-04-18 00:23:38 +02:00
}
2019-04-19 14:06:35 +02:00
class APIKeyError ( RequestException ) :
""" Raised if the Cuckoo API returns a 401. This means no or an invalid
bearer token was supplied . """
pass
class CuckooAPI ( object ) :
def __init__ ( self , api_url , api_key = " " ) :
self . api_key = api_key
if not api_url . startswith ( " http " ) :
api_url = " https:// {} " . format ( api_url )
self . api_url = api_url
def _post_api ( self , endpoint , files = None , data = { } ) :
data . update ( {
" owner " : " MISP "
} )
try :
response = requests . post (
urllib . parse . urljoin ( self . api_url , endpoint ) ,
files = files , data = data ,
2019-04-19 16:24:30 +02:00
headers = { " Authorization " : " Bearer {} " . format ( self . api_key ) }
2019-04-19 14:06:35 +02:00
)
except RequestException as e :
log . error ( " Failed to submit sample to Cuckoo Sandbox. %s " , e )
return None
if response . status_code == 401 :
raise APIKeyError ( " Invalid or no Cuckoo Sandbox API key provided " )
2019-04-19 16:24:30 +02:00
if response . status_code != 200 :
log . error ( " Invalid Cuckoo API response " )
return None
2019-04-19 14:06:35 +02:00
return response . json ( )
def create_task ( self , filename , fp ) :
response = self . _post_api (
" /tasks/create/file " , files = { " file " : ( filename , fp ) }
)
if not response :
return False
return response [ " task_id " ]
def create_url ( self , url ) :
response = self . _post_api (
" /tasks/create/url " , data = { " url " : url }
)
if not response :
return False
return response [ " task_id " ]
2019-04-18 00:23:38 +02:00
def handler ( q = False ) :
if q is False :
return False
2019-04-19 14:06:35 +02:00
2019-04-18 00:23:38 +02:00
request = json . loads ( q )
2019-04-19 14:06:35 +02:00
# See if the API URL was provided. The API key is optional, as it can
# be disabled in the Cuckoo API settings.
api_url = request [ " config " ] . get ( " api_url " )
api_key = request [ " config " ] . get ( " api_key " , " " )
if not api_url :
misperrors [ " error " ] = " No Cuckoo API URL provided "
2019-04-18 00:23:38 +02:00
return misperrors
2019-04-19 14:06:35 +02:00
url = request . get ( " url " ) or request . get ( " domain " )
data = request . get ( " data " )
filename = None
if data :
data = base64 . b64decode ( data )
2019-04-18 00:23:38 +02:00
2019-04-19 14:06:35 +02:00
if " malware-sample " in request :
filename = request . get ( " malware-sample " ) . split ( " | " , 1 ) [ 0 ]
with zipfile . ZipFile ( io . BytesIO ( data ) ) as zipf :
data = zipf . read ( zipf . namelist ( ) [ 0 ] , pwd = b " infected " )
2019-04-18 00:23:38 +02:00
2019-04-19 14:06:35 +02:00
elif " attachment " in request :
filename = request . get ( " attachment " )
2019-04-18 00:23:38 +02:00
2019-04-19 14:06:35 +02:00
cuckoo_api = CuckooAPI ( api_url = api_url , api_key = api_key )
task_id = None
2019-04-18 00:23:38 +02:00
try :
2019-04-19 14:06:35 +02:00
if url :
log . debug ( " Submitting URL to Cuckoo Sandbox %s " , api_url )
task_id = cuckoo_api . create_url ( url )
elif data and filename :
log . debug ( " Submitting file to Cuckoo Sandbox %s " , api_url )
task_id = cuckoo_api . create_task (
filename = filename , fp = io . BytesIO ( data )
)
except APIKeyError as e :
misperrors [ " error " ] = " Failed to submit to Cuckoo: {} " . format ( e )
return misperrors
if not task_id :
misperrors [ " error " ] = " File or URL submission failed "
2019-04-18 00:23:38 +02:00
return misperrors
2019-04-19 14:06:35 +02:00
return {
" results " : [
{ " types " : " text " , " values " : " Cuckoo task id: {} " . format ( task_id ) }
]
}
2019-04-18 00:23:38 +02:00
def introspection ( ) :
return mispattributes
def version ( ) :
2019-04-19 16:24:30 +02:00
moduleinfo [ " config " ] = moduleconfig
2019-04-18 00:23:38 +02:00
return moduleinfo