CVE-2020-12889
Description
MISP-maltego 1.4.4 shares a MISP connection across users during remote transforms, allowing potential unauthorized data access.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
MISP-maltego 1.4.4 shares a MISP connection across users during remote transforms, allowing potential unauthorized data access.
Vulnerability
Analysis
CVE-2020-12889 affects MISP-maltego version 1.4.4, a set of Maltego transforms that interface with a MISP Threat Sharing instance. The vulnerability stems from a shared, global misp_connection variable that is reused across different users when the transforms are executed in remote-transform mode. Because the connection is established once and then returned for all subsequent requests, an attacker or unauthorized user could potentially access a MISP connection that was initialized with another user's credentials, leading to unintended information disclosure [1].
Exploitation
Exploitation occurs when MISP-maltego is used as a remote transform, a scenario where the transforms run on a server and serve multiple users. In this mode, the original code stored the connection in a module-level global variable (misp_connection = None). When get_misp_connection() was called, it first checked if the connection already existed; if so, it returned that single instance without validating whether the request originated from the same user. This design flaw means that after the first user authenticates and establishes a connection, subsequent remote-transform users inherit that same authenticated session, potentially seeing data from a different MISP configuration [1][2].
Impact
An attacker who can invoke remote transforms on the same server might gain access to MISP data intended for another user. This could include sensitive threat intelligence, event details, and attributes such as IP addresses, domains, file hashes, and other indicators of compromise. The vulnerability does not require authentication to explicitly exploit, but the attacker must be able to trigger transforms on a shared MISP-maltego remote instance where at least one other user has previously established a connection [1][3].
Mitigation
The issue was addressed in commit 3ccde66 by refactoring the connection management into a MISPConnection class, ensuring each connection is scoped per request rather than shared globally. Users should update to MISP-maltego 1.4.5 or later. The PyPI advisory database also reflects this fix [4]. Administrators of remote Maltego transform servers should apply the update immediately to prevent cross-user data leakage.
- fix: [security] fixes a security issue when used as remote transform · MISP/MISP-maltego@3ccde66
- GitHub - MISP/MISP-maltego: Set of Maltego transforms to inferface with a MISP Threat Sharing instance, and also to explore the whole MITRE ATT&CK dataset.
- NVD - CVE-2020-12889
- advisory-database/vulns/misp-maltego/PYSEC-2020-66.yaml at main · pypa/advisory-database
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
MISP-maltegoPyPI | < 1.4.5 | 1.4.5 |
Affected products
2- MISP/MISP-maltegodescription
Patches
13ccde66dab40fix: [security] fixes a security issue when used as remote transform
4 files changed · +139 −144
src/MISP_maltego/transforms/attributetoevent.py+15 −15 modified@@ -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 @@ def do_transform(self, request, response, config): 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 @@ def do_transform(self, request, response, config): 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 @@ def do_transform(self, request, response, config): 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 @@ def do_transform(self, request, response, config): 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 @@ def do_transform(self, request, response, config): # 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 @@ def do_transform(self, request, response, config): 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 @@ def do_transform(self, request, response, config): 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
src/MISP_maltego/transforms/common/util.py+109 −114 modified@@ -18,7 +18,6 @@ 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:
src/MISP_maltego/transforms/eventtoattributes.py+12 −12 modified@@ -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 @@ __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 @@ def __init__(self): 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 @@ def do_transform(self, request, response, config): 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 @@ def gen_response_attributes(self): 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
src/MISP_maltego/transforms/galaxytoevent.py+3 −3 modified@@ -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
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.