From 49cf20bd6ae583928bdb6db07fdd259beb00fcc8 Mon Sep 17 00:00:00 2001 From: Anmol Vats Date: Sat, 14 Mar 2026 00:06:12 -0400 Subject: [PATCH 1/5] Add LibreOffice security advisories importer Fetches CVE IDs from the LibreOffice advisory listing page and retrieves structured data (CVSS, CWE, references, dates) from the CVE 5.0 JSON API at cveawg.mitre.org. Fixes: #1898 Signed-off-by: Anmol Vats --- vulnerabilities/importers/__init__.py | 2 + .../v2_importers/libreoffice_importer.py | 154 ++++++++++++++++++ .../v2_importers/test_libreoffice_importer.py | 144 ++++++++++++++++ .../test_data/libreoffice/advisories.html | 17 ++ .../test_data/libreoffice/cve_2023_2255.json | 76 +++++++++ .../test_data/libreoffice/cve_2025_1080.json | 91 +++++++++++ 6 files changed, 484 insertions(+) create mode 100644 vulnerabilities/pipelines/v2_importers/libreoffice_importer.py create mode 100644 vulnerabilities/tests/pipelines/v2_importers/test_libreoffice_importer.py create mode 100644 vulnerabilities/tests/test_data/libreoffice/advisories.html create mode 100644 vulnerabilities/tests/test_data/libreoffice/cve_2023_2255.json create mode 100644 vulnerabilities/tests/test_data/libreoffice/cve_2025_1080.json diff --git a/vulnerabilities/importers/__init__.py b/vulnerabilities/importers/__init__.py index 594021092..83a51f2b2 100644 --- a/vulnerabilities/importers/__init__.py +++ b/vulnerabilities/importers/__init__.py @@ -59,6 +59,7 @@ 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 vulnerabilities.pipelines.v2_importers import libreoffice_importer as libreoffice_importer_v2 from vulnerabilities.pipelines.v2_importers import mattermost_importer as mattermost_importer_v2 from vulnerabilities.pipelines.v2_importers import mozilla_importer as mozilla_importer_v2 from vulnerabilities.pipelines.v2_importers import nginx_importer as nginx_importer_v2 @@ -118,6 +119,7 @@ retiredotnet_importer_v2.RetireDotnetImporterPipeline, ubuntu_osv_importer_v2.UbuntuOSVImporterPipeline, alpine_linux_importer_v2.AlpineLinuxImporterPipeline, + libreoffice_importer_v2.LibreOfficeImporterPipeline, nvd_importer.NVDImporterPipeline, github_importer.GitHubAPIImporterPipeline, gitlab_importer.GitLabImporterPipeline, diff --git a/vulnerabilities/pipelines/v2_importers/libreoffice_importer.py b/vulnerabilities/pipelines/v2_importers/libreoffice_importer.py new file mode 100644 index 000000000..b14c9e64d --- /dev/null +++ b/vulnerabilities/pipelines/v2_importers/libreoffice_importer.py @@ -0,0 +1,154 @@ +# +# 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 json +import logging +import re +from typing import Iterable + +import dateparser +import requests + +from vulnerabilities.importer import AdvisoryDataV2 +from vulnerabilities.importer import ReferenceV2 +from vulnerabilities.importer import VulnerabilitySeverity +from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipelineV2 +from vulnerabilities.severity_systems import SCORING_SYSTEMS +from vulnerabilities.utils import get_cwe_id + +logger = logging.getLogger(__name__) + +ADVISORIES_URL = "https://www.libreoffice.org/about-us/security/advisories/" +CVE_API_URL = "https://cveawg.mitre.org/api/cve/{cve_id}" + +CVSS_KEY_MAP = { + "cvssV4_0": SCORING_SYSTEMS["cvssv4"], + "cvssV3_1": SCORING_SYSTEMS["cvssv3.1"], + "cvssV3_0": SCORING_SYSTEMS["cvssv3"], + "cvssV2_0": SCORING_SYSTEMS["cvssv2"], +} + + +class LibreOfficeImporterPipeline(VulnerableCodeBaseImporterPipelineV2): + """Collect LibreOffice security advisories via the CVE API.""" + + pipeline_id = "libreoffice_importer" + spdx_license_expression = "LicenseRef-scancode-proprietary-license" + license_url = "https://www.libreoffice.org/about-us/security/" + precedence = 200 + + @classmethod + def steps(cls): + return ( + cls.fetch, + cls.collect_and_store_advisories, + ) + + def fetch(self): + self.log(f"Fetch `{ADVISORIES_URL}`") + resp = requests.get(ADVISORIES_URL, timeout=30) + resp.raise_for_status() + self.cve_ids = parse_cve_ids(resp.text) + + def advisories_count(self): + return len(self.cve_ids) + + def collect_advisories(self) -> Iterable[AdvisoryDataV2]: + for cve_id in self.cve_ids: + url = CVE_API_URL.format(cve_id=cve_id) + try: + resp = requests.get(url, timeout=30) + resp.raise_for_status() + except Exception as e: + logger.error("Failed to fetch CVE API for %s: %s", cve_id, e) + continue + advisory = parse_cve_advisory(resp.json(), cve_id) + if advisory: + yield advisory + + +def parse_cve_ids(html: str) -> list: + """Return deduplicated CVE IDs from the LibreOffice advisories listing page.""" + return list(dict.fromkeys(re.findall(r"CVE-\d{4}-\d+", html))) + + +def parse_cve_advisory(data: dict, cve_id: str): + """Parse a CVE 5.0 JSON record from cveawg.mitre.org; return None if CVE ID is absent.""" + cve_metadata = data.get("cveMetadata") or {} + advisory_id = cve_metadata.get("cveId") or cve_id + if not advisory_id: + return None + + date_published = None + raw_date = cve_metadata.get("datePublished") or "" + if raw_date: + date_published = dateparser.parse( + raw_date, + settings={"TIMEZONE": "UTC", "RETURN_AS_TIMEZONE_AWARE": True, "TO_TIMEZONE": "UTC"}, + ) + if date_published is None: + logger.warning("Could not parse date %r for %s", raw_date, advisory_id) + + cna = (data.get("containers") or {}).get("cna") or {} + + summary = "" + for desc in cna.get("descriptions") or []: + if desc.get("lang") in ("en", "en-US"): + summary = desc.get("value") or "" + break + + severities = [] + for metric in cna.get("metrics") or []: + for key, system in CVSS_KEY_MAP.items(): + cvss = metric.get(key) + if not cvss: + continue + vector = cvss.get("vectorString") or "" + score = cvss.get("baseScore") + if vector and score is not None: + severities.append( + VulnerabilitySeverity( + system=system, + value=str(score), + scoring_elements=vector, + ) + ) + break + + weaknesses = [] + for problem_type in cna.get("problemTypes") or []: + for desc in problem_type.get("descriptions") or []: + cwe_str = desc.get("cweId") or "" + if cwe_str.upper().startswith("CWE-"): + try: + weaknesses.append(get_cwe_id(cwe_str)) + except Exception: + pass + + advisory_url = ( + f"https://www.libreoffice.org/about-us/security/advisories/{advisory_id.lower()}/" + ) + references = [] + for ref in cna.get("references") or []: + url = ref.get("url") or "" + if url: + references.append(ReferenceV2(url=url)) + + return AdvisoryDataV2( + advisory_id=advisory_id, + aliases=[], + summary=summary, + affected_packages=[], + references=references, + date_published=date_published, + weaknesses=weaknesses, + severities=severities, + url=advisory_url, + original_advisory_text=json.dumps(data, indent=2, ensure_ascii=False), + ) diff --git a/vulnerabilities/tests/pipelines/v2_importers/test_libreoffice_importer.py b/vulnerabilities/tests/pipelines/v2_importers/test_libreoffice_importer.py new file mode 100644 index 000000000..c483aa76a --- /dev/null +++ b/vulnerabilities/tests/pipelines/v2_importers/test_libreoffice_importer.py @@ -0,0 +1,144 @@ +# +# 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 json +import os +from unittest import TestCase +from unittest.mock import MagicMock +from unittest.mock import patch + +from vulnerabilities.pipelines.v2_importers.libreoffice_importer import LibreOfficeImporterPipeline +from vulnerabilities.pipelines.v2_importers.libreoffice_importer import parse_cve_advisory +from vulnerabilities.pipelines.v2_importers.libreoffice_importer import parse_cve_ids + +TEST_DATA = os.path.join(os.path.dirname(__file__), "..", "..", "test_data", "libreoffice") + + +def load_json(filename): + with open(os.path.join(TEST_DATA, filename), encoding="utf-8") as f: + return json.load(f) + + +def load_html(filename): + with open(os.path.join(TEST_DATA, filename), encoding="utf-8") as f: + return f.read() + + +class TestParseCveIds(TestCase): + def test_extracts_cve_ids_from_html(self): + html = load_html("advisories.html") + cve_ids = parse_cve_ids(html) + self.assertIn("CVE-2025-1080", cve_ids) + self.assertIn("CVE-2023-2255", cve_ids) + self.assertIn("CVE-2023-4863", cve_ids) + + def test_deduplicates_repeated_ids(self): + html = "CVE-2025-1080 ... CVE-2025-1080" + self.assertEqual(parse_cve_ids(html), ["CVE-2025-1080"]) + + def test_empty_html_returns_empty_list(self): + self.assertEqual(parse_cve_ids(""), []) + + +class TestParseCveAdvisory(TestCase): + def test_cvss4_and_cwe(self): + data = load_json("cve_2025_1080.json") + advisory = parse_cve_advisory(data, "CVE-2025-1080") + self.assertIsNotNone(advisory) + self.assertEqual(advisory.advisory_id, "CVE-2025-1080") + self.assertEqual(advisory.aliases, []) + self.assertIn("macro", advisory.summary.lower()) + self.assertEqual(len(advisory.severities), 1) + self.assertEqual(advisory.severities[0].value, "7.2") + self.assertIn("CVSS:4.0/", advisory.severities[0].scoring_elements) + self.assertEqual(advisory.weaknesses, [20]) + self.assertIsNotNone(advisory.date_published) + self.assertIn("cve-2025-1080", advisory.url) + + def test_no_cvss_has_empty_severities(self): + data = load_json("cve_2023_2255.json") + advisory = parse_cve_advisory(data, "CVE-2023-2255") + self.assertIsNotNone(advisory) + self.assertEqual(advisory.severities, []) + + def test_cwe_264_extracted(self): + data = load_json("cve_2023_2255.json") + advisory = parse_cve_advisory(data, "CVE-2023-2255") + self.assertEqual(advisory.weaknesses, [264]) + + def test_references_from_cna(self): + data = load_json("cve_2023_2255.json") + advisory = parse_cve_advisory(data, "CVE-2023-2255") + urls = [r.url for r in advisory.references] + self.assertIn("https://www.debian.org/security/2023/dsa-5415", urls) + self.assertIn("https://security.gentoo.org/glsa/202311-15", urls) + + def test_missing_cve_id_returns_none(self): + advisory = parse_cve_advisory({"cveMetadata": {"cveId": ""}, "containers": {}}, "") + self.assertIsNone(advisory) + + def test_original_advisory_text_is_json(self): + data = load_json("cve_2025_1080.json") + advisory = parse_cve_advisory(data, "CVE-2025-1080") + parsed = json.loads(advisory.original_advisory_text) + self.assertEqual(parsed["cveMetadata"]["cveId"], "CVE-2025-1080") + + def test_malformed_cwe_skipped(self): + data = load_json("cve_2025_1080.json") + data = json.loads(json.dumps(data)) + data["containers"]["cna"]["problemTypes"] = [ + {"descriptions": [{"cweId": "CWE-INVALID", "lang": "en", "type": "CWE"}]} + ] + advisory = parse_cve_advisory(data, "CVE-2025-1080") + self.assertEqual(advisory.weaknesses, []) + + +class TestLibreOfficeImporterPipeline(TestCase): + def _make_resp(self, data, status=200): + resp = MagicMock() + resp.json.return_value = data + resp.text = json.dumps(data) + resp.raise_for_status.return_value = None + resp.status_code = status + return resp + + @patch("vulnerabilities.pipelines.v2_importers.libreoffice_importer.requests.get") + def test_fetch_stores_cve_ids(self, mock_get): + html = load_html("advisories.html") + mock_get.return_value = MagicMock(text=html, raise_for_status=MagicMock()) + pipeline = LibreOfficeImporterPipeline() + pipeline.fetch() + self.assertIn("CVE-2025-1080", pipeline.cve_ids) + self.assertIn("CVE-2023-2255", pipeline.cve_ids) + + @patch("vulnerabilities.pipelines.v2_importers.libreoffice_importer.requests.get") + def test_collect_advisories_yields_advisory(self, mock_get): + cve_data = load_json("cve_2025_1080.json") + pipeline = LibreOfficeImporterPipeline() + pipeline.cve_ids = ["CVE-2025-1080"] + mock_get.return_value = self._make_resp(cve_data) + advisories = list(pipeline.collect_advisories()) + self.assertEqual(len(advisories), 1) + self.assertEqual(advisories[0].advisory_id, "CVE-2025-1080") + + @patch("vulnerabilities.pipelines.v2_importers.libreoffice_importer.requests.get") + def test_collect_advisories_skips_on_http_error(self, mock_get): + pipeline = LibreOfficeImporterPipeline() + pipeline.cve_ids = ["CVE-2025-1080"] + mock_get.side_effect = Exception("timeout") + logger_name = "vulnerabilities.pipelines.v2_importers.libreoffice_importer" + with self.assertLogs(logger_name, level="ERROR") as cm: + advisories = list(pipeline.collect_advisories()) + self.assertEqual(advisories, []) + self.assertTrue(any("CVE-2025-1080" in msg for msg in cm.output)) + + def test_advisories_count(self): + pipeline = LibreOfficeImporterPipeline() + pipeline.cve_ids = ["CVE-2025-1080", "CVE-2023-2255"] + self.assertEqual(pipeline.advisories_count(), 2) diff --git a/vulnerabilities/tests/test_data/libreoffice/advisories.html b/vulnerabilities/tests/test_data/libreoffice/advisories.html new file mode 100644 index 000000000..dd46225ed --- /dev/null +++ b/vulnerabilities/tests/test_data/libreoffice/advisories.html @@ -0,0 +1,17 @@ + + + +

Addressed in LibreOffice 24.8.5 and 25.2.1

+ +

Addressed in LibreOffice 7.4.7 and 7.5.3

+ +

Third Party Advisories

+ + + diff --git a/vulnerabilities/tests/test_data/libreoffice/cve_2023_2255.json b/vulnerabilities/tests/test_data/libreoffice/cve_2023_2255.json new file mode 100644 index 000000000..ff0bfaf79 --- /dev/null +++ b/vulnerabilities/tests/test_data/libreoffice/cve_2023_2255.json @@ -0,0 +1,76 @@ +{ + "dataType": "CVE_RECORD", + "dataVersion": "5.1", + "cveMetadata": { + "state": "PUBLISHED", + "cveId": "CVE-2023-2255", + "assignerOrgId": "4fe7d05b-1353-44cc-8b7a-1e416936dff2", + "assignerShortName": "Document Fdn.", + "dateReserved": "2023-04-24T00:00:00.000Z", + "datePublished": "2023-05-25T00:00:00.000Z", + "dateUpdated": "2024-08-02T06:19:14.082Z" + }, + "containers": { + "cna": { + "title": "Remote documents loaded without prompt via IFrame", + "providerMetadata": { + "orgId": "4fe7d05b-1353-44cc-8b7a-1e416936dff2", + "shortName": "Document Fdn.", + "dateUpdated": "2023-11-26T09:06:16.295Z" + }, + "descriptions": [ + { + "lang": "en", + "value": "Improper access control in editor components of The Document Foundation LibreOffice allowed an attacker to craft a document that would cause external links to be loaded without prompt. In the affected versions of LibreOffice documents that used \"floating frames\" linked to external files, would load the contents of those frames without prompting the user for permission to do so. This issue affects: The Document Foundation LibreOffice 7.4 versions prior to 7.4.7; 7.5 versions prior to 7.5.3." + } + ], + "affected": [ + { + "vendor": "The Document Foundation", + "product": "LibreOffice", + "versions": [ + { + "version": "7.4", + "status": "affected", + "lessThan": "7.4.7", + "versionType": "custom" + }, + { + "version": "7.5", + "status": "affected", + "lessThan": "7.5.3", + "versionType": "custom" + } + ] + } + ], + "references": [ + { + "url": "https://www.libreoffice.org/about-us/security/advisories/CVE-2023-2255" + }, + { + "name": "DSA-5415", + "tags": ["vendor-advisory"], + "url": "https://www.debian.org/security/2023/dsa-5415" + }, + { + "name": "GLSA-202311-15", + "tags": ["vendor-advisory"], + "url": "https://security.gentoo.org/glsa/202311-15" + } + ], + "problemTypes": [ + { + "descriptions": [ + { + "type": "CWE", + "lang": "en", + "description": "CWE-264 Permissions, Privileges, and Access Controls", + "cweId": "CWE-264" + } + ] + } + ] + } + } +} diff --git a/vulnerabilities/tests/test_data/libreoffice/cve_2025_1080.json b/vulnerabilities/tests/test_data/libreoffice/cve_2025_1080.json new file mode 100644 index 000000000..b9ee8fc05 --- /dev/null +++ b/vulnerabilities/tests/test_data/libreoffice/cve_2025_1080.json @@ -0,0 +1,91 @@ +{ + "dataType": "CVE_RECORD", + "dataVersion": "5.2", + "cveMetadata": { + "cveId": "CVE-2025-1080", + "assignerOrgId": "4fe7d05b-1353-44cc-8b7a-1e416936dff2", + "state": "PUBLISHED", + "assignerShortName": "Document Fdn.", + "dateReserved": "2025-02-06T13:14:08.175Z", + "datePublished": "2025-03-04T20:04:10.946Z", + "dateUpdated": "2025-11-03T19:35:13.950Z" + }, + "containers": { + "cna": { + "affected": [ + { + "defaultStatus": "unknown", + "product": "LibreOffice", + "vendor": "The Document Foundation", + "versions": [ + { + "lessThan": "< 24.8.5", + "status": "affected", + "version": "24.8", + "versionType": "24.8 series" + }, + { + "lessThan": "< 25.2.1", + "status": "affected", + "version": "25.2", + "versionType": "25.2 series" + } + ] + } + ], + "credits": [ + { + "lang": "en", + "type": "finder", + "value": "Thanks to Amel Bouziane-Leblond for finding and reporting this issue." + } + ], + "datePublic": "2025-03-04T19:00:00.000Z", + "descriptions": [ + { + "lang": "en", + "value": "LibreOffice supports Office URI Schemes to enable browser integration of LibreOffice with MS SharePoint server. An additional scheme 'vnd.libreoffice.command' specific to LibreOffice was added. In the affected versions of LibreOffice a link in a browser using that scheme could be constructed with an embedded inner URL that when passed to LibreOffice could call internal macros with arbitrary arguments.\nThis issue affects LibreOffice: from 24.8 before < 24.8.5, from 25.2 before < 25.2.1." + } + ], + "metrics": [ + { + "cvssV4_0": { + "attackComplexity": "HIGH", + "attackRequirements": "NONE", + "attackVector": "LOCAL", + "baseScore": 7.2, + "baseSeverity": "HIGH", + "privilegesRequired": "NONE", + "userInteraction": "PASSIVE", + "vectorString": "CVSS:4.0/AV:L/AC:H/AT:N/PR:N/UI:P/VC:H/VI:L/VA:H/SC:H/SI:H/SA:H", + "version": "4.0" + }, + "format": "CVSS" + } + ], + "problemTypes": [ + { + "descriptions": [ + { + "cweId": "CWE-20", + "description": "CWE-20 Improper Input Validation", + "lang": "en", + "type": "CWE" + } + ] + } + ], + "providerMetadata": { + "orgId": "4fe7d05b-1353-44cc-8b7a-1e416936dff2", + "shortName": "Document Fdn.", + "dateUpdated": "2025-03-04T20:04:10.946Z" + }, + "references": [ + { + "url": "https://www.libreoffice.org/about-us/security/advisories/cve-2025-1080" + } + ], + "title": "Macro URL arbitrary script execution" + } + } +} From 08933df8b26849e7c96d6429ca1705e413db0b59 Mon Sep 17 00:00:00 2001 From: Anmol Vats Date: Sat, 14 Mar 2026 00:14:22 -0400 Subject: [PATCH 2/5] Clean up libreoffice importer tests Remove advisories.html fixture in favour of inline ADVISORY_HTML constant. Drop dead mock attributes and _make_resp helper. Signed-off-by: Anmol Vats --- .../v2_importers/test_libreoffice_importer.py | 44 +++++++++++-------- .../test_data/libreoffice/advisories.html | 17 ------- 2 files changed, 25 insertions(+), 36 deletions(-) delete mode 100644 vulnerabilities/tests/test_data/libreoffice/advisories.html diff --git a/vulnerabilities/tests/pipelines/v2_importers/test_libreoffice_importer.py b/vulnerabilities/tests/pipelines/v2_importers/test_libreoffice_importer.py index c483aa76a..1dfc14871 100644 --- a/vulnerabilities/tests/pipelines/v2_importers/test_libreoffice_importer.py +++ b/vulnerabilities/tests/pipelines/v2_importers/test_libreoffice_importer.py @@ -19,21 +19,30 @@ TEST_DATA = os.path.join(os.path.dirname(__file__), "..", "..", "test_data", "libreoffice") +ADVISORY_HTML = """ +

Addressed in LibreOffice 24.8.5 and 25.2.1

+ +

Addressed in LibreOffice 7.4.7

+ +

Third Party Advisories

+ +""" + def load_json(filename): with open(os.path.join(TEST_DATA, filename), encoding="utf-8") as f: return json.load(f) -def load_html(filename): - with open(os.path.join(TEST_DATA, filename), encoding="utf-8") as f: - return f.read() - - class TestParseCveIds(TestCase): def test_extracts_cve_ids_from_html(self): - html = load_html("advisories.html") - cve_ids = parse_cve_ids(html) + cve_ids = parse_cve_ids(ADVISORY_HTML) self.assertIn("CVE-2025-1080", cve_ids) self.assertIn("CVE-2023-2255", cve_ids) self.assertIn("CVE-2023-4863", cve_ids) @@ -100,18 +109,12 @@ def test_malformed_cwe_skipped(self): class TestLibreOfficeImporterPipeline(TestCase): - def _make_resp(self, data, status=200): - resp = MagicMock() - resp.json.return_value = data - resp.text = json.dumps(data) - resp.raise_for_status.return_value = None - resp.status_code = status - return resp - @patch("vulnerabilities.pipelines.v2_importers.libreoffice_importer.requests.get") def test_fetch_stores_cve_ids(self, mock_get): - html = load_html("advisories.html") - mock_get.return_value = MagicMock(text=html, raise_for_status=MagicMock()) + resp = MagicMock() + resp.text = ADVISORY_HTML + resp.raise_for_status.return_value = None + mock_get.return_value = resp pipeline = LibreOfficeImporterPipeline() pipeline.fetch() self.assertIn("CVE-2025-1080", pipeline.cve_ids) @@ -120,18 +123,21 @@ def test_fetch_stores_cve_ids(self, mock_get): @patch("vulnerabilities.pipelines.v2_importers.libreoffice_importer.requests.get") def test_collect_advisories_yields_advisory(self, mock_get): cve_data = load_json("cve_2025_1080.json") + resp = MagicMock() + resp.json.return_value = cve_data + resp.raise_for_status.return_value = None + mock_get.return_value = resp pipeline = LibreOfficeImporterPipeline() pipeline.cve_ids = ["CVE-2025-1080"] - mock_get.return_value = self._make_resp(cve_data) advisories = list(pipeline.collect_advisories()) self.assertEqual(len(advisories), 1) self.assertEqual(advisories[0].advisory_id, "CVE-2025-1080") @patch("vulnerabilities.pipelines.v2_importers.libreoffice_importer.requests.get") def test_collect_advisories_skips_on_http_error(self, mock_get): + mock_get.side_effect = Exception("timeout") pipeline = LibreOfficeImporterPipeline() pipeline.cve_ids = ["CVE-2025-1080"] - mock_get.side_effect = Exception("timeout") logger_name = "vulnerabilities.pipelines.v2_importers.libreoffice_importer" with self.assertLogs(logger_name, level="ERROR") as cm: advisories = list(pipeline.collect_advisories()) diff --git a/vulnerabilities/tests/test_data/libreoffice/advisories.html b/vulnerabilities/tests/test_data/libreoffice/advisories.html deleted file mode 100644 index dd46225ed..000000000 --- a/vulnerabilities/tests/test_data/libreoffice/advisories.html +++ /dev/null @@ -1,17 +0,0 @@ - - - -

Addressed in LibreOffice 24.8.5 and 25.2.1

- -

Addressed in LibreOffice 7.4.7 and 7.5.3

-
    -
  • CVE-2023-2255 Remote documents loaded without prompt via IFrame
  • -
-

Third Party Advisories

- - - From 072379e8742fc76ef84ac216deb06651d59babf7 Mon Sep 17 00:00:00 2001 From: Anmol Vats Date: Sat, 14 Mar 2026 00:19:30 -0400 Subject: [PATCH 3/5] Use find_all_cve from utils in libreoffice importer Replace local re.findall CVE regex with the shared find_all_cve utility. Normalise to uppercase before dedup to handle IGNORECASE matches from both href and link text. Signed-off-by: Anmol Vats --- .../pipelines/v2_importers/libreoffice_importer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vulnerabilities/pipelines/v2_importers/libreoffice_importer.py b/vulnerabilities/pipelines/v2_importers/libreoffice_importer.py index b14c9e64d..ffc743859 100644 --- a/vulnerabilities/pipelines/v2_importers/libreoffice_importer.py +++ b/vulnerabilities/pipelines/v2_importers/libreoffice_importer.py @@ -9,7 +9,6 @@ import json import logging -import re from typing import Iterable import dateparser @@ -20,6 +19,7 @@ from vulnerabilities.importer import VulnerabilitySeverity from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipelineV2 from vulnerabilities.severity_systems import SCORING_SYSTEMS +from vulnerabilities.utils import find_all_cve from vulnerabilities.utils import get_cwe_id logger = logging.getLogger(__name__) @@ -75,7 +75,7 @@ def collect_advisories(self) -> Iterable[AdvisoryDataV2]: def parse_cve_ids(html: str) -> list: """Return deduplicated CVE IDs from the LibreOffice advisories listing page.""" - return list(dict.fromkeys(re.findall(r"CVE-\d{4}-\d+", html))) + return list(dict.fromkeys(cve.upper() for cve in find_all_cve(html))) def parse_cve_advisory(data: dict, cve_id: str): From bb4736d8bf574f2f99f11090bd9921ed4cc63bb0 Mon Sep 17 00:00:00 2001 From: Anmol Vats Date: Sat, 14 Mar 2026 16:08:47 -0400 Subject: [PATCH 4/5] Switch libreoffice importer to HTML parsing Parse advisory listing and individual advisory pages directly from libreoffice.org instead of calling cveawg.mitre.org. Drop unused JSON fixtures and update tests accordingly. Signed-off-by: Anmol Vats --- .../v2_importers/libreoffice_importer.py | 136 ++++++------- .../v2_importers/test_libreoffice_importer.py | 184 +++++++++--------- .../test_data/libreoffice/cve_2023_2255.json | 76 -------- .../test_data/libreoffice/cve_2025_1080.json | 91 --------- 4 files changed, 154 insertions(+), 333 deletions(-) delete mode 100644 vulnerabilities/tests/test_data/libreoffice/cve_2023_2255.json delete mode 100644 vulnerabilities/tests/test_data/libreoffice/cve_2025_1080.json diff --git a/vulnerabilities/pipelines/v2_importers/libreoffice_importer.py b/vulnerabilities/pipelines/v2_importers/libreoffice_importer.py index ffc743859..7a4f8d614 100644 --- a/vulnerabilities/pipelines/v2_importers/libreoffice_importer.py +++ b/vulnerabilities/pipelines/v2_importers/libreoffice_importer.py @@ -7,36 +7,25 @@ # See https://aboutcode.org for more information about nexB OSS projects. # -import json import logging +import re from typing import Iterable import dateparser import requests +from bs4 import BeautifulSoup from vulnerabilities.importer import AdvisoryDataV2 from vulnerabilities.importer import ReferenceV2 -from vulnerabilities.importer import VulnerabilitySeverity from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipelineV2 -from vulnerabilities.severity_systems import SCORING_SYSTEMS -from vulnerabilities.utils import find_all_cve -from vulnerabilities.utils import get_cwe_id logger = logging.getLogger(__name__) ADVISORIES_URL = "https://www.libreoffice.org/about-us/security/advisories/" -CVE_API_URL = "https://cveawg.mitre.org/api/cve/{cve_id}" - -CVSS_KEY_MAP = { - "cvssV4_0": SCORING_SYSTEMS["cvssv4"], - "cvssV3_1": SCORING_SYSTEMS["cvssv3.1"], - "cvssV3_0": SCORING_SYSTEMS["cvssv3"], - "cvssV2_0": SCORING_SYSTEMS["cvssv2"], -} class LibreOfficeImporterPipeline(VulnerableCodeBaseImporterPipelineV2): - """Collect LibreOffice security advisories via the CVE API.""" + """Collect LibreOffice security advisories from libreoffice.org.""" pipeline_id = "libreoffice_importer" spdx_license_expression = "LicenseRef-scancode-proprietary-license" @@ -54,101 +43,90 @@ def fetch(self): self.log(f"Fetch `{ADVISORIES_URL}`") resp = requests.get(ADVISORIES_URL, timeout=30) resp.raise_for_status() - self.cve_ids = parse_cve_ids(resp.text) + self.advisory_urls = parse_advisory_urls(resp.text) def advisories_count(self): - return len(self.cve_ids) + return len(self.advisory_urls) def collect_advisories(self) -> Iterable[AdvisoryDataV2]: - for cve_id in self.cve_ids: - url = CVE_API_URL.format(cve_id=cve_id) + for url in self.advisory_urls: try: resp = requests.get(url, timeout=30) resp.raise_for_status() except Exception as e: - logger.error("Failed to fetch CVE API for %s: %s", cve_id, e) + logger.error("Failed to fetch %s: %s", url, e) continue - advisory = parse_cve_advisory(resp.json(), cve_id) + advisory = parse_advisory(resp.text, url) if advisory: yield advisory -def parse_cve_ids(html: str) -> list: - """Return deduplicated CVE IDs from the LibreOffice advisories listing page.""" - return list(dict.fromkeys(cve.upper() for cve in find_all_cve(html))) +def parse_advisory_urls(html: str) -> list: + """Return deduplicated advisory page URLs from the listing page.""" + slugs = re.findall(r"/about-us/security/advisories/(cve-[\d-]+)/", html) + seen = dict.fromkeys(slugs) + return [f"https://www.libreoffice.org/about-us/security/advisories/{slug}/" for slug in seen] + +def parse_advisory(html: str, url: str): + """Parse a LibreOffice individual advisory page; return None if advisory id is missing.""" + soup = BeautifulSoup(html, features="lxml") + body = soup.find("body") + body_id = body.get("id", "") if body else "" + if not body_id.startswith("cve-"): + return None + advisory_id = body_id.upper() -def parse_cve_advisory(data: dict, cve_id: str): - """Parse a CVE 5.0 JSON record from cveawg.mitre.org; return None if CVE ID is absent.""" - cve_metadata = data.get("cveMetadata") or {} - advisory_id = cve_metadata.get("cveId") or cve_id - if not advisory_id: + content = soup.select_one("section#content1 div.margin-20") + if not content: return None + text = content.get_text(separator="\n") + + title = _get_field(text, "Title") + date_str = _get_field(text, "Announced") + date_published = None - raw_date = cve_metadata.get("datePublished") or "" - if raw_date: + if date_str: date_published = dateparser.parse( - raw_date, + date_str, settings={"TIMEZONE": "UTC", "RETURN_AS_TIMEZONE_AWARE": True, "TO_TIMEZONE": "UTC"}, ) if date_published is None: - logger.warning("Could not parse date %r for %s", raw_date, advisory_id) - - cna = (data.get("containers") or {}).get("cna") or {} + logger.warning("Could not parse date %r for %s", date_str, advisory_id) - summary = "" - for desc in cna.get("descriptions") or []: - if desc.get("lang") in ("en", "en-US"): - summary = desc.get("value") or "" - break - - severities = [] - for metric in cna.get("metrics") or []: - for key, system in CVSS_KEY_MAP.items(): - cvss = metric.get(key) - if not cvss: - continue - vector = cvss.get("vectorString") or "" - score = cvss.get("baseScore") - if vector and score is not None: - severities.append( - VulnerabilitySeverity( - system=system, - value=str(score), - scoring_elements=vector, - ) - ) - break - - weaknesses = [] - for problem_type in cna.get("problemTypes") or []: - for desc in problem_type.get("descriptions") or []: - cwe_str = desc.get("cweId") or "" - if cwe_str.upper().startswith("CWE-"): - try: - weaknesses.append(get_cwe_id(cwe_str)) - except Exception: - pass - - advisory_url = ( - f"https://www.libreoffice.org/about-us/security/advisories/{advisory_id.lower()}/" + desc_m = re.search( + r"Description\s*\n?\s*:\s*\n+(.*?)(?=\nCredits\b|\nReferences\b|$)", + text, + re.DOTALL, ) + description = " ".join(desc_m.group(1).split()).strip() if desc_m else "" + references = [] - for ref in cna.get("references") or []: - url = ref.get("url") or "" - if url: - references.append(ReferenceV2(url=url)) + in_refs = False + for tag in content.descendants: + tag_name = getattr(tag, "name", None) + if tag_name == "strong" and "References" in tag.get_text(): + in_refs = True + if in_refs and tag_name == "a": + href = tag.get("href", "") + if href.startswith("http"): + references.append(ReferenceV2(url=href)) return AdvisoryDataV2( advisory_id=advisory_id, aliases=[], - summary=summary, + summary=description or title, affected_packages=[], references=references, date_published=date_published, - weaknesses=weaknesses, - severities=severities, - url=advisory_url, - original_advisory_text=json.dumps(data, indent=2, ensure_ascii=False), + weaknesses=[], + severities=[], + url=url, + original_advisory_text=str(content), ) + + +def _get_field(text: str, label: str) -> str: + m = re.search(rf"{re.escape(label)}\s*:\s*\n?\s*([^\n]+)", text) + return m.group(1).strip() if m else "" diff --git a/vulnerabilities/tests/pipelines/v2_importers/test_libreoffice_importer.py b/vulnerabilities/tests/pipelines/v2_importers/test_libreoffice_importer.py index 1dfc14871..bb74c95a2 100644 --- a/vulnerabilities/tests/pipelines/v2_importers/test_libreoffice_importer.py +++ b/vulnerabilities/tests/pipelines/v2_importers/test_libreoffice_importer.py @@ -7,128 +7,133 @@ # See https://aboutcode.org for more information about nexB OSS projects. # -import json -import os from unittest import TestCase from unittest.mock import MagicMock from unittest.mock import patch from vulnerabilities.pipelines.v2_importers.libreoffice_importer import LibreOfficeImporterPipeline -from vulnerabilities.pipelines.v2_importers.libreoffice_importer import parse_cve_advisory -from vulnerabilities.pipelines.v2_importers.libreoffice_importer import parse_cve_ids +from vulnerabilities.pipelines.v2_importers.libreoffice_importer import parse_advisory +from vulnerabilities.pipelines.v2_importers.libreoffice_importer import parse_advisory_urls -TEST_DATA = os.path.join(os.path.dirname(__file__), "..", "..", "test_data", "libreoffice") +LISTING_HTML = """ +

CVE-2025-1080 Macro URL

+

CVE-2023-2255 Macro URL

+

CVE-2023-4863 Heap buffer overflow

+""" -ADVISORY_HTML = """ -

Addressed in LibreOffice 24.8.5 and 25.2.1

- -

Addressed in LibreOffice 7.4.7

- -

Third Party Advisories

-
    -
  • CVE-2023-4863
  • +ADVISORY_HTML = """\ + +
    +
    + +

    CVE-2025-1080

    +

    Title: Macro URL arbitrary script execution

    +

    Announced: March 4, 2025

    Fixed in: LibreOffice 24.8.5 and 25.2.1

    Description:

    +

    LibreOffice supports Office URI Schemes to enable browser integration.

    +

    In the affected versions a link could call internal macros with arbitrary arguments.

    +

    Credits:

    +
    • Thanks to Amel Bouziane-Leblond for finding this issue.
    +

    References:

    CVE-2025-1080

    +
    +
    +
    + """ -def load_json(filename): - with open(os.path.join(TEST_DATA, filename), encoding="utf-8") as f: - return json.load(f) - - -class TestParseCveIds(TestCase): - def test_extracts_cve_ids_from_html(self): - cve_ids = parse_cve_ids(ADVISORY_HTML) - self.assertIn("CVE-2025-1080", cve_ids) - self.assertIn("CVE-2023-2255", cve_ids) - self.assertIn("CVE-2023-4863", cve_ids) - - def test_deduplicates_repeated_ids(self): - html = "CVE-2025-1080 ... CVE-2025-1080" - self.assertEqual(parse_cve_ids(html), ["CVE-2025-1080"]) +class TestParseAdvisoryUrls(TestCase): + def test_extracts_urls_from_html(self): + urls = parse_advisory_urls(LISTING_HTML) + self.assertIn( + "https://www.libreoffice.org/about-us/security/advisories/cve-2025-1080/", urls + ) + self.assertIn( + "https://www.libreoffice.org/about-us/security/advisories/cve-2023-2255/", urls + ) + self.assertIn( + "https://www.libreoffice.org/about-us/security/advisories/cve-2023-4863/", urls + ) + + def test_deduplicates_repeated_urls(self): + html = 'x' * 2 + urls = parse_advisory_urls(html) + self.assertEqual(len(urls), 1) def test_empty_html_returns_empty_list(self): - self.assertEqual(parse_cve_ids(""), []) + self.assertEqual(parse_advisory_urls(""), []) -class TestParseCveAdvisory(TestCase): - def test_cvss4_and_cwe(self): - data = load_json("cve_2025_1080.json") - advisory = parse_cve_advisory(data, "CVE-2025-1080") +class TestParseAdvisory(TestCase): + URL = "https://www.libreoffice.org/about-us/security/advisories/cve-2025-1080/" + + def test_parses_advisory_id(self): + advisory = parse_advisory(ADVISORY_HTML, self.URL) self.assertIsNotNone(advisory) self.assertEqual(advisory.advisory_id, "CVE-2025-1080") - self.assertEqual(advisory.aliases, []) - self.assertIn("macro", advisory.summary.lower()) - self.assertEqual(len(advisory.severities), 1) - self.assertEqual(advisory.severities[0].value, "7.2") - self.assertIn("CVSS:4.0/", advisory.severities[0].scoring_elements) - self.assertEqual(advisory.weaknesses, [20]) - self.assertIsNotNone(advisory.date_published) - self.assertIn("cve-2025-1080", advisory.url) - def test_no_cvss_has_empty_severities(self): - data = load_json("cve_2023_2255.json") - advisory = parse_cve_advisory(data, "CVE-2023-2255") - self.assertIsNotNone(advisory) - self.assertEqual(advisory.severities, []) + def test_parses_description_as_summary(self): + advisory = parse_advisory(ADVISORY_HTML, self.URL) + self.assertIn("Office URI Schemes", advisory.summary) - def test_cwe_264_extracted(self): - data = load_json("cve_2023_2255.json") - advisory = parse_cve_advisory(data, "CVE-2023-2255") - self.assertEqual(advisory.weaknesses, [264]) + def test_parses_date(self): + advisory = parse_advisory(ADVISORY_HTML, self.URL) + self.assertIsNotNone(advisory.date_published) + self.assertEqual(advisory.date_published.year, 2025) + self.assertEqual(advisory.date_published.month, 3) + self.assertEqual(advisory.date_published.day, 4) - def test_references_from_cna(self): - data = load_json("cve_2023_2255.json") - advisory = parse_cve_advisory(data, "CVE-2023-2255") + def test_extracts_reference_url(self): + advisory = parse_advisory(ADVISORY_HTML, self.URL) urls = [r.url for r in advisory.references] - self.assertIn("https://www.debian.org/security/2023/dsa-5415", urls) - self.assertIn("https://security.gentoo.org/glsa/202311-15", urls) - - def test_missing_cve_id_returns_none(self): - advisory = parse_cve_advisory({"cveMetadata": {"cveId": ""}, "containers": {}}, "") - self.assertIsNone(advisory) - - def test_original_advisory_text_is_json(self): - data = load_json("cve_2025_1080.json") - advisory = parse_cve_advisory(data, "CVE-2025-1080") - parsed = json.loads(advisory.original_advisory_text) - self.assertEqual(parsed["cveMetadata"]["cveId"], "CVE-2025-1080") - - def test_malformed_cwe_skipped(self): - data = load_json("cve_2025_1080.json") - data = json.loads(json.dumps(data)) - data["containers"]["cna"]["problemTypes"] = [ - {"descriptions": [{"cweId": "CWE-INVALID", "lang": "en", "type": "CWE"}]} - ] - advisory = parse_cve_advisory(data, "CVE-2025-1080") + self.assertIn("https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2025-1080", urls) + + def test_severities_and_weaknesses_are_empty(self): + advisory = parse_advisory(ADVISORY_HTML, self.URL) + self.assertEqual(advisory.severities, []) self.assertEqual(advisory.weaknesses, []) + def test_missing_body_id_returns_none(self): + html = ( + "" + "
    " + "" + ) + self.assertIsNone(parse_advisory(html, self.URL)) + + def test_missing_content_div_returns_none(self): + html = "
    " + self.assertIsNone(parse_advisory(html, self.URL)) + + def test_original_advisory_text_contains_advisory_id(self): + advisory = parse_advisory(ADVISORY_HTML, self.URL) + self.assertIn("CVE-2025-1080", advisory.original_advisory_text) + class TestLibreOfficeImporterPipeline(TestCase): @patch("vulnerabilities.pipelines.v2_importers.libreoffice_importer.requests.get") - def test_fetch_stores_cve_ids(self, mock_get): + def test_fetch_stores_advisory_urls(self, mock_get): resp = MagicMock() - resp.text = ADVISORY_HTML + resp.text = LISTING_HTML resp.raise_for_status.return_value = None mock_get.return_value = resp pipeline = LibreOfficeImporterPipeline() pipeline.fetch() - self.assertIn("CVE-2025-1080", pipeline.cve_ids) - self.assertIn("CVE-2023-2255", pipeline.cve_ids) + self.assertTrue(any("cve-2025-1080" in u for u in pipeline.advisory_urls)) + self.assertTrue(any("cve-2023-2255" in u for u in pipeline.advisory_urls)) @patch("vulnerabilities.pipelines.v2_importers.libreoffice_importer.requests.get") def test_collect_advisories_yields_advisory(self, mock_get): - cve_data = load_json("cve_2025_1080.json") resp = MagicMock() - resp.json.return_value = cve_data + resp.text = ADVISORY_HTML resp.raise_for_status.return_value = None mock_get.return_value = resp pipeline = LibreOfficeImporterPipeline() - pipeline.cve_ids = ["CVE-2025-1080"] + pipeline.advisory_urls = [ + "https://www.libreoffice.org/about-us/security/advisories/cve-2025-1080/" + ] advisories = list(pipeline.collect_advisories()) self.assertEqual(len(advisories), 1) self.assertEqual(advisories[0].advisory_id, "CVE-2025-1080") @@ -137,14 +142,19 @@ def test_collect_advisories_yields_advisory(self, mock_get): def test_collect_advisories_skips_on_http_error(self, mock_get): mock_get.side_effect = Exception("timeout") pipeline = LibreOfficeImporterPipeline() - pipeline.cve_ids = ["CVE-2025-1080"] + pipeline.advisory_urls = [ + "https://www.libreoffice.org/about-us/security/advisories/cve-2025-1080/" + ] logger_name = "vulnerabilities.pipelines.v2_importers.libreoffice_importer" with self.assertLogs(logger_name, level="ERROR") as cm: advisories = list(pipeline.collect_advisories()) self.assertEqual(advisories, []) - self.assertTrue(any("CVE-2025-1080" in msg for msg in cm.output)) + self.assertTrue(any("cve-2025-1080" in msg for msg in cm.output)) def test_advisories_count(self): pipeline = LibreOfficeImporterPipeline() - pipeline.cve_ids = ["CVE-2025-1080", "CVE-2023-2255"] + pipeline.advisory_urls = [ + "https://www.libreoffice.org/about-us/security/advisories/cve-2025-1080/", + "https://www.libreoffice.org/about-us/security/advisories/cve-2023-2255/", + ] self.assertEqual(pipeline.advisories_count(), 2) diff --git a/vulnerabilities/tests/test_data/libreoffice/cve_2023_2255.json b/vulnerabilities/tests/test_data/libreoffice/cve_2023_2255.json deleted file mode 100644 index ff0bfaf79..000000000 --- a/vulnerabilities/tests/test_data/libreoffice/cve_2023_2255.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "dataType": "CVE_RECORD", - "dataVersion": "5.1", - "cveMetadata": { - "state": "PUBLISHED", - "cveId": "CVE-2023-2255", - "assignerOrgId": "4fe7d05b-1353-44cc-8b7a-1e416936dff2", - "assignerShortName": "Document Fdn.", - "dateReserved": "2023-04-24T00:00:00.000Z", - "datePublished": "2023-05-25T00:00:00.000Z", - "dateUpdated": "2024-08-02T06:19:14.082Z" - }, - "containers": { - "cna": { - "title": "Remote documents loaded without prompt via IFrame", - "providerMetadata": { - "orgId": "4fe7d05b-1353-44cc-8b7a-1e416936dff2", - "shortName": "Document Fdn.", - "dateUpdated": "2023-11-26T09:06:16.295Z" - }, - "descriptions": [ - { - "lang": "en", - "value": "Improper access control in editor components of The Document Foundation LibreOffice allowed an attacker to craft a document that would cause external links to be loaded without prompt. In the affected versions of LibreOffice documents that used \"floating frames\" linked to external files, would load the contents of those frames without prompting the user for permission to do so. This issue affects: The Document Foundation LibreOffice 7.4 versions prior to 7.4.7; 7.5 versions prior to 7.5.3." - } - ], - "affected": [ - { - "vendor": "The Document Foundation", - "product": "LibreOffice", - "versions": [ - { - "version": "7.4", - "status": "affected", - "lessThan": "7.4.7", - "versionType": "custom" - }, - { - "version": "7.5", - "status": "affected", - "lessThan": "7.5.3", - "versionType": "custom" - } - ] - } - ], - "references": [ - { - "url": "https://www.libreoffice.org/about-us/security/advisories/CVE-2023-2255" - }, - { - "name": "DSA-5415", - "tags": ["vendor-advisory"], - "url": "https://www.debian.org/security/2023/dsa-5415" - }, - { - "name": "GLSA-202311-15", - "tags": ["vendor-advisory"], - "url": "https://security.gentoo.org/glsa/202311-15" - } - ], - "problemTypes": [ - { - "descriptions": [ - { - "type": "CWE", - "lang": "en", - "description": "CWE-264 Permissions, Privileges, and Access Controls", - "cweId": "CWE-264" - } - ] - } - ] - } - } -} diff --git a/vulnerabilities/tests/test_data/libreoffice/cve_2025_1080.json b/vulnerabilities/tests/test_data/libreoffice/cve_2025_1080.json deleted file mode 100644 index b9ee8fc05..000000000 --- a/vulnerabilities/tests/test_data/libreoffice/cve_2025_1080.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "dataType": "CVE_RECORD", - "dataVersion": "5.2", - "cveMetadata": { - "cveId": "CVE-2025-1080", - "assignerOrgId": "4fe7d05b-1353-44cc-8b7a-1e416936dff2", - "state": "PUBLISHED", - "assignerShortName": "Document Fdn.", - "dateReserved": "2025-02-06T13:14:08.175Z", - "datePublished": "2025-03-04T20:04:10.946Z", - "dateUpdated": "2025-11-03T19:35:13.950Z" - }, - "containers": { - "cna": { - "affected": [ - { - "defaultStatus": "unknown", - "product": "LibreOffice", - "vendor": "The Document Foundation", - "versions": [ - { - "lessThan": "< 24.8.5", - "status": "affected", - "version": "24.8", - "versionType": "24.8 series" - }, - { - "lessThan": "< 25.2.1", - "status": "affected", - "version": "25.2", - "versionType": "25.2 series" - } - ] - } - ], - "credits": [ - { - "lang": "en", - "type": "finder", - "value": "Thanks to Amel Bouziane-Leblond for finding and reporting this issue." - } - ], - "datePublic": "2025-03-04T19:00:00.000Z", - "descriptions": [ - { - "lang": "en", - "value": "LibreOffice supports Office URI Schemes to enable browser integration of LibreOffice with MS SharePoint server. An additional scheme 'vnd.libreoffice.command' specific to LibreOffice was added. In the affected versions of LibreOffice a link in a browser using that scheme could be constructed with an embedded inner URL that when passed to LibreOffice could call internal macros with arbitrary arguments.\nThis issue affects LibreOffice: from 24.8 before < 24.8.5, from 25.2 before < 25.2.1." - } - ], - "metrics": [ - { - "cvssV4_0": { - "attackComplexity": "HIGH", - "attackRequirements": "NONE", - "attackVector": "LOCAL", - "baseScore": 7.2, - "baseSeverity": "HIGH", - "privilegesRequired": "NONE", - "userInteraction": "PASSIVE", - "vectorString": "CVSS:4.0/AV:L/AC:H/AT:N/PR:N/UI:P/VC:H/VI:L/VA:H/SC:H/SI:H/SA:H", - "version": "4.0" - }, - "format": "CVSS" - } - ], - "problemTypes": [ - { - "descriptions": [ - { - "cweId": "CWE-20", - "description": "CWE-20 Improper Input Validation", - "lang": "en", - "type": "CWE" - } - ] - } - ], - "providerMetadata": { - "orgId": "4fe7d05b-1353-44cc-8b7a-1e416936dff2", - "shortName": "Document Fdn.", - "dateUpdated": "2025-03-04T20:04:10.946Z" - }, - "references": [ - { - "url": "https://www.libreoffice.org/about-us/security/advisories/cve-2025-1080" - } - ], - "title": "Macro URL arbitrary script execution" - } - } -} From 86ffa97b59a824a519d3b3f6faf84c90c93c705a Mon Sep 17 00:00:00 2001 From: Anmol Vats Date: Sat, 14 Mar 2026 16:20:23 -0400 Subject: [PATCH 5/5] Trim test HTML fixtures to minimum needed Signed-off-by: Anmol Vats --- .../v2_importers/test_libreoffice_importer.py | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/vulnerabilities/tests/pipelines/v2_importers/test_libreoffice_importer.py b/vulnerabilities/tests/pipelines/v2_importers/test_libreoffice_importer.py index bb74c95a2..070381287 100644 --- a/vulnerabilities/tests/pipelines/v2_importers/test_libreoffice_importer.py +++ b/vulnerabilities/tests/pipelines/v2_importers/test_libreoffice_importer.py @@ -16,30 +16,22 @@ from vulnerabilities.pipelines.v2_importers.libreoffice_importer import parse_advisory_urls LISTING_HTML = """ -

    CVE-2025-1080 Macro URL

    -

    CVE-2023-2255 Macro URL

    -

    CVE-2023-4863 Heap buffer overflow

    +CVE-2025-1080 +CVE-2023-2255 +CVE-2023-4863 """ ADVISORY_HTML = """\ -
    +
    - -

    CVE-2025-1080

    Title: Macro URL arbitrary script execution

    -

    Announced: March 4, 2025

    Fixed in: LibreOffice 24.8.5 and 25.2.1

    Description:

    +

    Announced: March 4, 2025
    Description:

    LibreOffice supports Office URI Schemes to enable browser integration.

    -

    In the affected versions a link could call internal macros with arbitrary arguments.

    Credits:

    -
    • Thanks to Amel Bouziane-Leblond for finding this issue.
    -

    References:

    CVE-2025-1080

    +

    References:
    CVE-2025-1080

    -
    -
    + """