Merge pull request #379 from cudeso/master

Cytomic Orion MISP Module
pull/380/head
Alexandre Dulaunoy 2020-03-11 22:30:48 +01:00 committed by GitHub
commit 0e84ea7ad3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 194 additions and 1 deletions

View File

@ -33,6 +33,7 @@ For more information: [Extending MISP with Python modules](https://www.misp-proj
* [CVE](misp_modules/modules/expansion/cve.py) - a hover module to give more information about a vulnerability (CVE).
* [CVE advanced](misp_modules/modules/expansion/cve_advanced.py) - An expansion module to query the CIRCL CVE search API for more information about a vulnerability (CVE).
* [Cuckoo submit](misp_modules/modules/expansion/cuckoo_submit.py) - A hover module to submit malware sample, url, attachment, domain to Cuckoo Sandbox.
* [Cytomic Orion](misp_modules/modules/expansion/cytomic_orion.py) - An expansion module to enrich attributes in MISP and share indicators of compromise with Cytomic Orion.
* [DBL Spamhaus](misp_modules/modules/expansion/dbl_spamhaus.py) - a hover module to check Spamhaus DBL for a domain name.
* [DNS](misp_modules/modules/expansion/dns.py) - a simple module to resolve MISP attributes like hostname and domain to expand IP addresses attributes.
* [docx-enrich](misp_modules/modules/expansion/docx_enrich.py) - an enrichment module to get text out of Word document into MISP (using free-text parser).

View File

@ -0,0 +1,9 @@
{
"description": "An expansion module to enrich attributes in MISP by quering the Cytomic Orion API",
"logo": "logos/cytomic_orion.png",
"requirements": ["Access (license) to Cytomic Orion"],
"input": "MD5, hash of the sample / malware to search for.",
"output": "MISP objects with sightings of the hash in Cytomic Orion. Includes files and machines.",
"references": ["https://www.vanimpe.eu/2020/03/10/integrating-misp-and-cytomic-orion/", "https://www.cytomicmodel.com/solutions/"],
"features": "This module takes an MD5 hash and searches for occurrences of this hash in the Cytomic Orion database. Returns observed files and machines."
}

BIN
doc/logos/cytomic_orion.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 898 B

View File

@ -16,4 +16,4 @@ __all__ = ['cuckoo_submit', 'vmray_submit', 'bgpranking', 'circl_passivedns', 'c
'ods_enrich', 'odt_enrich', 'joesandbox_submit', 'joesandbox_query', 'urlhaus',
'virustotal_public', 'apiosintds', 'urlscan', 'securitytrails', 'apivoid',
'assemblyline_submit', 'assemblyline_query', 'ransomcoindb',
'lastline_query', 'lastline_submit', 'sophoslabs_intelix']
'lastline_query', 'lastline_submit', 'sophoslabs_intelix', 'cytomic_orion']

View File

@ -0,0 +1,183 @@
#!/usr/bin/env python3
'''
Cytomic Orion MISP Module
An expansion module to enrich attributes in MISP and share indicators of compromise with Cytomic Orion
'''
from pymisp import MISPAttribute, MISPEvent, MISPObject
import json
import requests
import sys
misperrors = {'error': 'Error'}
mispattributes = {'input': ['md5'], 'format': 'misp_standard'}
moduleinfo = {'version': '0.3', 'author': 'Koen Van Impe',
'description': 'an expansion module to enrich attributes in MISP and share indicators of compromise with Cytomic Orion',
'module-type': ['expansion']}
moduleconfig = ['api_url', 'token_url', 'clientid', 'clientsecret', 'clientsecret', 'username', 'password', 'upload_timeframe', 'upload_tag', 'delete_tag', 'upload_ttlDays', 'upload_threat_level_id', 'limit_upload_events', 'limit_upload_attributes']
# There are more config settings in this module than used by the enrichment
# There is also a PyMISP module which reuses the module config, and requires additional configuration, for example used for pushing indicators to the API
class CytomicParser():
def __init__(self, attribute, config_object):
self.misp_event = MISPEvent()
self.attribute = MISPAttribute()
self.attribute.from_dict(**attribute)
self.misp_event.add_attribute(**self.attribute)
self.config_object = config_object
if self.config_object:
self.token = self.get_token()
else:
sys.exit('Missing configuration')
def get_token(self):
try:
scope = self.config_object['scope']
grant_type = self.config_object['grant_type']
username = self.config_object['username']
password = self.config_object['password']
token_url = self.config_object['token_url']
clientid = self.config_object['clientid']
clientsecret = self.config_object['clientsecret']
if scope and grant_type and username and password:
data = {'scope': scope, 'grant_type': grant_type, 'username': username, 'password': password}
if token_url and clientid and clientsecret:
access_token_response = requests.post(token_url, data=data, verify=False, allow_redirects=False, auth=(clientid, clientsecret))
tokens = json.loads(access_token_response.text)
if 'access_token' in tokens:
return tokens['access_token']
else:
self.result = {'error': 'No token received.'}
return
else:
self.result = {'error': 'No token_url, clientid or clientsecret supplied.'}
return
else:
self.result = {'error': 'No scope, grant_type, username or password supplied.'}
return
except Exception:
self.result = {'error': 'Unable to connect to token_url.'}
return
def get_results(self):
if hasattr(self, 'result'):
return self.result
event = json.loads(self.misp_event.to_json())
results = {key: event[key] for key in ('Attribute', 'Object')}
return {'results': results}
def parse(self, searchkey):
if self.token:
endpoint_fileinformation = self.config_object['endpoint_fileinformation']
endpoint_machines = self.config_object['endpoint_machines']
endpoint_machines_client = self.config_object['endpoint_machines_client']
query_machines = self.config_object['query_machines']
query_machine_info = self.config_object['query_machine_info']
# Update endpoint URLs
query_endpoint_fileinformation = endpoint_fileinformation.format(md5=searchkey)
query_endpoint_machines = endpoint_machines.format(md5=searchkey)
# API calls
api_call_headers = {'Authorization': 'Bearer ' + self.token}
result_query_endpoint_fileinformation = requests.get(query_endpoint_fileinformation, headers=api_call_headers, verify=False)
json_result_query_endpoint_fileinformation = json.loads(result_query_endpoint_fileinformation.text)
if json_result_query_endpoint_fileinformation:
cytomic_object = MISPObject('cytomic-orion-file')
cytomic_object.add_attribute('fileName', type='text', value=json_result_query_endpoint_fileinformation['fileName'])
cytomic_object.add_attribute('fileSize', type='text', value=json_result_query_endpoint_fileinformation['fileSize'])
cytomic_object.add_attribute('last-seen', type='datetime', value=json_result_query_endpoint_fileinformation['lastSeen'])
cytomic_object.add_attribute('first-seen', type='datetime', value=json_result_query_endpoint_fileinformation['firstSeen'])
cytomic_object.add_attribute('classification', type='text', value=json_result_query_endpoint_fileinformation['classification'])
cytomic_object.add_attribute('classificationName', type='text', value=json_result_query_endpoint_fileinformation['classificationName'])
self.misp_event.add_object(**cytomic_object)
result_query_endpoint_machines = requests.get(query_endpoint_machines, headers=api_call_headers, verify=False)
json_result_query_endpoint_machines = json.loads(result_query_endpoint_machines.text)
if query_machines and json_result_query_endpoint_machines and len(json_result_query_endpoint_machines) > 0:
for machine in json_result_query_endpoint_machines:
if query_machine_info and machine['muid']:
query_endpoint_machines_client = endpoint_machines_client.format(muid=machine['muid'])
result_endpoint_machines_client = requests.get(query_endpoint_machines_client, headers=api_call_headers, verify=False)
json_result_endpoint_machines_client = json.loads(result_endpoint_machines_client.text)
if json_result_endpoint_machines_client:
cytomic_machine_object = MISPObject('cytomic-orion-machine')
clienttag = [{'name': json_result_endpoint_machines_client['clientName']}]
cytomic_machine_object.add_attribute('machineName', type='target-machine', value=json_result_endpoint_machines_client['machineName'], Tag=clienttag)
cytomic_machine_object.add_attribute('machineMuid', type='text', value=machine['muid'])
cytomic_machine_object.add_attribute('clientName', type='target-org', value=json_result_endpoint_machines_client['clientName'], Tag=clienttag)
cytomic_machine_object.add_attribute('clientId', type='text', value=machine['clientId'])
cytomic_machine_object.add_attribute('machinePath', type='text', value=machine['lastPath'])
cytomic_machine_object.add_attribute('first-seen', type='datetime', value=machine['firstSeen'])
cytomic_machine_object.add_attribute('last-seen', type='datetime', value=machine['lastSeen'])
cytomic_machine_object.add_attribute('creationDate', type='datetime', value=json_result_endpoint_machines_client['creationDate'])
cytomic_machine_object.add_attribute('clientCreationDateUTC', type='datetime', value=json_result_endpoint_machines_client['clientCreationDateUTC'])
cytomic_machine_object.add_attribute('lastSeenUtc', type='datetime', value=json_result_endpoint_machines_client['lastSeenUtc'])
self.misp_event.add_object(**cytomic_machine_object)
else:
self.result = {'error': 'No (valid) token.'}
return
def handler(q=False):
if q is False:
return False
request = json.loads(q)
if not request.get('attribute'):
return {'error': 'Unsupported input.'}
attribute = request['attribute']
if not any(input_type == attribute['type'] for input_type in mispattributes['input']):
return {'error': 'Unsupported attributes type'}
if not request.get('config'):
return {'error': 'Missing configuration'}
config_object = {
'clientid': request["config"].get("clientid"),
'clientsecret': request["config"].get("clientsecret"),
'scope': 'orion.api',
'password': request["config"].get("password"),
'username': request["config"].get("username"),
'grant_type': 'password',
'token_url': request["config"].get("token_url"),
'endpoint_fileinformation': '{api_url}{endpoint}'.format(api_url=request["config"].get("api_url"), endpoint='/forensics/md5/{md5}/info'),
'endpoint_machines': '{api_url}{endpoint}'.format(api_url=request["config"].get("api_url"), endpoint='/forensics/md5/{md5}/muids'),
'endpoint_machines_client': '{api_url}{endpoint}'.format(api_url=request["config"].get("api_url"), endpoint='/forensics/muid/{muid}/info'),
'query_machines': True,
'query_machine_info': True
}
cytomic_parser = CytomicParser(attribute, config_object)
cytomic_parser.parse(attribute['value'])
return cytomic_parser.get_results()
def introspection():
return mispattributes
def version():
moduleinfo['config'] = moduleconfig
return moduleinfo