mirror of https://github.com/MISP/misp-modules
148 lines
6.7 KiB
Python
Executable File
148 lines
6.7 KiB
Python
Executable File
import json
|
|
from . import check_input_attribute, standard_error_message
|
|
from falconpy import Intel
|
|
from pymisp import MISPAttribute, MISPEvent
|
|
|
|
moduleinfo = {
|
|
'version': '0.2',
|
|
'author': 'Christophe Vandeplas',
|
|
'description': 'Module to query CrowdStrike Falcon.',
|
|
'module-type': ['expansion', 'hover'],
|
|
'name': 'CrowdStrike Falcon',
|
|
'logo': 'crowdstrike.png',
|
|
'requirements': ['A CrowdStrike API access (API id & key)'],
|
|
'features': 'This module takes a MISP attribute as input to query a CrowdStrike Falcon API. The API returns then the result of the query with some types we map into compatible types we add as MISP attributes.\n\nPlease note that composite attributes composed by at least one of the input types mentionned below (domains, IPs, hostnames) are also supported.',
|
|
'references': ['https://www.crowdstrike.com/products/crowdstrike-falcon-faq/'],
|
|
'input': 'A MISP attribute included in the following list:\n- domain\n- email-attachment\n- email-dst\n- email-reply-to\n- email-src\n- email-subject\n- filename\n- hostname\n- ip-src\n- ip-dst\n- md5\n- mutex\n- regkey\n- sha1\n- sha256\n- uri\n- url\n- user-agent\n- whois-registrant-email\n- x509-fingerprint-md5',
|
|
'output': 'MISP attributes mapped after the CrowdStrike API has been queried, included in the following list:\n- hostname\n- email-src\n- email-subject\n- filename\n- md5\n- sha1\n- sha256\n- ip-dst\n- ip-dst\n- mutex\n- regkey\n- url\n- user-agent\n- x509-fingerprint-md5',
|
|
}
|
|
moduleconfig = ['api_id', 'apikey']
|
|
misperrors = {'error': 'Error'}
|
|
misp_type_in = ['domain', 'email-attachment', 'email-dst', 'email-reply-to', 'email-src', 'email-subject',
|
|
'filename', 'hostname', 'ip', 'ip-src', 'ip-dst', 'md5', 'mutex', 'regkey', 'sha1', 'sha256', 'uri', 'url',
|
|
'user-agent', 'whois-registrant-email', 'x509-fingerprint-md5']
|
|
mapping_out = { # mapping between the MISP attributes type and the compatible CrowdStrike indicator types.
|
|
'domain': {'type': 'hostname', 'to_ids': True},
|
|
'email_address': {'type': 'email-src', 'to_ids': True},
|
|
'email_subject': {'type': 'email-subject', 'to_ids': True},
|
|
'file_name': {'type': 'filename', 'to_ids': True},
|
|
'hash_md5': {'type': 'md5', 'to_ids': True},
|
|
'hash_sha1': {'type': 'sha1', 'to_ids': True},
|
|
'hash_sha256': {'type': 'sha256', 'to_ids': True},
|
|
'ip_address': {'type': 'ip-dst', 'to_ids': True},
|
|
'ip_address_block': {'type': 'ip-dst', 'to_ids': True},
|
|
'mutex_name': {'type': 'mutex', 'to_ids': True},
|
|
'registry': {'type': 'regkey', 'to_ids': True},
|
|
'url': {'type': 'url', 'to_ids': True},
|
|
'user_agent': {'type': 'user-agent', 'to_ids': True},
|
|
'x509_serial': {'type': 'x509-fingerprint-md5', 'to_ids': True},
|
|
|
|
'actors': {'type': 'threat-actor', 'category': 'Attribution'},
|
|
'malware_families': {'type': 'text', 'category': 'Attribution'}
|
|
}
|
|
misp_type_out = [item['type'] for item in mapping_out.values()]
|
|
mispattributes = {'input': misp_type_in, 'format': 'misp_standard'}
|
|
|
|
def handler(q=False):
|
|
if q is False:
|
|
return False
|
|
request = json.loads(q)
|
|
#validate CrowdStrike params
|
|
if (request.get('config')):
|
|
if (request['config'].get('apikey') is None):
|
|
misperrors['error'] = 'CrowdStrike apikey is missing'
|
|
return misperrors
|
|
if (request['config'].get('api_id') is None):
|
|
misperrors['error'] = 'CrowdStrike api_id is missing'
|
|
return misperrors
|
|
|
|
#validate attribute
|
|
if not request.get('attribute') or not check_input_attribute(request['attribute']):
|
|
return {'error': f'{standard_error_message}, which should contain at least a type, a value and an uuid.'}
|
|
attribute = request.get('attribute')
|
|
if not any(input_type == attribute.get('type') for input_type in misp_type_in):
|
|
return {'error': 'Unsupported attribute type.'}
|
|
|
|
client = CSIntelAPI(request['config']['api_id'], request['config']['apikey'])
|
|
|
|
attribute = MISPAttribute()
|
|
attribute.from_dict(**request.get('attribute') )
|
|
r = {"results": []}
|
|
valid_type = False
|
|
|
|
try:
|
|
for k in misp_type_in:
|
|
if attribute.type == k:
|
|
# map the MISP type to the CrowdStrike type
|
|
r['results'].append(lookup_indicator(client, attribute))
|
|
valid_type = True
|
|
except Exception as e:
|
|
return {'error': f"{e}"}
|
|
|
|
if not valid_type:
|
|
misperrors['error'] = "Unsupported attributes type"
|
|
return misperrors
|
|
return {'results': r.get('results').pop()}
|
|
|
|
|
|
def lookup_indicator(client, ref_attribute):
|
|
result = client.search_indicator(ref_attribute.value)
|
|
misp_event = MISPEvent()
|
|
misp_event.add_attribute(**ref_attribute)
|
|
|
|
for item in result.get('resources', []):
|
|
for relation in item.get('relations'):
|
|
if mapping_out.get(relation.get('type')):
|
|
r = mapping_out[relation.get('type')].copy()
|
|
r['value'] = relation.get('indicator')
|
|
attribute = MISPAttribute()
|
|
attribute.from_dict(**r)
|
|
misp_event.add_attribute(**attribute)
|
|
for actor in item.get('actors'):
|
|
r = mapping_out.get('actors').copy()
|
|
r['value'] = actor
|
|
attribute = MISPAttribute()
|
|
attribute.from_dict(**r)
|
|
misp_event.add_attribute(**attribute)
|
|
if item.get('malware_families'):
|
|
r = mapping_out.get('malware_families').copy()
|
|
r['value'] = f"malware_families: {' | '.join(item.get('malware_families'))}"
|
|
attribute = MISPAttribute()
|
|
attribute.from_dict(**r)
|
|
misp_event.add_attribute(**attribute)
|
|
|
|
event = json.loads(misp_event.to_json())
|
|
return {'Object': event.get('Object', []), 'Attribute': event.get('Attribute', [])}
|
|
|
|
def introspection():
|
|
return mispattributes
|
|
|
|
|
|
def version():
|
|
moduleinfo['config'] = moduleconfig
|
|
return moduleinfo
|
|
|
|
|
|
class CSIntelAPI():
|
|
def __init__(self, custid=None, custkey=None):
|
|
# customer id and key should be passed when obj is created
|
|
self.falcon = Intel(client_id=custid, client_secret=custkey)
|
|
|
|
def search_indicator(self, query):
|
|
r = self.falcon.query_indicator_entities(q=query)
|
|
# 400 - bad request
|
|
if r.get('status_code') == 400:
|
|
raise Exception('HTTP Error 400 - Bad request.')
|
|
|
|
# 404 - oh shit
|
|
if r.get('status_code') == 404:
|
|
raise Exception('HTTP Error 404 - awww snap.')
|
|
|
|
# catch all?
|
|
if r.get('status_code') != 200:
|
|
raise Exception('HTTP Error: ' + str(r.get('status_code')))
|
|
|
|
if len(r.get('body').get('errors')):
|
|
raise Exception('API Error: ' + ' | '.join(r.get('body').get('errors')))
|
|
|
|
return r.get('body', {}) |