mirror of https://github.com/MISP/misp-modules
				
				
				
			
		
			
				
	
	
		
			187 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
			
		
		
	
	
			187 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
#!/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 . import check_input_attribute, standard_error_message
 | 
						|
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.'}
 | 
						|
 | 
						|
    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['attribute']
 | 
						|
    if not any(input_type == attribute['type'] for input_type in mispattributes['input']):
 | 
						|
        return {'error': 'Unsupported attribute 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
 |