From 31a6794ab85dab752262e794d3109d4314306ab9 Mon Sep 17 00:00:00 2001 From: ziad hany Date: Mon, 29 Dec 2025 19:12:42 +0200 Subject: [PATCH 1/7] Initial Fireeye importer migration to Advisory V2 Signed-off-by: ziad hany --- vulnerabilities/importers/__init__.py | 2 + .../v2_importers/fireeye_importer_v2.py | 190 ++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py diff --git a/vulnerabilities/importers/__init__.py b/vulnerabilities/importers/__init__.py index b0b1ccb52..7aaa2072b 100644 --- a/vulnerabilities/importers/__init__.py +++ b/vulnerabilities/importers/__init__.py @@ -48,6 +48,7 @@ from vulnerabilities.pipelines.v2_importers import ( elixir_security_importer as elixir_security_importer_v2, ) +from vulnerabilities.pipelines.v2_importers import fireeye_importer_v2 from vulnerabilities.pipelines.v2_importers import epss_importer_v2 from vulnerabilities.pipelines.v2_importers import github_osv_importer as github_osv_importer_v2 from vulnerabilities.pipelines.v2_importers import gitlab_importer as gitlab_importer_v2 @@ -97,6 +98,7 @@ npm_importer.NpmImporterPipeline, nginx_importer.NginxImporterPipeline, pysec_importer.PyPIImporterPipeline, + fireeye_importer_v2.FireeyeImporterPipeline, apache_tomcat.ApacheTomcatImporter, postgresql.PostgreSQLImporter, debian.DebianImporter, diff --git a/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py b/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py new file mode 100644 index 000000000..d52ee2da7 --- /dev/null +++ b/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py @@ -0,0 +1,190 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# VulnerableCode is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/aboutcode-org/vulnerablecode for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# +import logging +import re +from pathlib import Path +from typing import Iterable +from typing import List + +from fetchcode.vcs import fetch_via_vcs + +from vulnerabilities.importer import AdvisoryData +from vulnerabilities.importer import ReferenceV2 +from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipelineV2 +from vulnerabilities.utils import build_description +from vulnerabilities.utils import create_weaknesses_list +from vulnerabilities.utils import cwe_regex +from vulnerabilities.utils import dedupe + +logger = logging.getLogger(__name__) + + +class FireeyeImporterPipeline(VulnerableCodeBaseImporterPipelineV2): + spdx_license_expression = "CC-BY-SA-4.0 AND MIT" + license_url = "https://github.com/mandiant/Vulnerability-Disclosures/blob/master/README.md" + notice = """ + Copyright (c) Mandiant + The following licenses/licensing apply to this Mandiant repository: + 1. CC BY-SA 4.0 - For CVE related information not including source code (such as PoCs) + 2. MIT - For source code contained within provided CVE information + """ + repo_url = "git+https://github.com/mandiant/Vulnerability-Disclosures" + pipeline_id = "fireeye_importer_v2" + + @classmethod + def steps(cls): + return ( + cls.clone, + cls.collect_and_store_advisories, + cls.clean_downloads, + ) + + def advisories_count(self): + files = filter( + lambda p: p.suffix in [".md", ".MD"], Path(self.vcs_response.dest_dir).glob("**/*") + ) + return len(list(files)) + + def clone(self): + self.log(f"Cloning `{self.repo_url}`") + self.vcs_response = fetch_via_vcs(self.repo_url) + + def collect_advisories(self) -> Iterable[AdvisoryData]: + base_path = Path(self.vcs_response.dest_dir) + files = filter( + lambda p: p.suffix in [".md", ".MD"], Path(self.vcs_response.dest_dir).glob("**/*") + ) + for file in files: + if Path(file).stem == "README": + continue + try: + with open(file, encoding="utf-8-sig") as f: + yield parse_advisory_data(raw_data=f.read(), file=file, base_path=base_path) + except UnicodeError: + logger.error(f"Invalid file {file}") + + def clean_downloads(self): + if self.vcs_response: + self.log(f"Removing cloned repository") + self.vcs_response.delete() + + def on_failure(self): + self.clean_downloads() + + +def parse_advisory_data(raw_data, file, base_path) -> AdvisoryData: + """ + Parse a fireeye advisory repo and return an AdvisoryData or None. + These files are in Markdown format. + """ + relative_path = str(file.relative_to(base_path)).strip("/") + advisory_url = ( + f"https://github.com/mandiant/Vulnerability-Disclosures/blob/master/{relative_path}" + ) + raw_data = raw_data.replace("\n\n", "\n") + md_list = raw_data.split("\n") + md_dict = md_list_to_dict(md_list) + + database_id = md_list[0][1::] + summary = md_dict.get(database_id[1::]) or [] + description = md_dict.get("## Description") or [] + impact = md_dict.get("## Impact") # not used but can be used to get severity + exploit_ability = md_dict.get("## Exploitability") # not used + cve_ref = md_dict.get("## CVE Reference") or [] + tech_details = md_dict.get("## Technical Details") # not used + resolution = md_dict.get("## Resolution") # not used + disc_credits = md_dict.get("## Discovery Credits") # not used + disc_timeline = md_dict.get("## Disclosure Timeline") # not used + references = md_dict.get("## References") or [] + cwe_data = md_dict.get("## Common Weakness Enumeration") or [] + + return AdvisoryData( + advisory_id=base_path.stem, + aliases=get_aliases(database_id, cve_ref), + summary=build_description(" ".join(summary), " ".join(description)), + references_v2=get_references(references), + weaknesses=get_weaknesses(cwe_data), + url=advisory_url, + ) + + +def get_references(references): + """ + Return a list of Reference from a list of URL reference in md format + >>> get_references(["- http://1-4a.com/cgi-bin/alienform/af.cgi"]) + [ReferenceV2(reference_id='', reference_type='', url='http://1-4a.com/cgi-bin/alienform/af.cgi')] + >>> get_references(["- [Mitre CVE-2021-42712](https://www.cve.org/CVERecord?id=CVE-2021-42712)"]) + [ReferenceV2(reference_id='', reference_type='', url='https://www.cve.org/CVERecord?id=CVE-2021-42712')] + """ + urls = [] + for ref in references: + if ref.startswith("- "): + urls.append(matcher_url(ref[2::])) + else: + urls.append(matcher_url(ref)) + + return [ReferenceV2(url=url) for url in urls if url] + + +def matcher_url(ref) -> str: + """ + Returns URL of the reference markup from reference url in Markdown format + """ + markup_regex = "\[([^\[]+)]\(\s*(http[s]?://.+)\s*\)" + matched_markup = re.findall(markup_regex, ref) + if matched_markup: + return matched_markup[0][1] + else: + return ref + + +def get_aliases(database_id, cve_ref) -> List: + """ + Returns a List of Aliases from a database_id and a list of CVEs + >>> get_aliases("MNDT-2021-0012", ["CVE-2021-44207"]) + ['CVE-2021-44207', 'MNDT-2021-0012'] + """ + cve_ref.append(database_id) + return dedupe(cve_ref) + + +def md_list_to_dict(md_list): + """ + Returns a dictionary of md_list from a list of a md file splited by \n + >>> md_list_to_dict(["# Header","hello" , "hello again" ,"# Header2"]) + {'# Header': ['hello', 'hello again'], '# Header2': []} + """ + md_dict = {} + md_key = "" + for md_line in md_list: + if md_line.startswith("#"): + md_dict[md_line] = [] + md_key = md_line + else: + md_dict[md_key].append(md_line) + return md_dict + + +def get_weaknesses(cwe_data): + """ + Return the list of CWE IDs as integers from a list of weakness summaries, e.g., [379]. + + >>> get_weaknesses([ + ... "CWE-379: Creation of Temporary File in Directory with Insecure Permissions", + ... "CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')" + ... ]) + [379, 362] + """ + cwe_list = [] + for line in cwe_data: + cwe_ids = re.findall(cwe_regex, line) + cwe_list.extend(cwe_ids) + + weaknesses = create_weaknesses_list(cwe_list) + return weaknesses From 17f2e177f2bbbff89581b3126c89f512e1ae3a88 Mon Sep 17 00:00:00 2001 From: ziad hany Date: Tue, 30 Dec 2025 16:02:40 +0200 Subject: [PATCH 2/7] Migrate Fireeye importer to Advisory V2 , Add a test Signed-off-by: ziad hany --- .../v2_importers/fireeye_importer_v2.py | 112 ++++++++++++------ .../pipelines/v2_importers/test_fireeye_v2.py | 38 ++++++ .../fireeye_v2/fireeye_test1-expected.json | 23 ++++ .../test_data/fireeye_v2/fireeye_test1.md | 41 +++++++ .../fireeye_v2/fireeye_test2-expected.json | 39 ++++++ .../test_data/fireeye_v2/fireeye_test2.md | 41 +++++++ .../fireeye_v2/fireeye_test3-expected.json | 35 ++++++ .../test_data/fireeye_v2/fireeye_test3.md | 38 ++++++ 8 files changed, 332 insertions(+), 35 deletions(-) create mode 100644 vulnerabilities/tests/pipelines/v2_importers/test_fireeye_v2.py create mode 100644 vulnerabilities/tests/test_data/fireeye_v2/fireeye_test1-expected.json create mode 100644 vulnerabilities/tests/test_data/fireeye_v2/fireeye_test1.md create mode 100644 vulnerabilities/tests/test_data/fireeye_v2/fireeye_test2-expected.json create mode 100644 vulnerabilities/tests/test_data/fireeye_v2/fireeye_test2.md create mode 100644 vulnerabilities/tests/test_data/fireeye_v2/fireeye_test3-expected.json create mode 100644 vulnerabilities/tests/test_data/fireeye_v2/fireeye_test3.md diff --git a/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py b/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py index d52ee2da7..78d7b18a3 100644 --- a/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py +++ b/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py @@ -16,11 +16,14 @@ from vulnerabilities.importer import AdvisoryData from vulnerabilities.importer import ReferenceV2 +from vulnerabilities.importer import VulnerabilitySeverity from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipelineV2 +from vulnerabilities.severity_systems import GENERIC from vulnerabilities.utils import build_description from vulnerabilities.utils import create_weaknesses_list from vulnerabilities.utils import cwe_regex from vulnerabilities.utils import dedupe +from vulnerabilities.utils import get_advisory_url logger = logging.getLogger(__name__) @@ -46,10 +49,12 @@ def steps(cls): ) def advisories_count(self): - files = filter( - lambda p: p.suffix in [".md", ".MD"], Path(self.vcs_response.dest_dir).glob("**/*") + base_path = Path(self.vcs_response.dest_dir) + return sum( + 1 + for p in base_path.glob("**/*") + if p.suffix.lower() == ".md" or p.stem.upper() == "README" ) - return len(list(files)) def clone(self): self.log(f"Cloning `{self.repo_url}`") @@ -57,17 +62,20 @@ def clone(self): def collect_advisories(self) -> Iterable[AdvisoryData]: base_path = Path(self.vcs_response.dest_dir) - files = filter( - lambda p: p.suffix in [".md", ".MD"], Path(self.vcs_response.dest_dir).glob("**/*") - ) - for file in files: - if Path(file).stem == "README": + for file_path in base_path.glob("**/*"): + if file_path.suffix.lower() != ".md": continue + + if file_path.stem.upper() == "README": + continue + try: - with open(file, encoding="utf-8-sig") as f: - yield parse_advisory_data(raw_data=f.read(), file=file, base_path=base_path) + with open(file_path, encoding="utf-8-sig") as f: + yield parse_advisory_data( + raw_data=f.read(), file_path=file_path, base_path=base_path + ) except UnicodeError: - logger.error(f"Invalid file {file}") + logger.error(f"Invalid File UnicodeError: {file_path}") def clean_downloads(self): if self.vcs_response: @@ -78,15 +86,11 @@ def on_failure(self): self.clean_downloads() -def parse_advisory_data(raw_data, file, base_path) -> AdvisoryData: +def parse_advisory_data(raw_data, file_path, base_path) -> AdvisoryData: """ Parse a fireeye advisory repo and return an AdvisoryData or None. These files are in Markdown format. """ - relative_path = str(file.relative_to(base_path)).strip("/") - advisory_url = ( - f"https://github.com/mandiant/Vulnerability-Disclosures/blob/master/{relative_path}" - ) raw_data = raw_data.replace("\n\n", "\n") md_list = raw_data.split("\n") md_dict = md_list_to_dict(md_list) @@ -94,23 +98,27 @@ def parse_advisory_data(raw_data, file, base_path) -> AdvisoryData: database_id = md_list[0][1::] summary = md_dict.get(database_id[1::]) or [] description = md_dict.get("## Description") or [] - impact = md_dict.get("## Impact") # not used but can be used to get severity - exploit_ability = md_dict.get("## Exploitability") # not used + impact = md_dict.get("## Impact") cve_ref = md_dict.get("## CVE Reference") or [] - tech_details = md_dict.get("## Technical Details") # not used - resolution = md_dict.get("## Resolution") # not used - disc_credits = md_dict.get("## Discovery Credits") # not used - disc_timeline = md_dict.get("## Disclosure Timeline") # not used references = md_dict.get("## References") or [] cwe_data = md_dict.get("## Common Weakness Enumeration") or [] + advisory_id = file_path.stem + advisory_url = get_advisory_url( + file=file_path, + base_path=base_path, + url="https://github.com/mandiant/Vulnerability-Disclosures/blob/master/", + ) + return AdvisoryData( - advisory_id=base_path.stem, + advisory_id=advisory_id, aliases=get_aliases(database_id, cve_ref), summary=build_description(" ".join(summary), " ".join(description)), references_v2=get_references(references), + severities=get_severities(impact), weaknesses=get_weaknesses(cwe_data), url=advisory_url, + original_advisory_text=raw_data, ) @@ -124,11 +132,11 @@ def get_references(references): """ urls = [] for ref in references: - if ref.startswith("- "): - urls.append(matcher_url(ref[2::])) - else: - urls.append(matcher_url(ref)) - + clean_ref = ref.strip() + clean_ref = clean_ref.lstrip("-* ") + url = matcher_url(clean_ref) + if url: + urls.append(url) return [ReferenceV2(url=url) for url in urls if url] @@ -150,7 +158,8 @@ def get_aliases(database_id, cve_ref) -> List: >>> get_aliases("MNDT-2021-0012", ["CVE-2021-44207"]) ['CVE-2021-44207', 'MNDT-2021-0012'] """ - cve_ref.append(database_id) + cleaned_db_id = database_id.strip() + cve_ref.append(cleaned_db_id) return dedupe(cve_ref) @@ -174,12 +183,11 @@ def md_list_to_dict(md_list): def get_weaknesses(cwe_data): """ Return the list of CWE IDs as integers from a list of weakness summaries, e.g., [379]. - - >>> get_weaknesses([ - ... "CWE-379: Creation of Temporary File in Directory with Insecure Permissions", - ... "CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')" - ... ]) - [379, 362] + >>> get_weaknesses([ + ... "CWE-379: Creation of Temporary File in Directory with Insecure Permissions", + ... "CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')" + ... ]) + [379, 362] """ cwe_list = [] for line in cwe_data: @@ -188,3 +196,37 @@ def get_weaknesses(cwe_data): weaknesses = create_weaknesses_list(cwe_list) return weaknesses + + +def get_severities(impact): + """ + Return a list of VulnerabilitySeverity extracted from the impact string. + >>> get_severities([ + ... "High - Arbitrary Ring 0 code execution", + ... ]) + [VulnerabilitySeverity(system="generic", value="high")] + >>> get_severities([ + ... "Low - The `ValidationKey` and `DecryptionKey` values would need to be obtained via a separate vulnerability or other channel." + ... ]) + [VulnerabilitySeverity(system="generic", value="low")] + >>> get_severities([]) + [] + """ + if not impact: + return [] + + impact_text = impact[0] + value = "" + if " - " in impact_text: + value = impact_text.split(" - ")[0] + elif ": " in impact_text: + value = impact_text.split(": ")[0] + else: + parts = impact_text.split(" ") + if parts: + value = parts[0] + + if not value.lower() in ["high", "medium", "low"]: + return [] + + return [VulnerabilitySeverity(system=GENERIC, value=value)] diff --git a/vulnerabilities/tests/pipelines/v2_importers/test_fireeye_v2.py b/vulnerabilities/tests/pipelines/v2_importers/test_fireeye_v2.py new file mode 100644 index 000000000..d22c8c9ec --- /dev/null +++ b/vulnerabilities/tests/pipelines/v2_importers/test_fireeye_v2.py @@ -0,0 +1,38 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# VulnerableCode is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/aboutcode-org/vulnerablecode for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +from pathlib import Path +from unittest.mock import Mock +from unittest.mock import patch + +import pytest + +from vulnerabilities.pipelines.v2_importers.fireeye_importer_v2 import FireeyeImporterPipeline +from vulnerabilities.tests import util_tests + +TEST_DATA = Path(__file__).parent.parent.parent / "test_data" / "fireeye_v2" + +TEST_CVE_FILES = [ + TEST_DATA / "fireeye_test1.md", + TEST_DATA / "fireeye_test2.md", + TEST_DATA / "fireeye_test3.md", +] + + +@pytest.mark.django_db +@pytest.mark.parametrize("md_file", TEST_CVE_FILES) +def test_fireeye_advisories_per_file(md_file): + pipeline = FireeyeImporterPipeline() + pipeline.vcs_response = Mock(dest_dir=TEST_DATA) + + with patch.object(Path, "glob", return_value=[md_file]): + result = [adv.to_dict() for adv in pipeline.collect_advisories()] + + expected_file = md_file.with_name(md_file.stem + "-expected.json") + util_tests.check_results_against_json(result, expected_file) diff --git a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test1-expected.json b/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test1-expected.json new file mode 100644 index 000000000..ec3422ae9 --- /dev/null +++ b/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test1-expected.json @@ -0,0 +1,23 @@ +[ + { + "advisory_id": "fireeye_test1", + "aliases": [ + "CVE-2019-7245 ", + "FEYE-2019-0002" + ], + "summary": "GPU-Z.sys, part of the GPU-Z package from TechPowerUp, exposes the wrmsr instruction to user-mode callers without properly validating the target Model Specific Register (MSR). This can result in arbitrary unsigned code being executed in Ring 0.", + "affected_packages": [], + "references_v2": [], + "patches": [], + "severities": [ + { + "system": "generic_textual", + "value": "High", + "scoring_elements": "" + } + ], + "date_published": null, + "weaknesses": [], + "url": "https://github.com/mandiant/Vulnerability-Disclosures/blob/master/fireeye_test1.md" + } +] \ No newline at end of file diff --git a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test1.md b/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test1.md new file mode 100644 index 000000000..3f990e06d --- /dev/null +++ b/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test1.md @@ -0,0 +1,41 @@ +# FEYE-2019-0002 +## Description +GPU-Z.sys, part of the GPU-Z package from TechPowerUp, exposes the wrmsr instruction to user-mode callers without properly validating the target Model Specific Register (MSR). This can result in arbitrary unsigned code being executed in Ring 0. + +## Impact +High - Arbitrary Ring 0 code execution + +## Exploitability +Medium/Low - Driver must be loaded or attacker will require admin rights. Newer versions require admin callers. + +## CVE Reference +CVE-2019-7245 + +## Technical Details +IOCTL 0x8000644C in the GPU-Z driver instructs the binary to modify a Model Specific Register (MSR) on the target system. These registers control a wide variety of system functionality and can be used to monitor CPU temperature, track branches in code, tweak voltages, etc. MSRs are also responsible for setting the kernel mode function responsible for handling system calls. + +The driver does not appropriately filter access to MSRs, allowing an attacker to overwrite the system call handler and run unsigned code in Ring 0. Allowing access to any of the following MSRs can result in arbitrary Ring 0 code being executed: + +* 0xC0000081 +* 0xC0000082 +* 0xC0000083 +* 0x174 +* 0x175 +* 0x176 + +For exploitation details see the INFILTRATE presentation in the references. + +## Resolution +This issue is fixed in v2.23.0: [https://www.techpowerup.com/257995/techpowerup-releases-gpu-z-v2-23-0](https://www.techpowerup.com/257995/techpowerup-releases-gpu-z-v2-23-0) + +## Discovery Credits +Ryan Warns + +## Disclosure Timeline +- 2 February 2019 - Contacted vendor +- 2 February 2019 - Vendor response, confirmation of issue +- 25 July 2019 - Vendor confirmed fix +- 6 August 2019 - Fixed version released + +## References +[Exploitation Details](https://downloads.immunityinc.com/infiltrate2019-slidepacks/ryan-warns-timothy-harrison-device-driver-debauchery-msr-madness/MSR_Madness_v2.9_INFILTRATE.pptx) \ No newline at end of file diff --git a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test2-expected.json b/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test2-expected.json new file mode 100644 index 000000000..8ad9fa142 --- /dev/null +++ b/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test2-expected.json @@ -0,0 +1,39 @@ +[ + { + "advisory_id": "fireeye_test2", + "aliases": [ + "CVE-2020-12878", + "FEYE-2020-0020" + ], + "summary": "Digi International's ConnectPort X2e is susceptible to a local privilege escalation vulnerable to the privileged user `root`.", + "affected_packages": [], + "references_v2": [ + { + "reference_id": "", + "reference_type": "", + "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-12878" + }, + { + "reference_id": "", + "reference_type": "", + "url": "https://www.fireeye.com/blog/threat-research/2021/02/solarcity-exploitation-of-x2e-iot-device-part-one.html" + }, + { + "reference_id": "", + "reference_type": "", + "url": "https://www.fireeye.com/blog/threat-research/2021/02/solarcity-exploitation-of-x2e-iot-device-part-two.html" + } + ], + "patches": [], + "severities": [ + { + "system": "generic_textual", + "value": "High", + "scoring_elements": "" + } + ], + "date_published": null, + "weaknesses": [], + "url": "https://github.com/mandiant/Vulnerability-Disclosures/blob/master/fireeye_test2.md" + } +] \ No newline at end of file diff --git a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test2.md b/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test2.md new file mode 100644 index 000000000..3f9d18e2e --- /dev/null +++ b/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test2.md @@ -0,0 +1,41 @@ +# FEYE-2020-0020 +## Description + +Digi International's ConnectPort X2e is susceptible to a local privilege escalation vulnerable to the privileged user `root`. + +## Impact +High - An attacker with remote network access to a X2e could remotely compromise the device. This could be used to install malware, modify system behavior, or stage a more serious attack. + +## Exploitability +Medium - An attacker would need to read and write files as the system user python. On production devices, this can be accomplished remotely by establishing an SSH connection or access via a TTY. + +## CVE Reference +CVE-2020-12878 + +## Technical Details +The ConnectPort X2e performed filesystem actions as the privileged system user root on files controllable by the less-privileged user python. A malicious attacker could use this to escalate privileges from the local user `python` user to `root`. + +Mandiant determined that the user `root` executed the file `/etc/init.d/S50dropbear.sh` during normal system boot. The shell script performed a `chown` on the directory `/WEB/python/.ssh/`, which was writable as the user `python`. + +To exploit this, Mandiant used Linux symbolic links to force the system to set the ownership of the directory `/etc/init.d/` to `python:python`. Mandiant could then create a malicious `init` script in the `/etc/init.d/` directory that would be executed by `root` on future system boots. + +## Resolution +Digi International has fixed the reported vulnerability in [version 3.2.30.6](https://ftp1.digi.com/support/firmware/93001304_D.pdf) (May 2020) of the ConnectPort X2e software. + +## Discovery Credits +- Jake Valletta, FireEye Mandiant +- Sam Sabetan, FireEye Mandiant + +## Disclosure Timeline + +- 13 February 2020 - Issue reported to vendor +- 11 March 2020 - Issue confirmed by Digi International +- 14 May 2020 - CVE reserved with MITRE +- May 2020 - Digi Releases Patch +- 17 February 2021 - FireEye Mandiant advisory published + +## References + +- https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-12878 +- https://www.fireeye.com/blog/threat-research/2021/02/solarcity-exploitation-of-x2e-iot-device-part-one.html +- https://www.fireeye.com/blog/threat-research/2021/02/solarcity-exploitation-of-x2e-iot-device-part-two.html \ No newline at end of file diff --git a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test3-expected.json b/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test3-expected.json new file mode 100644 index 000000000..6f8f07483 --- /dev/null +++ b/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test3-expected.json @@ -0,0 +1,35 @@ +[ + { + "advisory_id": "fireeye_test3", + "aliases": [ + "MNDT-2025-0009" + ], + "summary": "Improper verification of cryptographic signature in the installer for Zoom Workplace VDI Client for Windows may allow an authenticated user to conduct an escalation of privilege via local access.", + "affected_packages": [], + "references_v2": [ + { + "reference_id": "", + "reference_type": "", + "url": "https://www.zoom.com/en/trust/security-bulletin/zsb-25042/" + }, + { + "reference_id": "", + "reference_type": "", + "url": "https://www.cve.org/CVERecord?id=CVE-2025-64740" + } + ], + "patches": [], + "severities": [ + { + "system": "generic_textual", + "value": "Medium", + "scoring_elements": "" + } + ], + "date_published": null, + "weaknesses": [ + 347 + ], + "url": "https://github.com/mandiant/Vulnerability-Disclosures/blob/master/fireeye_test3.md" + } +] \ No newline at end of file diff --git a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test3.md b/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test3.md new file mode 100644 index 000000000..aee1a5386 --- /dev/null +++ b/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test3.md @@ -0,0 +1,38 @@ +# MNDT-2025-0009 + +## Description +Improper verification of cryptographic signature in the installer for Zoom Workplace VDI Client for Windows may allow an authenticated user to conduct an escalation of privilege via local access. + +## Impact +Medium: Executing untrusted, unsigned code can aid an adversary in bypassing endpoint security controls such as application whitelisting or endpoint detection and response products. It can also lead to the compromise of the VDI Client entirely. + +## Exploitability +High: Adversaries need standard user access to a system with the affected version of Zoom's Workplace VDI client for Windows installed. + +## CVE ID +[CVE-2025-64740](https://www.cve.org/CVERecord?id=CVE-2025-64740) + +## Common Weakness Enumeration +CWE-347: Improper Verification of Cryptographic Signature + +## Details +The Zoom VDI Client attempted to load a DLL named WFAPI64.dll which does not exist on a system as part of the standard installation process. Additionally, a path formatting issue also existed where the directory name of the user's PATH variable is appended to the name of the DLL to search for which led to a unique DLL load attempt upon every execution. The Zoom Client also did not verify the signature of the DLL when loaded. + +By crafting a specific DLL and making a modification to the PATH variable, it's possible to execute unauthorized, unsigned code in the context of Zoom's VDI Client. + +## Resolution +Upgrade to Zoom Workplace VDI Client for Windows, versions 6.3.14, 6.4.12 and 6.5.10 or greater in their respective tracks. + +## Discovery Credits +* Cory Baker, Mandiant + +## Disclosure Timeline +* July 24, 2025: Initial report to the Zoom Vulnerability Disclosure team +* August 8, 2025: Issue confirmed by Zoom's engineering team +* September 5, 2025: Zoom released Workspace VDI Client v6.5.10 +* November 11, 2025: Zoom Security Bulletin issued + +## References +* https://www.zoom.com/en/trust/security-bulletin/zsb-25042/ +* https://www.cve.org/CVERecord?id=CVE-2025-64740 + From 4cc588389f5668d87ba9b0d70927c9ed772e3144 Mon Sep 17 00:00:00 2001 From: ziad hany Date: Tue, 30 Dec 2025 16:14:34 +0200 Subject: [PATCH 3/7] Fix a doc test and resolve merge conflict Signed-off-by: ziad hany --- .../pipelines/v2_importers/fireeye_importer_v2.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py b/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py index 78d7b18a3..0b9dd6ff5 100644 --- a/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py +++ b/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py @@ -204,11 +204,7 @@ def get_severities(impact): >>> get_severities([ ... "High - Arbitrary Ring 0 code execution", ... ]) - [VulnerabilitySeverity(system="generic", value="high")] - >>> get_severities([ - ... "Low - The `ValidationKey` and `DecryptionKey` values would need to be obtained via a separate vulnerability or other channel." - ... ]) - [VulnerabilitySeverity(system="generic", value="low")] + [VulnerabilitySeverity(system=ScoringSystem(identifier='generic_textual', name='Generic textual severity rating', url='', notes='Severity for generic scoring systems. Contains generic textual values like High, Low etc'), value='High', scoring_elements='', published_at=None, url=None)] >>> get_severities([]) [] """ From e79ce59df9ec2d45e85e6d3ebff70f7e74cab4a8 Mon Sep 17 00:00:00 2001 From: ziad hany Date: Tue, 30 Dec 2025 16:21:21 +0200 Subject: [PATCH 4/7] Fix code format test Signed-off-by: ziad hany --- vulnerabilities/importers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vulnerabilities/importers/__init__.py b/vulnerabilities/importers/__init__.py index 7aaa2072b..8aa9961d5 100644 --- a/vulnerabilities/importers/__init__.py +++ b/vulnerabilities/importers/__init__.py @@ -48,8 +48,8 @@ from vulnerabilities.pipelines.v2_importers import ( elixir_security_importer as elixir_security_importer_v2, ) -from vulnerabilities.pipelines.v2_importers import fireeye_importer_v2 from vulnerabilities.pipelines.v2_importers import epss_importer_v2 +from vulnerabilities.pipelines.v2_importers import fireeye_importer_v2 from vulnerabilities.pipelines.v2_importers import github_osv_importer as github_osv_importer_v2 from vulnerabilities.pipelines.v2_importers import gitlab_importer as gitlab_importer_v2 from vulnerabilities.pipelines.v2_importers import istio_importer as istio_importer_v2 From e598a6b9308f151a4f6b88cf66e76962a06bff71 Mon Sep 17 00:00:00 2001 From: ziad hany Date: Wed, 31 Dec 2025 13:29:16 +0200 Subject: [PATCH 5/7] Update FireEye importer to avoid using the identifier as an alias Signed-off-by: ziad hany --- .../v2_importers/fireeye_importer_v2.py | 18 ++++-------------- .../fireeye_v2/fireeye_test1-expected.json | 5 ++--- .../fireeye_v2/fireeye_test2-expected.json | 5 ++--- .../fireeye_v2/fireeye_test3-expected.json | 6 ++---- 4 files changed, 10 insertions(+), 24 deletions(-) diff --git a/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py b/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py index 0b9dd6ff5..828bff8dc 100644 --- a/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py +++ b/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py @@ -99,11 +99,12 @@ def parse_advisory_data(raw_data, file_path, base_path) -> AdvisoryData: summary = md_dict.get(database_id[1::]) or [] description = md_dict.get("## Description") or [] impact = md_dict.get("## Impact") - cve_ref = md_dict.get("## CVE Reference") or [] + cve_ids = md_dict.get("## CVE Reference") or [] references = md_dict.get("## References") or [] cwe_data = md_dict.get("## Common Weakness Enumeration") or [] - advisory_id = file_path.stem + advisory_id = database_id.strip() + aliases = dedupe([cve_id.strip() for cve_id in cve_ids]) advisory_url = get_advisory_url( file=file_path, base_path=base_path, @@ -112,7 +113,7 @@ def parse_advisory_data(raw_data, file_path, base_path) -> AdvisoryData: return AdvisoryData( advisory_id=advisory_id, - aliases=get_aliases(database_id, cve_ref), + aliases=aliases, summary=build_description(" ".join(summary), " ".join(description)), references_v2=get_references(references), severities=get_severities(impact), @@ -152,17 +153,6 @@ def matcher_url(ref) -> str: return ref -def get_aliases(database_id, cve_ref) -> List: - """ - Returns a List of Aliases from a database_id and a list of CVEs - >>> get_aliases("MNDT-2021-0012", ["CVE-2021-44207"]) - ['CVE-2021-44207', 'MNDT-2021-0012'] - """ - cleaned_db_id = database_id.strip() - cve_ref.append(cleaned_db_id) - return dedupe(cve_ref) - - def md_list_to_dict(md_list): """ Returns a dictionary of md_list from a list of a md file splited by \n diff --git a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test1-expected.json b/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test1-expected.json index ec3422ae9..8286f4e09 100644 --- a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test1-expected.json +++ b/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test1-expected.json @@ -1,9 +1,8 @@ [ { - "advisory_id": "fireeye_test1", + "advisory_id": "FEYE-2019-0002", "aliases": [ - "CVE-2019-7245 ", - "FEYE-2019-0002" + "CVE-2019-7245" ], "summary": "GPU-Z.sys, part of the GPU-Z package from TechPowerUp, exposes the wrmsr instruction to user-mode callers without properly validating the target Model Specific Register (MSR). This can result in arbitrary unsigned code being executed in Ring 0.", "affected_packages": [], diff --git a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test2-expected.json b/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test2-expected.json index 8ad9fa142..e6fe114aa 100644 --- a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test2-expected.json +++ b/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test2-expected.json @@ -1,9 +1,8 @@ [ { - "advisory_id": "fireeye_test2", + "advisory_id": "FEYE-2020-0020", "aliases": [ - "CVE-2020-12878", - "FEYE-2020-0020" + "CVE-2020-12878" ], "summary": "Digi International's ConnectPort X2e is susceptible to a local privilege escalation vulnerable to the privileged user `root`.", "affected_packages": [], diff --git a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test3-expected.json b/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test3-expected.json index 6f8f07483..4bf29e7bd 100644 --- a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test3-expected.json +++ b/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test3-expected.json @@ -1,9 +1,7 @@ [ { - "advisory_id": "fireeye_test3", - "aliases": [ - "MNDT-2025-0009" - ], + "advisory_id": "MNDT-2025-0009", + "aliases": [], "summary": "Improper verification of cryptographic signature in the installer for Zoom Workplace VDI Client for Windows may allow an authenticated user to conduct an escalation of privilege via local access.", "affected_packages": [], "references_v2": [ From 44c7470a7afde55bf6b17c883f60e1994203c562 Mon Sep 17 00:00:00 2001 From: ziad hany Date: Wed, 31 Dec 2025 14:34:12 +0200 Subject: [PATCH 6/7] Update FireEye importer to use filename as advisory id Signed-off-by: ziad hany --- .../pipelines/v2_importers/fireeye_importer_v2.py | 2 +- .../tests/pipelines/v2_importers/test_fireeye_v2.py | 6 +++--- ...eye_test1-expected.json => FEYE-2019-0002-expected.json} | 2 +- .../fireeye_v2/{fireeye_test1.md => FEYE-2019-0002.md} | 0 ...eye_test2-expected.json => FEYE-2020-0020-expected.json} | 2 +- .../fireeye_v2/{fireeye_test2.md => FEYE-2020-0020.md} | 0 ...eye_test3-expected.json => MNDT-2025-0009-expected.json} | 2 +- .../fireeye_v2/{fireeye_test3.md => MNDT-2025-0009.md} | 0 8 files changed, 7 insertions(+), 7 deletions(-) rename vulnerabilities/tests/test_data/fireeye_v2/{fireeye_test1-expected.json => FEYE-2019-0002-expected.json} (95%) rename vulnerabilities/tests/test_data/fireeye_v2/{fireeye_test1.md => FEYE-2019-0002.md} (100%) rename vulnerabilities/tests/test_data/fireeye_v2/{fireeye_test2-expected.json => FEYE-2020-0020-expected.json} (97%) rename vulnerabilities/tests/test_data/fireeye_v2/{fireeye_test2.md => FEYE-2020-0020.md} (100%) rename vulnerabilities/tests/test_data/fireeye_v2/{fireeye_test3-expected.json => MNDT-2025-0009-expected.json} (96%) rename vulnerabilities/tests/test_data/fireeye_v2/{fireeye_test3.md => MNDT-2025-0009.md} (100%) diff --git a/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py b/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py index 828bff8dc..4ee8f2c2b 100644 --- a/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py +++ b/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py @@ -103,7 +103,7 @@ def parse_advisory_data(raw_data, file_path, base_path) -> AdvisoryData: references = md_dict.get("## References") or [] cwe_data = md_dict.get("## Common Weakness Enumeration") or [] - advisory_id = database_id.strip() + advisory_id = file_path.stem aliases = dedupe([cve_id.strip() for cve_id in cve_ids]) advisory_url = get_advisory_url( file=file_path, diff --git a/vulnerabilities/tests/pipelines/v2_importers/test_fireeye_v2.py b/vulnerabilities/tests/pipelines/v2_importers/test_fireeye_v2.py index d22c8c9ec..b42ad5cd5 100644 --- a/vulnerabilities/tests/pipelines/v2_importers/test_fireeye_v2.py +++ b/vulnerabilities/tests/pipelines/v2_importers/test_fireeye_v2.py @@ -19,9 +19,9 @@ TEST_DATA = Path(__file__).parent.parent.parent / "test_data" / "fireeye_v2" TEST_CVE_FILES = [ - TEST_DATA / "fireeye_test1.md", - TEST_DATA / "fireeye_test2.md", - TEST_DATA / "fireeye_test3.md", + TEST_DATA / "FEYE-2019-0002.md", + TEST_DATA / "FEYE-2020-0020.md", + TEST_DATA / "MNDT-2025-0009.md", ] diff --git a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test1-expected.json b/vulnerabilities/tests/test_data/fireeye_v2/FEYE-2019-0002-expected.json similarity index 95% rename from vulnerabilities/tests/test_data/fireeye_v2/fireeye_test1-expected.json rename to vulnerabilities/tests/test_data/fireeye_v2/FEYE-2019-0002-expected.json index 8286f4e09..3dcad14db 100644 --- a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test1-expected.json +++ b/vulnerabilities/tests/test_data/fireeye_v2/FEYE-2019-0002-expected.json @@ -17,6 +17,6 @@ ], "date_published": null, "weaknesses": [], - "url": "https://github.com/mandiant/Vulnerability-Disclosures/blob/master/fireeye_test1.md" + "url": "https://github.com/mandiant/Vulnerability-Disclosures/blob/master/FEYE-2019-0002.md" } ] \ No newline at end of file diff --git a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test1.md b/vulnerabilities/tests/test_data/fireeye_v2/FEYE-2019-0002.md similarity index 100% rename from vulnerabilities/tests/test_data/fireeye_v2/fireeye_test1.md rename to vulnerabilities/tests/test_data/fireeye_v2/FEYE-2019-0002.md diff --git a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test2-expected.json b/vulnerabilities/tests/test_data/fireeye_v2/FEYE-2020-0020-expected.json similarity index 97% rename from vulnerabilities/tests/test_data/fireeye_v2/fireeye_test2-expected.json rename to vulnerabilities/tests/test_data/fireeye_v2/FEYE-2020-0020-expected.json index e6fe114aa..2840f0d0f 100644 --- a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test2-expected.json +++ b/vulnerabilities/tests/test_data/fireeye_v2/FEYE-2020-0020-expected.json @@ -33,6 +33,6 @@ ], "date_published": null, "weaknesses": [], - "url": "https://github.com/mandiant/Vulnerability-Disclosures/blob/master/fireeye_test2.md" + "url": "https://github.com/mandiant/Vulnerability-Disclosures/blob/master/FEYE-2020-0020.md" } ] \ No newline at end of file diff --git a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test2.md b/vulnerabilities/tests/test_data/fireeye_v2/FEYE-2020-0020.md similarity index 100% rename from vulnerabilities/tests/test_data/fireeye_v2/fireeye_test2.md rename to vulnerabilities/tests/test_data/fireeye_v2/FEYE-2020-0020.md diff --git a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test3-expected.json b/vulnerabilities/tests/test_data/fireeye_v2/MNDT-2025-0009-expected.json similarity index 96% rename from vulnerabilities/tests/test_data/fireeye_v2/fireeye_test3-expected.json rename to vulnerabilities/tests/test_data/fireeye_v2/MNDT-2025-0009-expected.json index 4bf29e7bd..1c58326f0 100644 --- a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test3-expected.json +++ b/vulnerabilities/tests/test_data/fireeye_v2/MNDT-2025-0009-expected.json @@ -28,6 +28,6 @@ "weaknesses": [ 347 ], - "url": "https://github.com/mandiant/Vulnerability-Disclosures/blob/master/fireeye_test3.md" + "url": "https://github.com/mandiant/Vulnerability-Disclosures/blob/master/MNDT-2025-0009.md" } ] \ No newline at end of file diff --git a/vulnerabilities/tests/test_data/fireeye_v2/fireeye_test3.md b/vulnerabilities/tests/test_data/fireeye_v2/MNDT-2025-0009.md similarity index 100% rename from vulnerabilities/tests/test_data/fireeye_v2/fireeye_test3.md rename to vulnerabilities/tests/test_data/fireeye_v2/MNDT-2025-0009.md From d7807e9107eeda4853f6f2c086dc04bf08c45d1b Mon Sep 17 00:00:00 2001 From: ziad hany Date: Wed, 31 Dec 2025 15:59:52 +0200 Subject: [PATCH 7/7] Add missing CVEs to aliases field for MNDT files Signed-off-by: ziad hany --- .../pipelines/v2_importers/fireeye_importer_v2.py | 13 ++++++++++--- .../fireeye_v2/MNDT-2025-0009-expected.json | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py b/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py index 4ee8f2c2b..19430c237 100644 --- a/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py +++ b/vulnerabilities/pipelines/v2_importers/fireeye_importer_v2.py @@ -10,7 +10,6 @@ import re from pathlib import Path from typing import Iterable -from typing import List from fetchcode.vcs import fetch_via_vcs @@ -23,6 +22,7 @@ from vulnerabilities.utils import create_weaknesses_list from vulnerabilities.utils import cwe_regex from vulnerabilities.utils import dedupe +from vulnerabilities.utils import find_all_cve from vulnerabilities.utils import get_advisory_url logger = logging.getLogger(__name__) @@ -99,12 +99,19 @@ def parse_advisory_data(raw_data, file_path, base_path) -> AdvisoryData: summary = md_dict.get(database_id[1::]) or [] description = md_dict.get("## Description") or [] impact = md_dict.get("## Impact") - cve_ids = md_dict.get("## CVE Reference") or [] + cve_refs = md_dict.get("## CVE Reference") or [] + cve_ids = md_dict.get("## CVE ID") or [] + cleaned_cve_ids = [] + for line in cve_ids: + found_cves = find_all_cve(line) + cleaned_cve_ids.extend(found_cves) + references = md_dict.get("## References") or [] cwe_data = md_dict.get("## Common Weakness Enumeration") or [] advisory_id = file_path.stem - aliases = dedupe([cve_id.strip() for cve_id in cve_ids]) + aliases = dedupe([cve.strip() for cve in cleaned_cve_ids + cve_refs]) + aliases = [aliase for aliase in aliases if aliase != advisory_id] advisory_url = get_advisory_url( file=file_path, base_path=base_path, diff --git a/vulnerabilities/tests/test_data/fireeye_v2/MNDT-2025-0009-expected.json b/vulnerabilities/tests/test_data/fireeye_v2/MNDT-2025-0009-expected.json index 1c58326f0..7d875f201 100644 --- a/vulnerabilities/tests/test_data/fireeye_v2/MNDT-2025-0009-expected.json +++ b/vulnerabilities/tests/test_data/fireeye_v2/MNDT-2025-0009-expected.json @@ -1,7 +1,7 @@ [ { "advisory_id": "MNDT-2025-0009", - "aliases": [], + "aliases": ["CVE-2025-64740"], "summary": "Improper verification of cryptographic signature in the installer for Zoom Workplace VDI Client for Windows may allow an authenticated user to conduct an escalation of privilege via local access.", "affected_packages": [], "references_v2": [