From e027989d87e8cb1c6c048d8d34beda337b6d3240 Mon Sep 17 00:00:00 2001 From: Arpit Jain Date: Wed, 1 Jul 2026 19:53:25 +0900 Subject: [PATCH] fix: [openioc] require both search-contexts before merging a composite The composite check in set_all_attributes used `if (value1 and value2) in childList`, which Python evaluates as `(value1 and value2)` first. Both operands are non-empty mapping-key strings, so the expression reduces to `value2 in childList` and value1 is never checked. An AND-indicator with two children whose combined pair is not a real composite (but whose second search-context matches a composite mapping's value2) was wrongly merged into a bogus type='other' attribute, emitted once per matching mapping key. Check that both search-contexts are present before merging, so genuine composites still merge and unrelated pairs fall through to their two individual attributes. Adds a regression test covering both cases. Signed-off-by: Arpit Jain --- pymisp/tools/openioc.py | 2 +- tests/test_openioc.py | 77 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 tests/test_openioc.py diff --git a/pymisp/tools/openioc.py b/pymisp/tools/openioc.py index 488c5b09a..b3ddc2824 100644 --- a/pymisp/tools/openioc.py +++ b/pymisp/tools/openioc.py @@ -257,7 +257,7 @@ def set_all_attributes(openioc, misp_event): childList = [child.find('context')['search'] for child in childs] def check_and_add(value1, value2): - if (value1 and value2) in childList: + if value1 in childList and value2 in childList: if childs[0].find('context')['search'] == value1: attribute_values = set_values(childs[0], childs[1]) else: diff --git a/tests/test_openioc.py b/tests/test_openioc.py new file mode 100644 index 000000000..b09ba9450 --- /dev/null +++ b/tests/test_openioc.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python + +from __future__ import annotations + +import unittest +import warnings + +from pymisp.tools import openioc + +try: + from bs4 import XMLParsedAsHTMLWarning # type: ignore + warnings.filterwarnings("ignore", category=XMLParsedAsHTMLWarning) +except ImportError: + pass + + +# Two children of an AND-indicator whose combined search-context pair is NOT a +# defined composite (RegistryItem/Value + FileItem/Md5sum). These must come back +# as their two individual attributes, not a merged composite. +NON_COMPOSITE = """ + + non-composite test + + + + + malicious_value + + + + d41d8cd98f00b204e9800998ecf8427e + + + +""" + +# A genuine, defined composite pair (FileItem/FileName + FileItem/Md5sum). This +# must still merge into a single filename|md5 attribute. +COMPOSITE = """ + + composite test + + + + + evil.exe + + + + d41d8cd98f00b204e9800998ecf8427e + + + +""" + + +class TestOpenIOC(unittest.TestCase): + + def test_non_composite_and_indicator_not_merged(self) -> None: + event = openioc.load_openioc(NON_COMPOSITE) + by_type = {attr.type: attr.value for attr in event.attributes} + # Two distinct, correctly typed attributes, no bogus merged composite. + self.assertEqual(len(event.attributes), 2) + self.assertEqual(by_type.get('regkey'), 'malicious_value') + self.assertEqual(by_type.get('md5'), 'd41d8cd98f00b204e9800998ecf8427e') + self.assertNotIn('other', by_type) + + def test_genuine_composite_still_merged(self) -> None: + event = openioc.load_openioc(COMPOSITE) + self.assertEqual(len(event.attributes), 1) + attr = event.attributes[0] + self.assertEqual(attr.type, 'filename|md5') + self.assertEqual(attr.value, 'evil.exe|d41d8cd98f00b204e9800998ecf8427e') + + +if __name__ == '__main__': + unittest.main()