CVE-2020-12889 (published 2020-05-15T18:15Z)

MISP MISP-maltego 1.4.4 incorrectly shares a MISP connection across users in a remote-transform use case.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/MISP/MISP-maltego
commit_details: repo_url: https://github.com/MISP/MISP-maltego
author: Christophe Vandeplas
author_date: 2020-05-15 08:59:20+02:00
msg: fix: [security] fixes a security issue when used as remote transform

the MISP connection being shared by all the users
repository: repo_url: https://github.com/MISP/MISP-maltego
repo_name: MISP/MISP-maltego
description: Set of Maltego transforms to inferface with a MISP Threat Sharing instance, and also to explore the whole MITRE ATT&CK dataset.
repo_language: Python
owner: MISP
file_changes: filename: attributetoevent.py
change_type: ModificationType.MODIFY
diff: @@ -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

programming_language: Python
filename: util.py
change_type: ModificationType.MODIFY
diff: @@ -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:

programming_language: Python
filename: eventtoattributes.py
change_type: ModificationType.MODIFY
diff: @@ -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
 

programming_language: Python
filename: galaxytoevent.py
change_type: ModificationType.MODIFY
diff: @@ -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

programming_language: Python