diff --git a/src/MISP_maltego/transforms/attributetoevent.py b/src/MISP_maltego/transforms/attributetoevent.py index 14b3cbc..fc525ca 100644 --- a/src/MISP_maltego/transforms/attributetoevent.py +++ b/src/MISP_maltego/transforms/attributetoevent.py @@ -1,7 +1,7 @@ from canari.maltego.entities import Unknown, Hashtag from canari.maltego.transform import Transform from MISP_maltego.transforms.common.entities import MISPGalaxy -from MISP_maltego.transforms.common.util import check_update, get_misp_connection, event_to_entity, object_to_entity, get_attribute_in_event, get_attribute_in_object, attribute_to_entity, get_entity_property, search_galaxy_cluster, galaxycluster_to_entity, tag_matches_note_prefix +from MISP_maltego.transforms.common.util import check_update, MISPConnection, event_to_entity, get_attribute_in_event, get_attribute_in_object, attribute_to_entity, get_entity_property, search_galaxy_cluster, galaxycluster_to_entity from canari.maltego.message import LinkDirection, Bookmark __author__ = 'Christophe Vandeplas' @@ -26,20 +26,20 @@ class SearchInMISP(Transform): link_label = 'Search result' if 'properties.mispevent' in request.entity.fields: - misp = get_misp_connection(config, request.parameters) + conn = MISPConnection(config, request.parameters) # if event_id try: if request.entity.value == '0': return response eventid = int(request.entity.value) - events_json = misp.search(controller='events', eventid=eventid, with_attachments=False) + events_json = conn.misp.search(controller='events', eventid=eventid, with_attachments=False) for e in events_json: response += event_to_entity(e, link_label=link_label, link_direction=LinkDirection.OutputToInput) return response except ValueError: pass # if event_info string as value - events_json = misp.search(controller='events', eventinfo=request.entity.value, with_attachments=False) + events_json = conn.misp.search(controller='events', eventinfo=request.entity.value, with_attachments=False) for e in events_json: response += event_to_entity(e, link_label=link_label, link_direction=LinkDirection.OutputToInput) return response @@ -68,8 +68,8 @@ class SearchInMISP(Transform): keyword = get_entity_property(request.entity, 'Temp') if not keyword: keyword = request.entity.value - misp = get_misp_connection(config, request.parameters) - result = misp.direct_call('tags/search', {'name': keyword}) + conn = MISPConnection(config, request.parameters) + result = conn.misp.direct_call('tags/search', {'name': keyword}) for t in result: # skip misp-galaxies as we have processed them earlier on if t['Tag']['name'].startswith('misp-galaxy'): @@ -80,8 +80,8 @@ class SearchInMISP(Transform): return response # for all other normal entities - misp = get_misp_connection(config, request.parameters) - events_json = misp.search(controller='events', value=request.entity.value, with_attachments=False) + conn = MISPConnection(config, request.parameters) + events_json = conn.misp.search(controller='events', value=request.entity.value, with_attachments=False) # we need to do really rebuild the Entity from scratch as request.entity is of type Unknown for e in events_json: # find the value as attribute @@ -93,7 +93,7 @@ class SearchInMISP(Transform): if 'Object' in e['Event']: for o in e['Event']['Object']: if get_attribute_in_object(o, attribute_value=request.entity.value, substring=True).get('value'): - response += object_to_entity(o, link_label=link_label) + response += conn.object_to_entity(o, link_label=link_label) return response @@ -137,20 +137,20 @@ class AttributeToEvent(Transform): # placeholder for https://github.com/MISP/MISP-maltego/issues/11 pass - misp = get_misp_connection(config, request.parameters) + conn = MISPConnection(config, request.parameters) # from Galaxy if 'properties.mispgalaxy' in request.entity.fields: tag_name = get_entity_property(request.entity, 'tag_name') if not tag_name: tag_name = request.entity.value - events_json = misp.search(controller='events', tags=tag_name, with_attachments=False) + events_json = conn.misp.search(controller='events', tags=tag_name, with_attachments=False) for e in events_json: response += event_to_entity(e, link_direction=LinkDirection.OutputToInput) return response # from Object elif 'properties.mispobject' in request.entity.fields: if request.entity.fields.get('event_id'): - events_json = misp.search(controller='events', eventid=request.entity.fields.get('event_id').value, with_attachments=False) + events_json = conn.misp.search(controller='events', eventid=request.entity.fields.get('event_id').value, with_attachments=False) for e in events_json: response += event_to_entity(e, link_direction=LinkDirection.OutputToInput) return response @@ -161,13 +161,13 @@ class AttributeToEvent(Transform): tag_name = get_entity_property(request.entity, 'Temp') if not tag_name: tag_name = request.entity.value - events_json = misp.search(controller='events', tags=tag_name, with_attachments=False) + events_json = conn.misp.search(controller='events', tags=tag_name, with_attachments=False) for e in events_json: response += event_to_entity(e, link_direction=LinkDirection.OutputToInput) return response # standard Entities (normal attributes) else: - events_json = misp.search(controller='events', value=request.entity.value, with_attachments=False) + events_json = conn.misp.search(controller='events', value=request.entity.value, with_attachments=False) # return the MISPEvent or MISPObject of the attribute for e in events_json: @@ -179,6 +179,6 @@ class AttributeToEvent(Transform): if 'Object' in e['Event']: for o in e['Event']['Object']: if get_attribute_in_object(o, attribute_value=request.entity.value).get('value'): - response += object_to_entity(o, link_direction=LinkDirection.OutputToInput) + response += conn.object_to_entity(o, link_direction=LinkDirection.OutputToInput) return response diff --git a/src/MISP_maltego/transforms/common/util.py b/src/MISP_maltego/transforms/common/util.py index bb6cecf..c34bb4c 100644 --- a/src/MISP_maltego/transforms/common/util.py +++ b/src/MISP_maltego/transforms/common/util.py @@ -18,7 +18,6 @@ __version__ = '1.4.4' # also update version in setup.py tag_note_prefixes = ['tlp:', 'PAP:', 'de-vs:', 'euci:', 'fr-classif:', 'nato:'] -misp_connection = None update_url = 'https://raw.githubusercontent.com/MISP/MISP-maltego/master/setup.py' local_path_root = os.path.join(tempfile.gettempdir(), 'MISP-maltego') local_path_version = os.path.join(local_path_root, 'versioncheck') @@ -64,37 +63,116 @@ def check_update(config): return None -def get_misp_connection(config=None, parameters=None): - global misp_connection - if misp_connection: - return misp_connection - if not config: - raise MaltegoException("ERROR: MISP connection not yet established, and config not provided as parameter.") - misp_verify = True - misp_debug = False - misp_url = None - misp_key = None - try: - if is_local_exec_mode(): - misp_url = config['MISP_maltego.local.misp_url'] - misp_key = config['MISP_maltego.local.misp_key'] - if config['MISP_maltego.local.misp_verify'] in ['False', 'false', 0, 'no', 'No']: - misp_verify = False - if config['MISP_maltego.local.misp_debug'] in ['True', 'true', 1, 'yes', 'Yes']: - misp_debug = True - if is_remote_exec_mode(): +class MISPConnection(): + def __init__(self, config=None, parameters=None): + self.misp = None + + if not config: + raise MaltegoException("ERROR: MISP connection not yet established, and config not provided as parameter.") + misp_verify = True + misp_debug = False + misp_url = None + misp_key = None + try: + if is_local_exec_mode(): + misp_url = config['MISP_maltego.local.misp_url'] + misp_key = config['MISP_maltego.local.misp_key'] + if config['MISP_maltego.local.misp_verify'] in ['False', 'false', 0, 'no', 'No']: + misp_verify = False + if config['MISP_maltego.local.misp_debug'] in ['True', 'true', 1, 'yes', 'Yes']: + misp_debug = True + else: + try: + misp_url = parameters['mispurl'].value + misp_key = parameters['mispkey'].value + except AttributeError: + raise MaltegoException("ERROR: mispurl and mispkey need to be set to something valid") + self.misp = PyMISP(misp_url, misp_key, misp_verify, 'json', misp_debug, tool='misp_maltego') + except Exception: + if is_local_exec_mode(): + raise MaltegoException("ERROR: Cannot connect to MISP server. Please verify your MISP_Maltego.conf settings.") + else: + raise MaltegoException("ERROR: Cannot connect to MISP server. Please verify your settings (MISP URL and API key), and ensure the MISP server is reachable from the internet.") + + def object_to_entity(self, o, link_label=None, link_direction=LinkDirection.InputToOutput): + # find a nice icon for it + try: + icon_url = mapping_object_icon[o['name']] + except KeyError: + # it's not in our mapping, just ignore and leave the default icon + icon_url = None + # Generate a human readable display-name: + # - find the first RequiredOneOf that exists + # - if none, use the first RequiredField + # LATER further finetune the human readable version of this object + o_template = self.misp.get_object_template(o['template_uuid']) + human_readable = None + try: + found = False + while not found: # the while loop is broken once something is found, or the requiredOneOf has no elements left + required_ote_type = o_template['ObjectTemplate']['requirements']['requiredOneOf'].pop(0) + for ote in o_template['ObjectTemplateElement']: + if ote['object_relation'] == required_ote_type: + required_a_type = ote['type'] + break + for a in o['Attribute']: + if a['type'] == required_a_type: + human_readable = '{}:\n{}'.format(o['name'], a['value']) + found = True + break + except Exception: + pass + if not human_readable: try: - misp_url = parameters['mispurl'].value - misp_key = parameters['mispkey'].value - except AttributeError: - raise MaltegoException("ERROR: mispurl and mispkey need to be set to something valid") - misp_connection = PyMISP(misp_url, misp_key, misp_verify, 'json', misp_debug, tool='misp_maltego') - except Exception: - if is_local_exec_mode(): - raise MaltegoException("ERROR: Cannot connect to MISP server. Please verify your MISP_Maltego.conf settings.") - if is_remote_exec_mode(): - raise MaltegoException("ERROR: Cannot connect to MISP server. Please verify your settings (MISP URL and API key), and ensure the MISP server is reachable from the internet.") - return misp_connection + found = False + parts = [] + for required_ote_type in o_template['ObjectTemplate']['requirements']['required']: + for ote in o_template['ObjectTemplateElement']: + if ote['object_relation'] == required_ote_type: + required_a_type = ote['type'] + break + for a in o['Attribute']: + if a['type'] == required_a_type: + parts.append(a['value']) + break + human_readable = '{}:\n{}'.format(o['name'], '|'.join(parts)) + except Exception: + human_readable = o['name'] + return MISPObject( + human_readable, + uuid=o['uuid'], + event_id=int(o['event_id']), + meta_category=o.get('meta_category'), + description=o.get('description'), + comment=o.get('comment'), + icon_url=icon_url, + link_label=link_label, + link_direction=link_direction, + bookmark=Bookmark.Green + ) + + def object_to_relations(self, o, e): + # process forward and reverse references, so just loop over all the objects of the event + if 'Object' in e['Event']: + for eo in e['Event']['Object']: + if 'ObjectReference' in eo: + for ref in eo['ObjectReference']: + # we have found original object. Expand to the related object and attributes + if eo['uuid'] == o['uuid']: + # the reference is an Object + if ref.get('Object'): + # get the full object in the event, as our objectReference included does not contain everything we need + sub_object = get_object_in_event(ref['Object']['uuid'], e) + yield self.object_to_entity(sub_object, link_label=ref['relationship_type']) + # the reference is an Attribute + if ref.get('Attribute'): + ref['Attribute']['event_id'] = ref['event_id'] # LATER remove this ugly workaround - object can't be requested directly from MISP using the uuid, and to find a full object we need the event_id + for item in attribute_to_entity(ref['Attribute'], link_label=ref['relationship_type']): + yield item + + # reverse-lookup - this is another objects relating the original object + if ref['referenced_uuid'] == o['uuid']: + yield self.object_to_entity(eo, link_label=ref['relationship_type'], link_direction=LinkDirection.OutputToInput) def entity_obj_to_entity(entity_obj, v, t, **kwargs): @@ -176,65 +254,6 @@ def attribute_to_entity(a, link_label=None, event_tags=[], only_self=False): # LATER : relationships from attributes - not yet supported by MISP yet, but there are references in the datamodel -def object_to_entity(o, link_label=None, link_direction=LinkDirection.InputToOutput): - misp = get_misp_connection() - # find a nice icon for it - try: - icon_url = mapping_object_icon[o['name']] - except KeyError: - # it's not in our mapping, just ignore and leave the default icon - icon_url = None - # Generate a human readable display-name: - # - find the first RequiredOneOf that exists - # - if none, use the first RequiredField - # LATER further finetune the human readable version of this object - o_template = misp.get_object_template(o['template_uuid']) - human_readable = None - try: - found = False - while not found: # the while loop is broken once something is found, or the requiredOneOf has no elements left - required_ote_type = o_template['ObjectTemplate']['requirements']['requiredOneOf'].pop(0) - for ote in o_template['ObjectTemplateElement']: - if ote['object_relation'] == required_ote_type: - required_a_type = ote['type'] - break - for a in o['Attribute']: - if a['type'] == required_a_type: - human_readable = '{}:\n{}'.format(o['name'], a['value']) - found = True - break - except Exception: - pass - if not human_readable: - try: - found = False - parts = [] - for required_ote_type in o_template['ObjectTemplate']['requirements']['required']: - for ote in o_template['ObjectTemplateElement']: - if ote['object_relation'] == required_ote_type: - required_a_type = ote['type'] - break - for a in o['Attribute']: - if a['type'] == required_a_type: - parts.append(a['value']) - break - human_readable = '{}:\n{}'.format(o['name'], '|'.join(parts)) - except Exception: - human_readable = o['name'] - return MISPObject( - human_readable, - uuid=o['uuid'], - event_id=int(o['event_id']), - meta_category=o.get('meta_category'), - description=o.get('description'), - comment=o.get('comment'), - icon_url=icon_url, - link_label=link_label, - link_direction=link_direction, - bookmark=Bookmark.Green - ) - - def object_to_attributes(o, e): # first process attributes from an object that belong together (eg: first-name + last-name), and remove them from the list if o['name'] == 'person': @@ -248,30 +267,6 @@ def object_to_attributes(o, e): yield item -def object_to_relations(o, e): - # process forward and reverse references, so just loop over all the objects of the event - if 'Object' in e['Event']: - for eo in e['Event']['Object']: - if 'ObjectReference' in eo: - for ref in eo['ObjectReference']: - # we have found original object. Expand to the related object and attributes - if eo['uuid'] == o['uuid']: - # the reference is an Object - if ref.get('Object'): - # get the full object in the event, as our objectReference included does not contain everything we need - sub_object = get_object_in_event(ref['Object']['uuid'], e) - yield object_to_entity(sub_object, link_label=ref['relationship_type']) - # the reference is an Attribute - if ref.get('Attribute'): - ref['Attribute']['event_id'] = ref['event_id'] # LATER remove this ugly workaround - object can't be requested directly from MISP using the uuid, and to find a full object we need the event_id - for item in attribute_to_entity(ref['Attribute'], link_label=ref['relationship_type']): - yield item - - # reverse-lookup - this is another objects relating the original object - if ref['referenced_uuid'] == o['uuid']: - yield object_to_entity(eo, link_label=ref['relationship_type'], link_direction=LinkDirection.OutputToInput) - - def get_object_in_event(uuid, e): for o in e['Event']['Object']: if o['uuid'] == uuid: diff --git a/src/MISP_maltego/transforms/eventtoattributes.py b/src/MISP_maltego/transforms/eventtoattributes.py index 7aa6a9b..b0b0012 100644 --- a/src/MISP_maltego/transforms/eventtoattributes.py +++ b/src/MISP_maltego/transforms/eventtoattributes.py @@ -1,7 +1,7 @@ from canari.maltego.entities import Hashtag from canari.maltego.transform import Transform from MISP_maltego.transforms.common.entities import MISPEvent, MISPObject -from MISP_maltego.transforms.common.util import check_update, get_misp_connection, attribute_to_entity, event_to_entity, galaxycluster_to_entity, object_to_entity, object_to_attributes, object_to_relations, tag_matches_note_prefix +from MISP_maltego.transforms.common.util import check_update, MISPConnection, attribute_to_entity, event_to_entity, galaxycluster_to_entity, object_to_attributes, tag_matches_note_prefix from canari.maltego.message import LinkStyle @@ -15,7 +15,7 @@ __maintainer__ = 'Christophe Vandeplas' __email__ = 'christophe@vandeplas.com' __status__ = 'Development' -# FIXME have a more human readable version of the MISP event value in the graph. change entity + event_to_entity + do_transform +# TODO have a more human readable version of the MISP event value in the graph. change entity + event_to_entity + do_transform class EventToTransform(Transform): @@ -26,7 +26,7 @@ class EventToTransform(Transform): self.request = None self.response = None self.config = None - self.misp = None + self.conn = None self.event_json = None self.event_tags = None @@ -36,9 +36,9 @@ class EventToTransform(Transform): self.config = config self.response += check_update(config) maltego_misp_event = request.entity - self.misp = get_misp_connection(config, request.parameters) + self.conn = MISPConnection(config, request.parameters) event_id = maltego_misp_event.id - search_result = self.misp.search(controller='events', eventid=event_id, with_attachments=False) + search_result = self.conn.misp.search(controller='events', eventid=event_id, with_attachments=False) if search_result: self.event_json = search_result.pop() else: @@ -76,7 +76,7 @@ class EventToTransform(Transform): def gen_response_objects(self): for o in self.event_json['Event']['Object']: - self.response += object_to_entity(o) + self.response += self.conn.object_to_entity(o) def gen_response_relations(self): for e in self.event_json['Event']['RelatedEvent']: @@ -169,14 +169,14 @@ class ObjectToAttributes(Transform): def do_transform(self, request, response, config): response += check_update(config) maltego_object = request.entity - misp = get_misp_connection(config, request.parameters) - event_json = misp.get_event(maltego_object.event_id) + conn = MISPConnection(config, request.parameters) + event_json = conn.misp.get_event(maltego_object.event_id) for o in event_json['Event']['Object']: if o['uuid'] == maltego_object.uuid: for entity in object_to_attributes(o, event_json): if entity: response += entity - for entity in object_to_relations(o, event_json): + for entity in conn.object_to_relations(o, event_json): if entity: response += entity @@ -192,11 +192,11 @@ class ObjectToRelations(Transform): def do_transform(self, request, response, config): response += check_update(config) maltego_object = request.entity - misp = get_misp_connection(config, request.parameters) - event_json = misp.get_event(maltego_object.event_id) + conn = MISPConnection(config, request.parameters) + event_json = conn.misp.get_event(maltego_object.event_id) for o in event_json['Event']['Object']: if o['uuid'] == maltego_object.uuid: - for entity in object_to_relations(o, event_json): + for entity in conn.object_to_relations(o, event_json): if entity: response += entity diff --git a/src/MISP_maltego/transforms/galaxytoevent.py b/src/MISP_maltego/transforms/galaxytoevent.py index 0468a81..785bad4 100644 --- a/src/MISP_maltego/transforms/galaxytoevent.py +++ b/src/MISP_maltego/transforms/galaxytoevent.py @@ -1,6 +1,6 @@ from canari.maltego.transform import Transform from MISP_maltego.transforms.common.entities import MISPEvent, MISPGalaxy, ThreatActor, Software, AttackTechnique -from MISP_maltego.transforms.common.util import check_update, get_misp_connection, galaxycluster_to_entity, get_galaxy_cluster, get_galaxies_relating, search_galaxy_cluster, mapping_galaxy_icon +from MISP_maltego.transforms.common.util import check_update, MISPConnection, galaxycluster_to_entity, get_galaxy_cluster, get_galaxies_relating, search_galaxy_cluster, mapping_galaxy_icon from canari.maltego.message import UIMessageType, UIMessage, LinkDirection @@ -24,12 +24,12 @@ class GalaxyToEvents(Transform): def do_transform(self, request, response, config): response += check_update(config) - misp = get_misp_connection(config, request.parameters) + conn = MISPConnection(config, request.parameters) if request.entity.tag_name: tag_name = request.entity.tag_name else: tag_name = request.entity.value - events_json = misp.search(controller='events', tags=tag_name, with_attachments=False) + events_json = conn.misp.search(controller='events', tags=tag_name, with_attachments=False) for e in events_json: response += MISPEvent(e['Event']['id'], uuid=e['Event']['uuid'], info=e['Event']['info'], link_direction=LinkDirection.OutputToInput) return response