diff --git a/vulnerabilities/importers/__init__.py b/vulnerabilities/importers/__init__.py index add6967f8..55c3cef4c 100644 --- a/vulnerabilities/importers/__init__.py +++ b/vulnerabilities/importers/__init__.py @@ -24,6 +24,7 @@ from vulnerabilities.importers import nginx from vulnerabilities.importers import npm from vulnerabilities.importers import nvd +from vulnerabilities.importers import openssf from vulnerabilities.importers import openssl from vulnerabilities.importers import oss_fuzz from vulnerabilities.importers import postgresql @@ -67,6 +68,7 @@ fireeye.FireyeImporter, apache_kafka.ApacheKafkaImporter, oss_fuzz.OSSFuzzImporter, + openssf.OpenSSFImporter, ] IMPORTERS_REGISTRY = {x.qualified_name: x for x in IMPORTERS_REGISTRY} diff --git a/vulnerabilities/importers/openssf.py b/vulnerabilities/importers/openssf.py new file mode 100644 index 000000000..ce8e79564 --- /dev/null +++ b/vulnerabilities/importers/openssf.py @@ -0,0 +1,55 @@ +# +# 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/nexB/vulnerablecode for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# +import json +import logging +from pathlib import Path +from typing import Iterable + +from vulnerabilities.importer import AdvisoryData +from vulnerabilities.importer import Importer +from vulnerabilities.importers.osv import parse_advisory_data +from vulnerabilities.utils import get_advisory_url + +logger = logging.getLogger(__name__) + + +class OpenSSFImporter(Importer): + license_url = "https://github.com/ossf/malicious-packages/blob/main/LICENSE" + spdx_license_expression = "CC-BY-4.0" + url = "git+https://github.com/ossf/malicious-packages" + importer_name = "OpenSSF Malacious Packages Importer" + + def advisory_data(self) -> Iterable[AdvisoryData]: + try: + supported_ecosystems = ["crates.io", "npm", "pypi", "rubygems"] + self.clone(repo_url=self.url) + base_path = Path(self.vcs_response.dest_dir) + + for supported_ecosystem in supported_ecosystems: + path = base_path / "osv" / "malicious" / supported_ecosystem + + for file in path.glob("**/*.json"): + try: + with open(file) as f: + json_data = json.load(f) + advisory_url = get_advisory_url( + file=file, + base_path=base_path, + url="https://github.com/ossf/malicious-packages/blob/main", + ) + yield parse_advisory_data( + json_data, + supported_ecosystem=supported_ecosystem, + advisory_url=advisory_url, + ) + except Exception as e: + logger.debug(f"Filepath {file} threw an Exception {type(e).__name__} {e!r}") + finally: + if self.vcs_response: + self.vcs_response.delete() diff --git a/vulnerabilities/improvers/__init__.py b/vulnerabilities/improvers/__init__.py index 9880bf9ee..f31fd7f7d 100644 --- a/vulnerabilities/improvers/__init__.py +++ b/vulnerabilities/improvers/__init__.py @@ -24,6 +24,7 @@ valid_versions.DebianOvalImprover, valid_versions.UbuntuOvalImprover, valid_versions.OSSFuzzImprover, + valid_versions.OpenSSFImprover, vulnerability_status.VulnerabilityStatusImprover, ] diff --git a/vulnerabilities/improvers/valid_versions.py b/vulnerabilities/improvers/valid_versions.py index cada4bbb6..2658d0650 100644 --- a/vulnerabilities/improvers/valid_versions.py +++ b/vulnerabilities/improvers/valid_versions.py @@ -36,6 +36,7 @@ from vulnerabilities.importers.istio import IstioImporter from vulnerabilities.importers.nginx import NginxImporter from vulnerabilities.importers.npm import NpmImporter +from vulnerabilities.importers.openssf import OpenSSFImporter from vulnerabilities.importers.oss_fuzz import OSSFuzzImporter from vulnerabilities.importers.ubuntu import UbuntuImporter from vulnerabilities.improver import MAX_CONFIDENCE @@ -460,3 +461,8 @@ class UbuntuOvalImprover(ValidVersionImprover): class OSSFuzzImprover(ValidVersionImprover): importer = OSSFuzzImporter ignorable_versions = [] + + +class OpenSSFImprover(ValidVersionImprover): + importer = OpenSSFImporter + ignorable_versions = [] diff --git a/vulnerabilities/tests/test_data/openssf/openssf-data1.json b/vulnerabilities/tests/test_data/openssf/openssf-data1.json new file mode 100644 index 000000000..2783149f7 --- /dev/null +++ b/vulnerabilities/tests/test_data/openssf/openssf-data1.json @@ -0,0 +1,43 @@ +{ + "modified": "2023-07-19T06:30:24Z", + "published": "2023-07-19T06:30:24Z", + "schema_version": "1.5.0", + "id": "MAL-2023-1426", + "summary": "Malicious code in google-apis-androidpublisher_v2 (RubyGems)", + "details": "\n---\n_-= Per source details. Do not edit below this line.=-_\n\n## Source: ossf-package-analysis (715b9e91530380e15e848bc0374f342584cdd61853308582683eb214e0da9927)\nThe OpenSSF Package Analysis project identified 'google-apis-androidpublisher_v2' @ 0.0 (rubygems) as malicious.\n\nIt is considered malicious because:\n- The package communicates with a domain associated with malicious activity.\n", + "affected": [ + { + "package": { + "ecosystem": "RubyGems", + "name": "google-apis-androidpublisher_v2" + }, + "versions": [ + "0.0" + ] + } + ], + "credits": [ + { + "name": "OpenSSF: Package Analysis", + "type": "FINDER", + "contact": [ + "https://github.com/ossf/package-analysis", + "https://openssf.slack.com/channels/package_analysis" + ] + } + ], + "database_specific": { + "malicious-packages-origins": [ + { + "import_time": "2023-08-10T06:16:36.932649705Z", + "modified_time": "2023-07-19T06:30:24.337173817Z", + "sha256": "715b9e91530380e15e848bc0374f342584cdd61853308582683eb214e0da9927", + "source": "ossf-package-analysis", + "versions": [ + "0.0" + ] + } + ] + } + } + \ No newline at end of file diff --git a/vulnerabilities/tests/test_data/openssf/openssf-data1.json-expected.json b/vulnerabilities/tests/test_data/openssf/openssf-data1.json-expected.json new file mode 100644 index 000000000..68b825700 --- /dev/null +++ b/vulnerabilities/tests/test_data/openssf/openssf-data1.json-expected.json @@ -0,0 +1,9 @@ +{ + "aliases": ["MAL-2023-1426"], + "summary": "Malicious code in google-apis-androidpublisher_v2 (RubyGems)\n---\n_-= Per source details. Do not edit below this line.=-_\n\n## Source: ossf-package-analysis (715b9e91530380e15e848bc0374f342584cdd61853308582683eb214e0da9927)\nThe OpenSSF Package Analysis project identified 'google-apis-androidpublisher_v2' @ 0.0 (rubygems) as malicious.\n\nIt is considered malicious because:\n- The package communicates with a domain associated with malicious activity.", + "affected_packages": [], + "references": [], + "date_published": "2023-07-19T06:30:24+00:00", + "weaknesses": [], + "url": "http://test.com" +} diff --git a/vulnerabilities/tests/test_data/openssf/openssf-data2.json b/vulnerabilities/tests/test_data/openssf/openssf-data2.json new file mode 100644 index 000000000..73de15818 --- /dev/null +++ b/vulnerabilities/tests/test_data/openssf/openssf-data2.json @@ -0,0 +1,43 @@ +{ + "modified": "2023-07-12T14:53:05Z", + "published": "2023-07-12T14:53:05Z", + "schema_version": "1.5.0", + "id": "MAL-2023-1077", + "summary": "Malicious code in 1password-postman-integration (npm)", + "details": "\n---\n_-= Per source details. Do not edit below this line.=-_\n\n## Source: ossf-package-analysis (93be26c7bf7ce939a783f5c98c26ecd585430bdb1c993cf3f78dd213a316a058)\nThe OpenSSF Package Analysis project identified '1password-postman-integration' @ 1.0.0 (npm) as malicious.\n\nIt is considered malicious because:\n- The package communicates with a domain associated with malicious activity.\n", + "affected": [ + { + "package": { + "ecosystem": "npm", + "name": "1password-postman-integration" + }, + "versions": [ + "1.0.0" + ] + } + ], + "credits": [ + { + "name": "OpenSSF: Package Analysis", + "type": "FINDER", + "contact": [ + "https://github.com/ossf/package-analysis", + "https://openssf.slack.com/channels/package_analysis" + ] + } + ], + "database_specific": { + "malicious-packages-origins": [ + { + "import_time": "2023-08-10T06:16:31.337614734Z", + "modified_time": "2023-07-12T14:53:05.954866979Z", + "sha256": "93be26c7bf7ce939a783f5c98c26ecd585430bdb1c993cf3f78dd213a316a058", + "source": "ossf-package-analysis", + "versions": [ + "1.0.0" + ] + } + ] + } + } + \ No newline at end of file diff --git a/vulnerabilities/tests/test_data/openssf/openssf-data2.json-expected.json b/vulnerabilities/tests/test_data/openssf/openssf-data2.json-expected.json new file mode 100644 index 000000000..9c1e73747 --- /dev/null +++ b/vulnerabilities/tests/test_data/openssf/openssf-data2.json-expected.json @@ -0,0 +1,9 @@ +{ + "aliases": ["MAL-2023-1077"], + "summary": "Malicious code in 1password-postman-integration (npm)\n---\n_-= Per source details. Do not edit below this line.=-_\n\n## Source: ossf-package-analysis (93be26c7bf7ce939a783f5c98c26ecd585430bdb1c993cf3f78dd213a316a058)\nThe OpenSSF Package Analysis project identified '1password-postman-integration' @ 1.0.0 (npm) as malicious.\n\nIt is considered malicious because:\n- The package communicates with a domain associated with malicious activity.", + "affected_packages": [], + "references": [], + "date_published": "2023-07-12T14:53:05+00:00", + "weaknesses": [], + "url": "http://test.com" +} diff --git a/vulnerabilities/tests/test_openssf.py b/vulnerabilities/tests/test_openssf.py new file mode 100644 index 000000000..0eab9945a --- /dev/null +++ b/vulnerabilities/tests/test_openssf.py @@ -0,0 +1,39 @@ +# +# 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/nexB/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 vulnerabilities.importers.osv import parse_advisory_data +from vulnerabilities.tests import util_tests + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +TEST_DATA = os.path.join(BASE_DIR, "test_data/openssf") + + +class TestOpenSSFImporter(TestCase): + def test_to_advisories1(self): + with open(os.path.join(TEST_DATA, "openssf-data1.json")) as f: + mock_response = json.load(f) + expected_file = os.path.join(TEST_DATA, "openssf-data1.json-expected.json") + imported_data = parse_advisory_data( + mock_response, "openssf", advisory_url="http://test.com" + ) + result = imported_data.to_dict() + util_tests.check_results_against_json(result, expected_file) + + def test_to_advisories2(self): + with open(os.path.join(TEST_DATA, "openssf-data2.json")) as f: + mock_response = json.load(f) + expected_file = os.path.join(TEST_DATA, "openssf-data2.json-expected.json") + imported_data = parse_advisory_data( + mock_response, "openssf", advisory_url="http://test.com" + ) + result = imported_data.to_dict() + util_tests.check_results_against_json(result, expected_file)