From d87b983fe0c5c1706df5dfa58c62386330613a54 Mon Sep 17 00:00:00 2001 From: ziadhany Date: Tue, 9 Apr 2024 17:14:36 +0200 Subject: [PATCH 1/2] Add support for vex vulnerableCode_url Fix Vex export test Remove get_export_vex_url func from Package model Add support for vulnerability encoder Add a test for get_references_and_rating Rename UI VEX fields Fix Export VEX View Rename VEX model Add VEX Form Fix UI bug and add the model to dataspace Add basic VEX mapping for CycloneDX Automate VEX creation Add the basic Vex Form Add the skeleton view and form for vex Add Product VEX List view and update tab_vex Add the basic for vex model Add the basic skeleton for vex export Signed-off-by: ziadhany --- component_catalog/models.py | 3 + .../component_catalog/package_vex_form.html | 4 + component_catalog/views.py | 4 +- dejacode_toolkit/tests/test_vex.py | 214 ++++ .../tests/testfiles/cyclonedx_vul1.json | 151 +++ .../tests/testfiles/cyclonedx_vul2.json | 287 +++++ .../tests/testfiles/vcio_vul1.json | 147 +++ .../tests/testfiles/vcio_vul2.json | 1015 +++++++++++++++++ dejacode_toolkit/tests/testfiles/vex1.json | 158 +++ dejacode_toolkit/vex.py | 290 +++++ dejacode_toolkit/vulnerablecode.py | 1 - dje/tests/test_permissions.py | 1 + dje/views.py | 43 + product_portfolio/forms.py | 28 + .../migrations/0005_productpackagevex.py | 125 ++ product_portfolio/models.py | 91 ++ .../product_portfolio/product_details.html | 6 + .../product_portfolio/tabs/tab_vex.html | 58 + product_portfolio/urls.py | 13 + product_portfolio/views.py | 65 ++ 20 files changed, 2702 insertions(+), 2 deletions(-) create mode 100644 component_catalog/templates/component_catalog/package_vex_form.html create mode 100644 dejacode_toolkit/tests/test_vex.py create mode 100644 dejacode_toolkit/tests/testfiles/cyclonedx_vul1.json create mode 100644 dejacode_toolkit/tests/testfiles/cyclonedx_vul2.json create mode 100644 dejacode_toolkit/tests/testfiles/vcio_vul1.json create mode 100644 dejacode_toolkit/tests/testfiles/vcio_vul2.json create mode 100644 dejacode_toolkit/tests/testfiles/vex1.json create mode 100644 dejacode_toolkit/vex.py create mode 100644 product_portfolio/migrations/0005_productpackagevex.py create mode 100644 product_portfolio/templates/product_portfolio/tabs/tab_vex.html diff --git a/component_catalog/models.py b/component_catalog/models.py index 18dda9c..ab19c8a 100644 --- a/component_catalog/models.py +++ b/component_catalog/models.py @@ -745,6 +745,9 @@ def get_export_spdx_url(self): def get_export_cyclonedx_url(self): return self.get_url("export_cyclonedx") + def get_export_vex_url(self): + return self.get_url("export_vex") + def get_about_files(self): """ Return the list of all AboutCode files from all the Packages diff --git a/component_catalog/templates/component_catalog/package_vex_form.html b/component_catalog/templates/component_catalog/package_vex_form.html new file mode 100644 index 0000000..75bca7b --- /dev/null +++ b/component_catalog/templates/component_catalog/package_vex_form.html @@ -0,0 +1,4 @@ +{% extends "object_form.html" %} +{% block javascripts %} + {{ block.super }} +{% endblock %} \ No newline at end of file diff --git a/component_catalog/views.py b/component_catalog/views.py index 5df07c8..f98429f 100644 --- a/component_catalog/views.py +++ b/component_catalog/views.py @@ -75,6 +75,7 @@ from dejacode_toolkit.scancodeio import ScanCodeIO from dejacode_toolkit.scancodeio import get_package_download_url from dejacode_toolkit.scancodeio import get_scan_results_as_file_url +from dejacode_toolkit.vex import create_auto_vex from dejacode_toolkit.vulnerablecode import VulnerableCode from dje import tasks from dje.client_data import add_client_data @@ -857,7 +858,6 @@ def get_vulnerabilities_tab_fields(self, vulnerabilities): vulnerability_fields = self.get_vulnerability_fields(vulnerability, dataspace) fields.extend(vulnerability_fields) vulnerabilities_count += 1 - return fields, vulnerabilities_count def get_context_data(self, **kwargs): @@ -1452,6 +1452,8 @@ def get_vulnerabilities_tab_fields(self, vulnerabilities): fields = [] vulnerabilities_count = 0 + create_auto_vex(self.object, vulnerabilities) + for entry in vulnerabilities: unresolved = entry.get("affected_by_vulnerabilities", []) for vulnerability in unresolved: diff --git a/dejacode_toolkit/tests/test_vex.py b/dejacode_toolkit/tests/test_vex.py new file mode 100644 index 0000000..fbb2ae7 --- /dev/null +++ b/dejacode_toolkit/tests/test_vex.py @@ -0,0 +1,214 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# DejaCode is a trademark of nexB Inc. +# SPDX-License-Identifier: AGPL-3.0-only +# See https://github.com/nexB/dejacode for support or download. +# See https://aboutcode.org for more information about AboutCode FOSS projects. +# + + +import json +import os + +from django.contrib.auth import get_user_model +from django.test import TestCase + +from cyclonedx.output.json import SchemaVersion1Dot4 +from serializable import _SerializableJsonEncoder + +from component_catalog.models import Package +from dejacode_toolkit import vex +from dejacode_toolkit.vex import VEXCycloneDX +from dejacode_toolkit.vex import vulnerability_format_vcic_to_cyclonedx +from dje.models import Dataspace +from dje.tests import create_user +from product_portfolio.models import Product +from product_portfolio.models import ProductPackage +from product_portfolio.models import ProductPackageVEX + +User = get_user_model() + + +class VEXTestCase(TestCase): + def setUp(self): + self.nexb_dataspace = Dataspace.objects.create(name="nexB") + self.nexb_user = User.objects.create_superuser( + "nexb_user", "test@test.com", "t3st", self.nexb_dataspace + ) + self.basic_user = create_user("basic_user", self.nexb_dataspace) + self.product1 = Product.objects.create( + name="Product1 With Space", version="1.0", dataspace=self.nexb_dataspace + ) + self.package1 = Package.objects.create(filename="package1", dataspace=self.nexb_dataspace) + + self.productpacakge1 = ProductPackage.objects.create( + product=self.product1, package=self.package1, dataspace=self.nexb_dataspace + ) + self.vex1 = ProductPackageVEX.objects.create( + dataspace=self.productpacakge1.dataspace, + productpackage=self.productpacakge1, + vulnerability_id="VCID-111c-u9bh-aaac", + responses=["CNF"], + justification="CNP", + detail=( + "Automated dataflow analysis and manual " + "code review indicates that the vulnerable code is not reachable," + " either directly or indirectly." + ), + ) + + def test_create_auto_vex1(self): + vulnerabilities = [ + { + "affected_by_vulnerabilities": [ + { + "url": "http://public.vulnerablecode.io/api/vulnerabilities/121332", + "vulnerability_id": "VCID-111c-u9bh-aaac", + } + ] + }, + { + "affected_by_vulnerabilities": [ + { + "url": "https://public.vulnerablecode.io/api/vulnerabilities/121331", + "vulnerability_id": "VCID-uxf9-7c97-aaaj", + } + ] + }, + ] + assert ProductPackageVEX.objects.count() == 1 + vex.create_auto_vex(self.package1, vulnerabilities) + assert ProductPackageVEX.objects.count() == 2 + + # run create_auto_vex agian and make sure that the databse ignore errors + vex.create_auto_vex(self.package1, vulnerabilities) + assert ProductPackageVEX.objects.count() == 2 + + def test_create_auto_vex2(self): + # duplicated vulnerability + vulnerabilities = [ + { + "affected_by_vulnerabilities": [ + { + "url": "http://public.vulnerablecode.io/api/vulnerabilities/121332", + "vulnerability_id": "VCID-111c-u9bh-aaac", + } + ] + }, + { + "affected_by_vulnerabilities": [ + { + "url": "http://public.vulnerablecode.io/api/vulnerabilities/121332", + "vulnerability_id": "VCID-111c-u9bh-aaac", + } + ] + }, + ] + assert ProductPackageVEX.objects.count() == 1 + vex.create_auto_vex(self.package1, vulnerabilities) + assert ProductPackageVEX.objects.count() == 1 + + def test_get_references_and_rating(self): + references = [ + { + "reference_url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136", + "reference_id": "CVE-2017-1000136", + "scores": [ + { + "value": "5.0", + "scoring_system": "cvssv2", + "scoring_elements": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + }, + { + "value": "5.3", + "scoring_system": "cvssv3", + "scoring_elements": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + }, + ], + "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136", + } + ] + ref, rate = vex.get_references_and_rating(references) + + assert json.dumps( + ref, + cls=_SerializableJsonEncoder, + view_=SchemaVersion1Dot4, + ) == json.dumps( + [ + { + "id": "CVE-2017-1000136", + "source": {"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136"}, + } + ] + ) + + assert json.dumps( + rate, + cls=_SerializableJsonEncoder, + view_=SchemaVersion1Dot4, + ) == json.dumps( + [ + { + "method": "CVSSv2", + "score": "5.0", + "source": {"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136"}, + "vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + }, + { + "method": "CVSSv3", + "score": "5.3", + "source": {"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136"}, + "vector": "AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + }, + ] + ) + + def test_vulnerability_format_vcic_to_cyclonedx1(self): + vul_data_path = os.path.join(os.path.dirname(__file__), "testfiles", "vcio_vul1.json") + with open(vul_data_path) as f: + vcio_vulnerability = json.load(f) + + vulnerability = vulnerability_format_vcic_to_cyclonedx(vcio_vulnerability, self.vex1) + + cyclonedx_vul_data_path = os.path.join( + os.path.dirname(__file__), "testfiles", "cyclonedx_vul1.json" + ) + with open(cyclonedx_vul_data_path) as f: + cyclonedx_vul = json.load(f) + + assert json.dumps( + vulnerability, + cls=_SerializableJsonEncoder, + view_=SchemaVersion1Dot4, + ) == json.dumps(cyclonedx_vul) + + def test_vulnerability_format_vcic_to_cyclonedx2(self): + vul_data_path = os.path.join(os.path.dirname(__file__), "testfiles", "vcio_vul2.json") + with open(vul_data_path) as f: + vcio_vulnerability = json.load(f) + + vulnerability = vulnerability_format_vcic_to_cyclonedx(vcio_vulnerability, self.vex1) + + cyclonedx_vul_data_path = os.path.join( + os.path.dirname(__file__), "testfiles", "cyclonedx_vul2.json" + ) + with open(cyclonedx_vul_data_path) as f: + cyclonedx_vul = json.load(f) + + assert json.dumps( + vulnerability, + cls=_SerializableJsonEncoder, + view_=SchemaVersion1Dot4, + ) == json.dumps(cyclonedx_vul) + + def test_vex_cyclonedx_export(self): + vul_data_path = os.path.join(os.path.dirname(__file__), "testfiles", "vcio_vul1.json") + with open(vul_data_path) as f: + vcio_vulnerability = json.load(f) + + vex_data_path = os.path.join(os.path.dirname(__file__), "testfiles", "vex1.json") + with open(vex_data_path) as f: + vex_data = json.load(f) + + assert VEXCycloneDX().export([vcio_vulnerability], [self.vex1]) == json.dumps(vex_data) diff --git a/dejacode_toolkit/tests/testfiles/cyclonedx_vul1.json b/dejacode_toolkit/tests/testfiles/cyclonedx_vul1.json new file mode 100644 index 0000000..90d981f --- /dev/null +++ b/dejacode_toolkit/tests/testfiles/cyclonedx_vul1.json @@ -0,0 +1,151 @@ +{ + "analysis": { + "detail": "Automated dataflow analysis and manual code review indicates that the vulnerable code is not reachable, either directly or indirectly.", + "justification": "code_not_present", + "response": [ + "can_not_fix" + ] + }, + "cwes": [ + 613 + ], + "description": "Mahara 1.8 before 1.8.6 and 1.9 before 1.9.4 and 1.10 before 1.10.1 and 15.04 before 15.04.0 are vulnerable to old sessions not being invalidated after a password change.", + "id": "VCID-111c-u9bh-aaac", + "ratings": [ + { + "method": "CVSSv2", + "score": "4.3", + "source": { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136" + }, + "vector": "AV:N/AC:M/Au:N/C:N/I:P/A:N" + }, + { + "method": "CVSSv3", + "score": "6.5", + "source": { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136" + }, + "vector": "AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N" + } + ], + "references": [ + { + "id": "", + "source": { + "url": "https://bugs.launchpad.net/mahara/+bug/1363873" + } + }, + { + "id": "CVE-2017-1000136", + "source": { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.10.0:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.10.0:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.10:rc1:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.10:rc1:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.0:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.0:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.1:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.1:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.2:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.2:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.3:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.3:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.4:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.4:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.5:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.5:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8:rc1:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8:rc1:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8:rc2:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8:rc2:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9.0:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.0:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9.1:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.1:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9.2:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.2:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9.3:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.3:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9:rc1:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9:rc1:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:15.04:rc1:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:15.04:rc1:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:15.04:rc2:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:15.04:rc2:*:*:*:*:*:*" + } + } + ], + "source": { + "url": "http://public.vulnerablecode.io/api/vulnerabilities/121332" + } +} \ No newline at end of file diff --git a/dejacode_toolkit/tests/testfiles/cyclonedx_vul2.json b/dejacode_toolkit/tests/testfiles/cyclonedx_vul2.json new file mode 100644 index 0000000..2115620 --- /dev/null +++ b/dejacode_toolkit/tests/testfiles/cyclonedx_vul2.json @@ -0,0 +1,287 @@ +{ + "affects": [ + { + "ref": "urn:cdx:serialNumber/version#bom-ref", + "versions": [ + { + "status": "affected", + "version": "0.1" + }, + { + "status": "affected", + "version": "0.10" + }, + { + "status": "affected", + "version": "0.10.1" + }, + { + "status": "affected", + "version": "0.11" + }, + { + "status": "affected", + "version": "0.11.1" + }, + { + "status": "affected", + "version": "0.12" + }, + { + "status": "affected", + "version": "0.12.1" + }, + { + "status": "affected", + "version": "0.12.2" + }, + { + "status": "affected", + "version": "0.12.3" + }, + { + "status": "affected", + "version": "0.12.4" + }, + { + "status": "affected", + "version": "0.12.5" + }, + { + "status": "affected", + "version": "0.2" + }, + { + "status": "affected", + "version": "0.3" + }, + { + "status": "affected", + "version": "0.3.1" + }, + { + "status": "affected", + "version": "0.4" + }, + { + "status": "affected", + "version": "0.5" + }, + { + "status": "affected", + "version": "0.5.1" + }, + { + "status": "affected", + "version": "0.5.2" + }, + { + "status": "affected", + "version": "0.6" + }, + { + "status": "affected", + "version": "0.6.1" + }, + { + "status": "affected", + "version": "0.7" + }, + { + "status": "affected", + "version": "0.7.1" + }, + { + "status": "affected", + "version": "0.7.2" + }, + { + "status": "affected", + "version": "0.8" + }, + { + "status": "affected", + "version": "0.8.1" + }, + { + "status": "affected", + "version": "0.9" + }, + { + "status": "affected", + "version": "1.0" + }, + { + "status": "affected", + "version": "1.0.1" + }, + { + "status": "affected", + "version": "1.0.2" + }, + { + "status": "affected", + "version": "1.0.2-3" + }, + { + "status": "affected", + "version": "1.0.3" + }, + { + "status": "affected", + "version": "1.0.4" + }, + { + "status": "affected", + "version": "1.1.0" + }, + { + "status": "affected", + "version": "1.1.1" + }, + { + "status": "affected", + "version": "1.1.2" + }, + { + "status": "affected", + "version": "1.1.2-2" + }, + { + "status": "affected", + "version": "1.1.3" + }, + { + "status": "affected", + "version": "1.1.4" + }, + { + "status": "affected", + "version": "1:0.10.1-7" + }, + { + "status": "affected", + "version": "1:1.0.2-8" + }, + { + "status": "affected", + "version": "1:1.1.2-6" + }, + { + "status": "affected", + "version": "1:2.0.1-3" + }, + { + "status": "affected", + "version": "2.0.0" + }, + { + "status": "affected", + "version": "2.0.0rc1" + }, + { + "status": "affected", + "version": "2.0.0rc2" + }, + { + "status": "affected", + "version": "2.0.1" + }, + { + "status": "affected", + "version": "2.0.2" + }, + { + "status": "affected", + "version": "2.0.3" + }, + { + "status": "affected", + "version": "2.1.0" + }, + { + "status": "affected", + "version": "2.1.1" + }, + { + "status": "affected", + "version": "2.1.2" + }, + { + "status": "affected", + "version": "2.1.3" + }, + { + "status": "affected", + "version": "2.2.0" + }, + { + "status": "affected", + "version": "2.2.1" + }, + { + "status": "affected", + "version": "2.2.2" + }, + { + "status": "affected", + "version": "2.2.2-2" + }, + { + "status": "affected", + "version": "2.2.3" + }, + { + "status": "affected", + "version": "2.2.4" + }, + { + "status": "affected", + "version": "2.3.0" + }, + { + "status": "affected", + "version": "2.3.1" + }, + { + "status": "affected", + "version": "2:2.0.1-4.el9" + } + ] + } + ], + "analysis": { + "detail": "Automated dataflow analysis and manual code review indicates that the vulnerable code is not reachable, either directly or indirectly.", + "justification": "code_not_present", + "response": [ + "can_not_fix" + ] + }, + "cwes": [ + 488, + 539 + ], + "description": "Flask vulnerable to possible disclosure of permanent session cookie due to missing Vary: Cookie header", + "id": "VCID-111c-u9bh-aaac", + "ratings": [ + { + "method": "CVSSv3", + "score": "7.5", + "source": { + "url": "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2023-30861.json" + }, + "vector": "AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N" + } + ], + "references": [ + { + "id": "", + "source": { + "url": "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2023-30861.json" + } + } + ], + "source": { + "url": "http://public.vulnerablecode.io/api/vulnerabilities/457133?format=json" + } +} \ No newline at end of file diff --git a/dejacode_toolkit/tests/testfiles/vcio_vul1.json b/dejacode_toolkit/tests/testfiles/vcio_vul1.json new file mode 100644 index 0000000..e672006 --- /dev/null +++ b/dejacode_toolkit/tests/testfiles/vcio_vul1.json @@ -0,0 +1,147 @@ +{ + "url": "http://public.vulnerablecode.io/api/vulnerabilities/121332", + "vulnerability_id": "VCID-111c-u9bh-aaac", + "summary": "Mahara 1.8 before 1.8.6 and 1.9 before 1.9.4 and 1.10 before 1.10.1 and 15.04 before 15.04.0 are vulnerable to old sessions not being invalidated after a password change.", + "aliases": [ + { + "alias": "CVE-2017-1000136" + } + ], + "fixed_packages": [], + "affected_packages": [], + "references": [ + { + "reference_url": "https://bugs.launchpad.net/mahara/+bug/1363873", + "reference_id": "", + "scores": [], + "url": "https://bugs.launchpad.net/mahara/+bug/1363873" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.10.0:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.10.0:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.10.0:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.10:rc1:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.10:rc1:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.10:rc1:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:15.04:rc1:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:15.04:rc1:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:15.04:rc1:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:15.04:rc2:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:15.04:rc2:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:15.04:rc2:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.0:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.8.0:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.0:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.1:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.8.1:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.1:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.2:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.8.2:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.2:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.3:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.8.3:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.3:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.4:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.8.4:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.4:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.5:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.8.5:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.5:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8:rc1:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.8:rc1:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8:rc1:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8:rc2:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.8:rc2:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8:rc2:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.0:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.9.0:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.0:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.1:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.9.1:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.1:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.2:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.9.2:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.2:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.3:*:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.9.3:*:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.3:*:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9:rc1:*:*:*:*:*:*", + "reference_id": "cpe:2.3:a:mahara:mahara:1.9:rc1:*:*:*:*:*:*", + "scores": [], + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9:rc1:*:*:*:*:*:*" + }, + { + "reference_url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136", + "reference_id": "CVE-2017-1000136", + "scores": [ + { + "value": "4.3", + "scoring_system": "cvssv2", + "scoring_elements": "AV:N/AC:M/Au:N/C:N/I:P/A:N" + }, + { + "value": "6.5", + "scoring_system": "cvssv3", + "scoring_elements": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N" + } + ], + "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136" + } + ], + "weaknesses": [ + { + "cwe_id": 613, + "name": "Insufficient Session Expiration", + "description": "According to WASC, Insufficient Session Expiration is when a web site permits an attacker to reuse old session credentials or session IDs for authorization." + } + ], + "resource_url": "http://public.vulnerablecode.io/vulnerabilities/VCID-111c-u9bh-aaac" +} \ No newline at end of file diff --git a/dejacode_toolkit/tests/testfiles/vcio_vul2.json b/dejacode_toolkit/tests/testfiles/vcio_vul2.json new file mode 100644 index 0000000..7bfeef1 --- /dev/null +++ b/dejacode_toolkit/tests/testfiles/vcio_vul2.json @@ -0,0 +1,1015 @@ +{ + "url": "http://public.vulnerablecode.io/api/vulnerabilities/457133?format=json", + "vulnerability_id": "VCID-111c-u9bh-aaac", + "summary": "Flask vulnerable to possible disclosure of permanent session cookie due to missing Vary: Cookie header", + "aliases": [ + { + "alias": "CVE-2023-30861" + }, + { + "alias": "GHSA-m2qf-hxjv-5gpq" + }, + { + "alias": "PYSEC-2023-62" + } + ], + "fixed_packages": [ + { + "url": "http://public.vulnerablecode.io/api/packages/625069?format=json", + "purl": "pkg:deb/debian/flask@1.0.2-3?distro=trixie", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@1.0.2-3%3Fdistro=trixie" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/625070?format=json", + "purl": "pkg:deb/debian/flask@1.0.2-3%2Bdeb10u1?distro=trixie", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@1.0.2-3%252Bdeb10u1%3Fdistro=trixie" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/625067?format=json", + "purl": "pkg:deb/debian/flask@1.1.2-2?distro=trixie", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@1.1.2-2%3Fdistro=trixie" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/728786?format=json", + "purl": "pkg:deb/debian/flask@1.1.2-2%2Bdeb11u1", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@1.1.2-2%252Bdeb11u1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/625068?format=json", + "purl": "pkg:deb/debian/flask@1.1.2-2%2Bdeb11u1?distro=trixie", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@1.1.2-2%252Bdeb11u1%3Fdistro=trixie" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/597957?format=json", + "purl": "pkg:deb/debian/flask@2.2.2-3?distro=sid", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@2.2.2-3%3Fdistro=sid" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/625066?format=json", + "purl": "pkg:deb/debian/flask@2.2.2-3?distro=trixie", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@2.2.2-3%3Fdistro=trixie" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/639141?format=json", + "purl": "pkg:deb/debian/flask@2.2.5-1?distro=trixie", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@2.2.5-1%3Fdistro=trixie" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/808362?format=json", + "purl": "pkg:deb/debian/flask@3.0.2-1?distro=trixie", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@3.0.2-1%3Fdistro=trixie" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/815308?format=json", + "purl": "pkg:deb/debian/flask@3.0.3-1?distro=trixie", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@3.0.3-1%3Fdistro=trixie" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/642099?format=json", + "purl": "pkg:pypi/flask@2.2.5", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.2.5" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606889?format=json", + "purl": "pkg:pypi/flask@2.3.2", + "is_vulnerable": false, + "affected_by_vulnerabilities": [], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.3.2" + } + ], + "affected_packages": [ + { + "url": "http://public.vulnerablecode.io/api/packages/591807?format=json", + "purl": "pkg:deb/debian/flask@1.0.2-3", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@1.0.2-3" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/366672?format=json", + "purl": "pkg:deb/debian/flask@1.0.2-3?distro=sid", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@1.0.2-3%3Fdistro=sid" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/728785?format=json", + "purl": "pkg:deb/debian/flask@1.1.2-2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@1.1.2-2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/366671?format=json", + "purl": "pkg:deb/debian/flask@1.1.2-2?distro=sid", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@1.1.2-2%3Fdistro=sid" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/559244?format=json", + "purl": "pkg:deb/debian/flask@2.2.2-2?distro=sid", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:deb/debian/flask@2.2.2-2%3Fdistro=sid" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110820?format=json", + "purl": "pkg:pypi/flask@0.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-a1md-ney4-aaac" + }, + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110836?format=json", + "purl": "pkg:pypi/flask@0.10", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.10" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110837?format=json", + "purl": "pkg:pypi/flask@0.10.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.10.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110838?format=json", + "purl": "pkg:pypi/flask@0.11", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.11" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110839?format=json", + "purl": "pkg:pypi/flask@0.11.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.11.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110840?format=json", + "purl": "pkg:pypi/flask@0.12", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.12" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110841?format=json", + "purl": "pkg:pypi/flask@0.12.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.12.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110842?format=json", + "purl": "pkg:pypi/flask@0.12.2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.12.2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110843?format=json", + "purl": "pkg:pypi/flask@0.12.3", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.12.3" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/112303?format=json", + "purl": "pkg:pypi/flask@0.12.4", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.12.4" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/112304?format=json", + "purl": "pkg:pypi/flask@0.12.5", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.12.5" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110821?format=json", + "purl": "pkg:pypi/flask@0.2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-a1md-ney4-aaac" + }, + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110822?format=json", + "purl": "pkg:pypi/flask@0.3", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-a1md-ney4-aaac" + }, + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.3" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110823?format=json", + "purl": "pkg:pypi/flask@0.3.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-a1md-ney4-aaac" + }, + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.3.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110824?format=json", + "purl": "pkg:pypi/flask@0.4", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-a1md-ney4-aaac" + }, + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.4" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110825?format=json", + "purl": "pkg:pypi/flask@0.5", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-a1md-ney4-aaac" + }, + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.5" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110826?format=json", + "purl": "pkg:pypi/flask@0.5.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-a1md-ney4-aaac" + }, + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.5.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110827?format=json", + "purl": "pkg:pypi/flask@0.5.2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-a1md-ney4-aaac" + }, + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.5.2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110828?format=json", + "purl": "pkg:pypi/flask@0.6", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-a1md-ney4-aaac" + }, + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.6" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110829?format=json", + "purl": "pkg:pypi/flask@0.6.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.6.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110830?format=json", + "purl": "pkg:pypi/flask@0.7", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.7" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110831?format=json", + "purl": "pkg:pypi/flask@0.7.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.7.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110832?format=json", + "purl": "pkg:pypi/flask@0.7.2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.7.2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110833?format=json", + "purl": "pkg:pypi/flask@0.8", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.8" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110834?format=json", + "purl": "pkg:pypi/flask@0.8.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.8.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/110835?format=json", + "purl": "pkg:pypi/flask@0.9", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-es3g-2k93-aaaf" + }, + { + "vulnerability": "VCID-w41d-hjxu-aaas" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@0.9" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/112305?format=json", + "purl": "pkg:pypi/flask@1.0", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.0" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606890?format=json", + "purl": "pkg:pypi/flask@1.0.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.0.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606891?format=json", + "purl": "pkg:pypi/flask@1.0.2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.0.2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606892?format=json", + "purl": "pkg:pypi/flask@1.0.3", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.0.3" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606893?format=json", + "purl": "pkg:pypi/flask@1.0.4", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.0.4" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606894?format=json", + "purl": "pkg:pypi/flask@1.1.0", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.1.0" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606895?format=json", + "purl": "pkg:pypi/flask@1.1.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.1.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606896?format=json", + "purl": "pkg:pypi/flask@1.1.2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.1.2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606897?format=json", + "purl": "pkg:pypi/flask@1.1.3", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.1.3" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606898?format=json", + "purl": "pkg:pypi/flask@1.1.4", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@1.1.4" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606901?format=json", + "purl": "pkg:pypi/flask@2.0.0", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.0.0" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606899?format=json", + "purl": "pkg:pypi/flask@2.0.0rc1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.0.0rc1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606900?format=json", + "purl": "pkg:pypi/flask@2.0.0rc2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.0.0rc2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606902?format=json", + "purl": "pkg:pypi/flask@2.0.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.0.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606903?format=json", + "purl": "pkg:pypi/flask@2.0.2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.0.2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606904?format=json", + "purl": "pkg:pypi/flask@2.0.3", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.0.3" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606905?format=json", + "purl": "pkg:pypi/flask@2.1.0", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.1.0" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606906?format=json", + "purl": "pkg:pypi/flask@2.1.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.1.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606907?format=json", + "purl": "pkg:pypi/flask@2.1.2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.1.2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606908?format=json", + "purl": "pkg:pypi/flask@2.1.3", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.1.3" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606909?format=json", + "purl": "pkg:pypi/flask@2.2.0", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.2.0" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606910?format=json", + "purl": "pkg:pypi/flask@2.2.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.2.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606911?format=json", + "purl": "pkg:pypi/flask@2.2.2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.2.2" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606912?format=json", + "purl": "pkg:pypi/flask@2.2.3", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.2.3" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606913?format=json", + "purl": "pkg:pypi/flask@2.2.4", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.2.4" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606887?format=json", + "purl": "pkg:pypi/flask@2.3.0", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.3.0" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/606888?format=json", + "purl": "pkg:pypi/flask@2.3.1", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:pypi/flask@2.3.1" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/686987?format=json", + "purl": "pkg:rpm/redhat/python-flask@1:0.10.1-7?arch=el7_9", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:rpm/redhat/python-flask@1:0.10.1-7%3Farch=el7_9" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/686989?format=json", + "purl": "pkg:rpm/redhat/python-flask@1:1.0.2-8?arch=el8ost", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:rpm/redhat/python-flask@1:1.0.2-8%3Farch=el8ost" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/686988?format=json", + "purl": "pkg:rpm/redhat/python-flask@1:1.1.2-6?arch=el8", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:rpm/redhat/python-flask@1:1.1.2-6%3Farch=el8" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/686990?format=json", + "purl": "pkg:rpm/redhat/python-flask@1:1.1.2-6?arch=el9ost", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:rpm/redhat/python-flask@1:1.1.2-6%3Farch=el9ost" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/686985?format=json", + "purl": "pkg:rpm/redhat/python-flask@1:2.0.1-3?arch=el9", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:rpm/redhat/python-flask@1:2.0.1-3%3Farch=el9" + }, + { + "url": "http://public.vulnerablecode.io/api/packages/686986?format=json", + "purl": "pkg:rpm/redhat/python-flask@2:2.0.1-4.el9?arch=2", + "is_vulnerable": true, + "affected_by_vulnerabilities": [ + { + "vulnerability": "VCID-ccmw-8ht8-aaaq" + }, + { + "vulnerability": "VCID-kuxv-41y8-aaaj" + }, + { + "vulnerability": "VCID-z6fe-2j8a-aaak" + } + ], + "resource_url": "http://public.vulnerablecode.io/packages/pkg:rpm/redhat/python-flask@2:2.0.1-4.el9%3Farch=2" + } + ], + "references": [ + { + "reference_url": "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2023-30861.json", + "reference_id": "", + "scores": [ + { + "value": "7.5", + "scoring_system": "cvssv3", + "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N" + } + ], + "url": "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2023-30861.json" + } + ], + "weaknesses": [ + { + "cwe_id": 539, + "name": "Use of Persistent Cookies Containing Sensitive Information", + "description": "The web application uses persistent cookies, but the cookies contain sensitive information." + }, + { + "cwe_id": 488, + "name": "Exposure of Data Element to Wrong Session", + "description": "The product does not sufficiently enforce boundaries between the states of different sessions, causing data to be provided to, or used by, the wrong session." + } + ], + "resource_url": "http://public.vulnerablecode.io/vulnerabilities/VCID-z6fe-2j8a-aaak" +} \ No newline at end of file diff --git a/dejacode_toolkit/tests/testfiles/vex1.json b/dejacode_toolkit/tests/testfiles/vex1.json new file mode 100644 index 0000000..7e43f35 --- /dev/null +++ b/dejacode_toolkit/tests/testfiles/vex1.json @@ -0,0 +1,158 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.4", + "version": 1, + "vulnerabilities": [ + { + "analysis": { + "detail": "Automated dataflow analysis and manual code review indicates that the vulnerable code is not reachable, either directly or indirectly.", + "justification": "code_not_present", + "response": [ + "can_not_fix" + ] + }, + "cwes": [ + 613 + ], + "description": "Mahara 1.8 before 1.8.6 and 1.9 before 1.9.4 and 1.10 before 1.10.1 and 15.04 before 15.04.0 are vulnerable to old sessions not being invalidated after a password change.", + "id": "VCID-111c-u9bh-aaac", + "ratings": [ + { + "method": "CVSSv2", + "score": "4.3", + "source": { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136" + }, + "vector": "AV:N/AC:M/Au:N/C:N/I:P/A:N" + }, + { + "method": "CVSSv3", + "score": "6.5", + "source": { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136" + }, + "vector": "AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N" + } + ], + "references": [ + { + "id": "", + "source": { + "url": "https://bugs.launchpad.net/mahara/+bug/1363873" + } + }, + { + "id": "CVE-2017-1000136", + "source": { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.10.0:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.10.0:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.10:rc1:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.10:rc1:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.0:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.0:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.1:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.1:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.2:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.2:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.3:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.3:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.4:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.4:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8.5:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8.5:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8:rc1:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8:rc1:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.8:rc2:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.8:rc2:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9.0:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.0:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9.1:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.1:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9.2:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.2:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9.3:*:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9.3:*:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:1.9:rc1:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:1.9:rc1:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:15.04:rc1:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:15.04:rc1:*:*:*:*:*:*" + } + }, + { + "id": "cpe:2.3:a:mahara:mahara:15.04:rc2:*:*:*:*:*:*", + "source": { + "url": "https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:2.3:a:mahara:mahara:15.04:rc2:*:*:*:*:*:*" + } + } + ], + "source": { + "url": "http://public.vulnerablecode.io/api/vulnerabilities/121332" + } + } + ] +} \ No newline at end of file diff --git a/dejacode_toolkit/vex.py b/dejacode_toolkit/vex.py new file mode 100644 index 0000000..1125448 --- /dev/null +++ b/dejacode_toolkit/vex.py @@ -0,0 +1,290 @@ +import json +from dataclasses import dataclass +from dataclasses import field +from typing import List +from typing import Literal + +from cyclonedx.model.vulnerability import BomTarget +from cyclonedx.model.vulnerability import BomTargetVersionRange +from cyclonedx.model.vulnerability import ImpactAnalysisAffectedStatus +from cyclonedx.model.vulnerability import ImpactAnalysisJustification +from cyclonedx.model.vulnerability import ImpactAnalysisResponse +from cyclonedx.model.vulnerability import ImpactAnalysisState +from cyclonedx.model.vulnerability import Vulnerability +from cyclonedx.model.vulnerability import VulnerabilityAnalysis +from cyclonedx.model.vulnerability import VulnerabilityRating +from cyclonedx.model.vulnerability import VulnerabilityReference +from cyclonedx.model.vulnerability import VulnerabilityScoreSource +from cyclonedx.model.vulnerability import VulnerabilitySeverity +from cyclonedx.model.vulnerability import VulnerabilitySource +from cyclonedx.output.json import SchemaVersion1Dot4 +from cyclonedx.output.json import SchemaVersion1Dot5 +from cyclonedx.output.json import SchemaVersion1Dot6 +from packageurl import PackageURL +from serializable import _SerializableJsonEncoder + +from product_portfolio.models import ProductPackage +from product_portfolio.models import ProductPackageVEX + +# from cyclonedx.model.vulnerability import VulnerabilityCredits +# from cyclonedx.model.vulnerability import VulnerabilityAdvisory + +SCORING_SYSTEMS_CYCLONEDX = { + "cvssv2": VulnerabilityScoreSource.CVSS_V2, + "cvssv3": VulnerabilityScoreSource.CVSS_V3, + "cvssv3.1": VulnerabilityScoreSource.CVSS_V3_1, + "cvssv4": VulnerabilityScoreSource.CVSS_V4, + "owasp": VulnerabilityScoreSource.OWASP, + "ssvc": VulnerabilityScoreSource.SSVC, + "other": VulnerabilityScoreSource.OTHER, +} + +GENERIC_SEVERITIES_VALUE_CYCLONEDX = { + "none": VulnerabilitySeverity.NONE, + "info": VulnerabilitySeverity.INFO, + "low": VulnerabilitySeverity.LOW, + "meduim": VulnerabilitySeverity.MEDIUM, + "high": VulnerabilitySeverity.HIGH, + "critical": VulnerabilitySeverity.CRITICAL, + "unknow": VulnerabilitySeverity.UNKNOWN, +} + +STATUS_VEX_CYCLONEDX = { + True: ImpactAnalysisAffectedStatus.AFFECTED, + False: ImpactAnalysisAffectedStatus.UNAFFECTED, + # "unknow": ImpactAnalysisAffectedStatus.UNKNOWN, default +} + +STATE_VEX_CYCLONEDX = { + "R": ImpactAnalysisState.RESOLVED, + "RWP": ImpactAnalysisState.RESOLVED_WITH_PEDIGREE, + "E": ImpactAnalysisState.EXPLOITABLE, + "IT": ImpactAnalysisState.IN_TRIAGE, + "FP": ImpactAnalysisState.FALSE_POSITIVE, + "NA": ImpactAnalysisState.NOT_AFFECTED, +} + +JUSTIFICATION_VEX_CYCLONEDX = { + "CNP": ImpactAnalysisJustification.CODE_NOT_PRESENT, + "CNR": ImpactAnalysisJustification.CODE_NOT_REACHABLE, + "PP": ImpactAnalysisJustification.PROTECTED_AT_PERIMITER, + "PR": ImpactAnalysisJustification.PROTECTED_AT_RUNTIME, + "PC": ImpactAnalysisJustification.PROTECTED_BY_COMPILER, + "PMC": ImpactAnalysisJustification.PROTECTED_BY_MITIGATING_CONTROL, + "RC": ImpactAnalysisJustification.REQUIRES_CONFIGURATION, + "RD": ImpactAnalysisJustification.REQUIRES_DEPENDENCY, + "RE": ImpactAnalysisJustification.REQUIRES_ENVIRONMENT, +} + +RESPONSES_VEX_CYCLONEDX = { + "CNF": ImpactAnalysisResponse.CAN_NOT_FIX, + "RB": ImpactAnalysisResponse.ROLLBACK, + "U": ImpactAnalysisResponse.UPDATE, + "WNF": ImpactAnalysisResponse.WILL_NOT_FIX, + "WA": ImpactAnalysisResponse.WORKAROUND_AVAILABLE, +} + + +def create_auto_vex(package, vulnerabilities): + # automatically create a VEX for each product package that has a vulnerability + vex_objects = [] + vulnerability_ids = [] + for entry in vulnerabilities: + unresolved = entry.get("affected_by_vulnerabilities", []) + for vulnerability in unresolved: + vulnerability_id = vulnerability.get("vulnerability_id") + if vulnerability_id: + vulnerability_ids.append(vulnerability_id) + + productpackages = ProductPackage.objects.filter(package=package) + for productpackage in productpackages: + for vulnerability_id in vulnerability_ids: + vex_objects.append( + ProductPackageVEX( + dataspace=productpackage.dataspace, + productpackage=productpackage, + vulnerability_id=vulnerability_id, + ) + ) + + ProductPackageVEX.objects.bulk_create(vex_objects, ignore_conflicts=True) + + +def vulnerability_format_vcic_to_cyclonedx(vcio_vulnerability, vex: ProductPackageVEX): + """Change the VCIO format of the vulnerability to CycloneDX and add the vex vulnerability""" + vulnerability_source = VulnerabilitySource( + url=vcio_vulnerability.get("url"), + ) + + references = vcio_vulnerability.get("references") or [] + vulnerability_reference, vulnerability_ratings = get_references_and_rating(references) + + state = STATE_VEX_CYCLONEDX.get(vex.state) + justification = JUSTIFICATION_VEX_CYCLONEDX.get(vex.justification) + + responses = [] + for vex_res in vex.responses or []: + response = RESPONSES_VEX_CYCLONEDX.get(vex_res) + if response: + responses.append(response) + + vulnerability_analysis = VulnerabilityAnalysis( + state=state, + responses=responses, + justification=justification, + detail=vex.detail, + ) + + # vulnerability_advisory = VulnerabilityAdvisory() # ignore + # vulnerability_credits = VulnerabilityCredits() # ignore + + # property = Property() # ignore + # tool = Tool() # ignore + + bom_targets = [] + versions = [] + for affected_package in vcio_vulnerability.get("affected_packages") or []: + is_vulnerable = affected_package.get("is_vulnerable") + status = STATUS_VEX_CYCLONEDX.get(is_vulnerable, ImpactAnalysisAffectedStatus.UNKNOWN) + purl_string = affected_package.get("purl") + + vul_purl = None + if purl_string: + vul_purl = PackageURL.from_string(purl_string) + + versions.append(BomTargetVersionRange(version=vul_purl.version, status=status)) + + if versions: + bom_target = BomTarget(ref="urn:cdx:serialNumber/version#bom-ref", versions=versions) + bom_targets.append(bom_target) + + weaknesses = vcio_vulnerability.get("weaknesses") or [] + cwes = get_cwes(weaknesses) + + vulnerability = Vulnerability( + # bom_ref="", + id=vcio_vulnerability.get("vulnerability_id"), + source=vulnerability_source, + references=vulnerability_reference, + ratings=vulnerability_ratings, + cwes=cwes, + description=vcio_vulnerability.get("summary") or "", + # detail=detail, + # recommendation="", + # advisories=advisories, + # created=created, + # published=published, + # updated=updated, + # credits=credits, + # tools=tools, + # properties=properties, + # Deprecated Parameters kept for backwards compatibility + analysis=vulnerability_analysis, + affects=bom_targets, + # source_name=source_name, + # source_url=source_url, + # recommendations=recommendations, + ) + return vulnerability + + +def get_cwes(weaknesses): + """ + Get the list of cwes number using vulnerability weaknesses + >>> get_cwes([{"cwe_id": 613,"name": "..."}]}) + [613] + >>> get_cwes([{"cwe_id": 613,"name": "..."}, {"cwe_id": 79,"name": "..."}]) + [613, 79] + >>> get_cwes([]) + [] + """ + cwes = [] + for weakness in weaknesses: + cwe_id = weakness.get("cwe_id") + if cwe_id: + cwes.append(cwe_id) + return cwes + + +def is_float(s): + try: + float(s) + return True + except ValueError: + return False + + +def get_references_and_rating(references): + cyclonedx_references, cyclonedx_rating = [], [] + for ref in references: + source = VulnerabilitySource(url=ref.get("url")) + cyclonedx_references.append( + VulnerabilityReference(id=ref.get("reference_id"), source=source) + ) + + for score in ref.get("scores") or []: + vul_source = VulnerabilitySource(url=ref.get("reference_url")) + + value = score.get("value") + scoring_system = score.get("scoring_system") + vector = score.get("scoring_elements") + + vul_severity = None + if scoring_system in SCORING_SYSTEMS_CYCLONEDX: + vul_method = SCORING_SYSTEMS_CYCLONEDX.get(scoring_system) + + if value.lower() in GENERIC_SEVERITIES_VALUE_CYCLONEDX: + vul_method = SCORING_SYSTEMS_CYCLONEDX.get(scoring_system) + vul_severity = GENERIC_SEVERITIES_VALUE_CYCLONEDX.get(value.lower()) + + vul_rating = VulnerabilityRating( + source=vul_source, + score=float(value) if is_float(value) else None, + severity=vul_severity, + method=vul_method, + vector=vector, + ) + cyclonedx_rating.append(vul_rating) + + return cyclonedx_references, cyclonedx_rating + + +@dataclass +class VEXCycloneDX: + """https://github.com/CycloneDX/bom-examples/tree/master/VEX""" + + vulnerabilities: List[Vulnerability] = field(default_factory=lambda: []) + bomFormat: str = "CycloneDX" + specVersion: Literal["1.4", "1.5", "1.6"] = "1.4" + version: int = 1 + + def export(self, vcio_vulnerabilities, vexs): + self.vulnerabilities = [] + schema_version = { + "1.4": SchemaVersion1Dot4, + "1.5": SchemaVersion1Dot5, + "1.6": SchemaVersion1Dot6, + }.get(self.specVersion) + + for vcio_vulnerability, vex in zip(vcio_vulnerabilities, vexs): + cyclonedx_vulnerability = vulnerability_format_vcic_to_cyclonedx( + vcio_vulnerability, vex + ) + self.vulnerabilities.append( + json.loads( + json.dumps( + cyclonedx_vulnerability, + cls=_SerializableJsonEncoder, + view_=schema_version, + ) + ) + ) + + return json.dumps( + { + "bomFormat": self.bomFormat, + "specVersion": self.specVersion, + "version": self.version, + "vulnerabilities": self.vulnerabilities, + } + ) diff --git a/dejacode_toolkit/vulnerablecode.py b/dejacode_toolkit/vulnerablecode.py index 7cefb24..0b2ad68 100644 --- a/dejacode_toolkit/vulnerablecode.py +++ b/dejacode_toolkit/vulnerablecode.py @@ -5,7 +5,6 @@ # See https://github.com/nexB/dejacode for support or download. # See https://aboutcode.org for more information about AboutCode FOSS projects. # - from django.core.cache import caches from dejacode_toolkit import BaseService diff --git a/dje/tests/test_permissions.py b/dje/tests/test_permissions.py index 640e792..f9de0b9 100644 --- a/dje/tests/test_permissions.py +++ b/dje/tests/test_permissions.py @@ -140,6 +140,7 @@ def test_permissions_get_all_tabsets(self): "activity", "imports", "history", + "vex", ], } diff --git a/dje/views.py b/dje/views.py index 8438d20..4d34115 100644 --- a/dje/views.py +++ b/dje/views.py @@ -75,6 +75,7 @@ from component_catalog.license_expression_dje import get_license_objects from dejacode_toolkit.purldb import PurlDB from dejacode_toolkit.scancodeio import ScanCodeIO +from dejacode_toolkit.vex import VEXCycloneDX from dejacode_toolkit.vulnerablecode import VulnerableCode from dje import outputs from dje.copier import COPY_DEFAULT_EXCLUDE @@ -116,6 +117,7 @@ from dje.utils import has_permission from dje.utils import queryset_to_changelist_href from dje.utils import str_to_id_list +from product_portfolio.models import ProductPackageVEX License = apps.get_model("license_library", "License") Request = apps.get_model("workflow", "Request") @@ -2364,3 +2366,44 @@ def get(self, request, *args, **kwargs): filename=outputs.get_cyclonedx_filename(instance), content_type="application/json", ) + + +class ExportVEXView( + LoginRequiredMixin, + DataspaceScopeMixin, + GetDataspacedObjectMixin, + BaseDetailView, +): + def get(self, request, *args, **kwargs): + product = self.get_object() + filename = f"dejacode_{product.dataspace.name}_{product._meta.model_name}_vex.json" + vulnerablecode = VulnerableCode(self.request.user) + + if not vulnerablecode.is_configured(): + raise Http404 + + vexs = ProductPackageVEX.objects.filter( + productpackage__product=product, dataspace=product.dataspace, ignored=False + ) + + vulnerabilities = [] + for vex in vexs: + url = f"{vulnerablecode.api_url}vulnerabilities/" + vulnerability = vulnerablecode.get_vulnerabilities( + url, field_name="vulnerability_id", field_value=vex.vulnerability_id + ) + vulnerabilities.append(vulnerability[0]) + + if not vulnerabilities: + raise Http404 + + spec_version = self.request.GET.get("spec_version") + vex = VEXCycloneDX(specVersion=spec_version) + vex_json = vex.export(vulnerabilities, vexs) + response = FileResponse( + vex_json, + filename=filename, + content_type="application/json", + ) + response["Content-Disposition"] = f'attachment; filename="{filename}"' + return response diff --git a/product_portfolio/forms.py b/product_portfolio/forms.py index 29d3681..f5507a5 100644 --- a/product_portfolio/forms.py +++ b/product_portfolio/forms.py @@ -51,6 +51,7 @@ from product_portfolio.models import Product from product_portfolio.models import ProductComponent from product_portfolio.models import ProductPackage +from product_portfolio.models import ProductPackageVEX from product_portfolio.models import ScanCodeProject @@ -944,3 +945,30 @@ def submit(self, product, user): scancodeproject_uuid=scancode_project.uuid, ) ) + + +class PackageVEXForm(DataspacedModelForm): + class Meta: + model = ProductPackageVEX + fields = ["state", "responses", "justification", "detail", "ignored"] + widgets = { + "detail": forms.Textarea(attrs={"rows": 3}), + } + + RESPONSES_VEX_CHOICES = [ + ("CNF", "CAN_NOT_FIX"), + ("RB", "ROLLBACK"), + ("U", "UPDATE"), + ("WNF", "WILL_NOT_FIX"), + ("WA", "WORKAROUND_AVAILABLE"), + ] + + responses = forms.MultipleChoiceField( + label="Response:", + choices=RESPONSES_VEX_CHOICES, + widget=forms.CheckboxSelectMultiple, + required=False, + ) + + helper = FormHelper() + helper.add_input(Submit("submit", "Submit", css_class="btn-success")) diff --git a/product_portfolio/migrations/0005_productpackagevex.py b/product_portfolio/migrations/0005_productpackagevex.py new file mode 100644 index 0000000..fa2e264 --- /dev/null +++ b/product_portfolio/migrations/0005_productpackagevex.py @@ -0,0 +1,125 @@ +# Generated by Django 5.0.3 on 2024-05-04 15:51 + +import django.contrib.postgres.fields +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("dje", "0002_initial"), + ("product_portfolio", "0004_alter_scancodeproject_type"), + ] + + operations = [ + migrations.CreateModel( + name="ProductPackageVEX", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "uuid", + models.UUIDField( + default=uuid.uuid4, editable=False, verbose_name="UUID" + ), + ), + ( + "vulnerability_id", + models.CharField(help_text="VCID-xxxx-xxxx-xxxx", max_length=50), + ), + ( + "state", + models.CharField( + choices=[ + ("R", "RESOLVED"), + ("RWP", "RESOLVED_WITH_PEDIGREE"), + ("E", "EXPLOITABLE"), + ("IT", "IN_TRIAGE"), + ("FP", "FALSE_POSITIVE"), + ("NA", "NOT_AFFECTED"), + ], + help_text="The rationale of why the impact analysis state was asserted.", + max_length=3, + ), + ), + ( + "responses", + django.contrib.postgres.fields.ArrayField( + base_field=models.CharField( + blank=True, + choices=[ + ("CNF", "CAN_NOT_FIX"), + ("RB", "ROLLBACK"), + ("U", "UPDATE"), + ("WNF", "WILL_NOT_FIX"), + ("WA", "WORKAROUND_AVAILABLE"), + ], + max_length=3, + ), + help_text="A response to the vulnerability by the manufacturer, supplier, or project responsible for the affected component or service", + max_length=20, + null=True, + size=None, + ), + ), + ( + "justification", + models.CharField( + choices=[ + ("CNP", "CODE_NOT_PRESENT"), + ("CNR", "CODE_NOT_REACHABLE"), + ("PP", "PROTECTED_AT_PERIMITER"), + ("PR", "PROTECTED_AT_RUNTIME"), + ("PC", "PROTECTED_BY_COMPILER"), + ("PMC", "PROTECTED_BY_MITIGATING_CONTROL"), + ("RC", "REQUIRES_CONFIGURATION"), + ("RD", "REQUIRES_DEPENDENCY"), + ("RE", "REQUIRES_ENVIRONMENT"), + ], + help_text="The rationale of why the impact analysis state was asserted.", + max_length=3, + ), + ), + ( + "detail", + models.CharField(help_text="Additional notes to explain the VEX."), + ), + ( + "ignored", + models.BooleanField( + default=False, help_text="Ignore VEX for this vulnerability" + ), + ), + ( + "dataspace", + models.ForeignKey( + editable=False, + help_text="A Dataspace is an independent, exclusive set of DejaCode data, which can be either nexB master reference data or installation-specific data.", + on_delete=django.db.models.deletion.PROTECT, + to="dje.dataspace", + ), + ), + ( + "productpackage", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="product_portfolio.productpackage", + ), + ), + ], + options={ + "unique_together": { + ("productpackage", "vulnerability_id", "dataspace") + }, + }, + ), + ] diff --git a/product_portfolio/models.py b/product_portfolio/models.py index e33d83f..f95e902 100644 --- a/product_portfolio/models.py +++ b/product_portfolio/models.py @@ -10,6 +10,7 @@ from pathlib import Path from django.conf import settings +from django.contrib.postgres.fields import ArrayField from django.core.exceptions import ValidationError from django.db import models from django.utils.functional import cached_property @@ -1247,3 +1248,93 @@ def notify(self, verb, description): recipient=self.created_by, description=description, ) + + +class ProductPackageVEX(DataspacedModel): + """ + Vulnerability Exploitability eXchange for + Single Product, Single Version, Single/* Vulnerability, Single Status + ( every Vulnerability and package should have a VEX ) + """ + + productpackage = models.ForeignKey(ProductPackage, on_delete=models.CASCADE) + vulnerability_id = models.CharField( + help_text="VCID-xxxx-xxxx-xxxx", max_length=50, null=False, blank=False + ) + + STATE_VEX_CHOICES = [ + ("R", "RESOLVED"), + ("RWP", "RESOLVED_WITH_PEDIGREE"), + ("E", "EXPLOITABLE"), + ("IT", "IN_TRIAGE"), + ("FP", "FALSE_POSITIVE"), + ("NA", "NOT_AFFECTED"), + ] + + state = models.CharField( + max_length=3, + help_text="The rationale of why the impact analysis state was asserted.", + choices=STATE_VEX_CHOICES, + ) + + RESPONSES_VEX_CHOICES = [ + ("CNF", "CAN_NOT_FIX"), + ("RB", "ROLLBACK"), + ("U", "UPDATE"), + ("WNF", "WILL_NOT_FIX"), + ("WA", "WORKAROUND_AVAILABLE"), + ] + + responses = ArrayField( + models.CharField( + max_length=3, + choices=RESPONSES_VEX_CHOICES, + blank=True, + ), + help_text="A response to the vulnerability by the manufacturer, " + "supplier, or project responsible for the affected component or service", + null=True, + max_length=20, + ) + + JUSTIFICATION_VEX_CHOICES = [ + ("CNP", "CODE_NOT_PRESENT"), + ("CNR", "CODE_NOT_REACHABLE"), + ("PP", "PROTECTED_AT_PERIMITER"), + ("PR", "PROTECTED_AT_RUNTIME"), + ("PC", "PROTECTED_BY_COMPILER"), + ("PMC", "PROTECTED_BY_MITIGATING_CONTROL"), + ("RC", "REQUIRES_CONFIGURATION"), + ("RD", "REQUIRES_DEPENDENCY"), + ("RE", "REQUIRES_ENVIRONMENT"), + ] + + justification = models.CharField( + max_length=3, + choices=JUSTIFICATION_VEX_CHOICES, + help_text="The rationale of why the impact analysis state was asserted.", + ) + + detail = models.CharField(help_text="Additional notes to explain the VEX.") + ignored = models.BooleanField(help_text="Ignore VEX for this vulnerability", default=False) + + class Meta: + unique_together = ("productpackage", "vulnerability_id", "dataspace") + + @property + def get_responses_choices(self): + labels = [] + for choice_code, choice_label in self.RESPONSES_VEX_CHOICES: + if choice_code in self.responses: + labels.append(choice_label) + return labels + + @property + def vulnerability_url(self): + return ( + f"{self.dataspace.configuration.vulnerablecode_url}" + f"vulnerabilities/{self.vulnerability_id}" + ) + + def __str__(self): + return f"{str(self.productpackage)} / {str(self.vulnerability_id)}" diff --git a/product_portfolio/templates/product_portfolio/product_details.html b/product_portfolio/templates/product_portfolio/product_details.html index bf37593..80a969c 100644 --- a/product_portfolio/templates/product_portfolio/product_details.html +++ b/product_portfolio/templates/product_portfolio/product_details.html @@ -73,6 +73,12 @@ 1.5 1.4 + diff --git a/product_portfolio/templates/product_portfolio/tabs/tab_vex.html b/product_portfolio/templates/product_portfolio/tabs/tab_vex.html new file mode 100644 index 0000000..3612ece --- /dev/null +++ b/product_portfolio/templates/product_portfolio/tabs/tab_vex.html @@ -0,0 +1,58 @@ +{% load i18n %} +{% load as_icon from dje_tags %} +{% load urlize_target_blank from dje_tags %} +{% spaceless %} + + + + + + + + + + + + + + {% for vex in values.vex_items %} + {% cycle 'table-odd' '' as rowcolors silent %} + + + + {% include 'product_portfolio/includes/productrelation_element.html' with relation=vex.productpackage %} + + + + + + + + {% empty %} + + {% endfor %} + +
+ {% trans 'Package' %} + + {% trans 'Vulnerability' %} + + {% trans 'State' %} + + {% trans 'Responses' %} + + {% trans 'Justification' %} + + {% trans 'Detail' %} +
+ + + {{ vex.vulnerability_id }} + {{ vex.get_state_display }} +
    + {% for response in vex.get_responses_choices %} +
  • {{ response }}
  • + {% endfor %} +
+
{{ vex.get_justification_display }}{{ vex.detail }}
No results.
+{% endspaceless %} \ No newline at end of file diff --git a/product_portfolio/urls.py b/product_portfolio/urls.py index c63227c..f4e7396 100644 --- a/product_portfolio/urls.py +++ b/product_portfolio/urls.py @@ -13,11 +13,13 @@ from product_portfolio.views import LoadSBOMsView from product_portfolio.views import ManageComponentGridView from product_portfolio.views import ManagePackageGridView +from product_portfolio.views import PackageVEXUpdateView from product_portfolio.views import ProductAddView from product_portfolio.views import ProductDeleteView from product_portfolio.views import ProductDetailsView from product_portfolio.views import ProductExportCycloneDXBOMView from product_portfolio.views import ProductExportSPDXDocumentView +from product_portfolio.views import ProductExportVEXView from product_portfolio.views import ProductListView from product_portfolio.views import ProductSendAboutFilesView from product_portfolio.views import ProductTabCodebaseView @@ -80,6 +82,7 @@ def product_path(path_segment, view): *product_path("about_files", ProductSendAboutFilesView.as_view()), *product_path("export_spdx", ProductExportSPDXDocumentView.as_view()), *product_path("export_cyclonedx", ProductExportCycloneDXBOMView.as_view()), + *product_path("export_vex", ProductExportVEXView.as_view()), *product_path("attribution", AttributionView.as_view()), *product_path("change", ProductUpdateView.as_view()), *product_path("delete", ProductDeleteView.as_view()), @@ -113,4 +116,14 @@ def product_path(path_segment, view): ProductListView.as_view(), name="product_list", ), + path( + "/productpackage///vex_change", + PackageVEXUpdateView.as_view(), + name="package_vex_change", + ), + path( + "", + ProductListView.as_view(), + name="productpackagevex_list", + ), ] diff --git a/product_portfolio/views.py b/product_portfolio/views.py index 2cd95ea..ecdcbc6 100644 --- a/product_portfolio/views.py +++ b/product_portfolio/views.py @@ -79,6 +79,7 @@ from dje.views import DataspaceScopeMixin from dje.views import ExportCycloneDXBOMView from dje.views import ExportSPDXDocumentView +from dje.views import ExportVEXView from dje.views import GetDataspacedObjectMixin from dje.views import Header from dje.views import LicenseDataForBuilderMixin @@ -101,6 +102,7 @@ from product_portfolio.forms import ImportFromScanForm from product_portfolio.forms import ImportManifestsForm from product_portfolio.forms import LoadSBOMsForm +from product_portfolio.forms import PackageVEXForm from product_portfolio.forms import ProductComponentForm from product_portfolio.forms import ProductComponentInlineForm from product_portfolio.forms import ProductCustomComponentForm @@ -114,6 +116,7 @@ from product_portfolio.models import Product from product_portfolio.models import ProductComponent from product_portfolio.models import ProductPackage +from product_portfolio.models import ProductPackageVEX from product_portfolio.models import ScanCodeProject @@ -281,6 +284,16 @@ class ProductDetailsView( "last_modified_by", ], }, + "vex": { + "fields": [ + "productpackage", + "vulnerability_id", + "state", + "responses", + "justification", + "detail", + ], + }, } def get_queryset(self): @@ -515,6 +528,22 @@ def tab_inventory(self): ], } + def tab_vex(self): + productpackage_qs = self.filter_productpackage.qs.order_by("feature", "package") + vex_items = ProductPackageVEX.objects.filter(productpackage__in=productpackage_qs) + label = f'VEX List {vex_items.count()}' + + tab_context = { + "vex_items": vex_items, + } + + return { + "label": format_html(label), + "fields": [ + (None, tab_context, None, "product_portfolio/tabs/tab_vex.html"), + ], + } + @staticmethod def inject_scan_data(scancodeio, feature_grouped, dataspace_uuid): download_urls = [ @@ -1431,6 +1460,10 @@ class ProductExportCycloneDXBOMView(BaseProductView, ExportCycloneDXBOMView): pass +class ProductExportVEXView(BaseProductView, ExportVEXView): + pass + + @login_required def scan_all_packages_view(request, dataspace, name, version=""): user = request.user @@ -1958,3 +1991,35 @@ def scancodeio_project_status_view(request, scancodeproject_uuid): } return TemplateResponse(request, template, context) + + +class PackageVEXUpdateView( + DataspacedUpdateView, +): + model = ProductPackageVEX + form_class = PackageVEXForm + permission_required = "component_catalog.change_package" + template_name = "component_catalog/package_vex_form.html" + slug_url_kwarg = ("vulnerability_id", "productpackage_id") + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + opts = self.model._meta + context.update( + { + "verbose_name": opts.verbose_name, + "verbose_name_plural": opts.verbose_name_plural, + "list_url": reverse(f"{opts.app_label}:{opts.model_name}_list"), + } + ) + return context + + def get_success_url(self): + return reverse( + "product_portfolio:package_vex_change", + kwargs={ + "dataspace": self.kwargs["dataspace"], + "productpackage_id": self.kwargs["productpackage_id"], + "vulnerability_id": self.kwargs["vulnerability_id"], + }, + ) From ef028dcd458d62c9658be018813e58d39df6a9e8 Mon Sep 17 00:00:00 2001 From: ziadhany Date: Fri, 10 May 2024 22:28:21 +0300 Subject: [PATCH 2/2] Add a test for vex with affected and fixed package Add package purl string as a bom ref Signed-off-by: ziadhany --- dejacode_toolkit/tests/test_vex.py | 55 ++- .../tests/testfiles/cyclonedx_vul2.json | 42 ++- dejacode_toolkit/tests/testfiles/vex2.json | 331 ++++++++++++++++++ dejacode_toolkit/vex.py | 91 +++-- dje/views.py | 5 +- 5 files changed, 455 insertions(+), 69 deletions(-) create mode 100644 dejacode_toolkit/tests/testfiles/vex2.json diff --git a/dejacode_toolkit/tests/test_vex.py b/dejacode_toolkit/tests/test_vex.py index fbb2ae7..0f5a088 100644 --- a/dejacode_toolkit/tests/test_vex.py +++ b/dejacode_toolkit/tests/test_vex.py @@ -17,9 +17,10 @@ from serializable import _SerializableJsonEncoder from component_catalog.models import Package -from dejacode_toolkit import vex -from dejacode_toolkit.vex import VEXCycloneDX -from dejacode_toolkit.vex import vulnerability_format_vcic_to_cyclonedx +from dejacode_toolkit.vex import create_auto_vex +from dejacode_toolkit.vex import get_references_and_rating +from dejacode_toolkit.vex import get_vex_document +from dejacode_toolkit.vex import vulnerability_format_vcio_to_cyclonedx from dje.models import Dataspace from dje.tests import create_user from product_portfolio.models import Product @@ -40,6 +41,10 @@ def setUp(self): name="Product1 With Space", version="1.0", dataspace=self.nexb_dataspace ) self.package1 = Package.objects.create(filename="package1", dataspace=self.nexb_dataspace) + self.package1.type = "pypi" + self.package1.namespace = "" + self.package1.name = "flask" + self.package1.version = "2.3.2" self.productpacakge1 = ProductPackage.objects.create( product=self.product1, package=self.package1, dataspace=self.nexb_dataspace @@ -56,6 +61,13 @@ def setUp(self): " either directly or indirectly." ), ) + self.vex2 = ProductPackageVEX.objects.create( + dataspace=self.productpacakge1.dataspace, + productpackage=self.productpacakge1, + vulnerability_id="VCID-z6fe-2j8a-aaak", + state="R", # resolved + detail="This version of Product DEF has been fixed.", + ) def test_create_auto_vex1(self): vulnerabilities = [ @@ -76,13 +88,13 @@ def test_create_auto_vex1(self): ] }, ] - assert ProductPackageVEX.objects.count() == 1 - vex.create_auto_vex(self.package1, vulnerabilities) assert ProductPackageVEX.objects.count() == 2 + create_auto_vex(self.package1, vulnerabilities) + assert ProductPackageVEX.objects.count() == 3 # run create_auto_vex agian and make sure that the databse ignore errors - vex.create_auto_vex(self.package1, vulnerabilities) - assert ProductPackageVEX.objects.count() == 2 + create_auto_vex(self.package1, vulnerabilities) + assert ProductPackageVEX.objects.count() == 3 def test_create_auto_vex2(self): # duplicated vulnerability @@ -104,9 +116,9 @@ def test_create_auto_vex2(self): ] }, ] - assert ProductPackageVEX.objects.count() == 1 - vex.create_auto_vex(self.package1, vulnerabilities) - assert ProductPackageVEX.objects.count() == 1 + assert ProductPackageVEX.objects.count() == 2 + create_auto_vex(self.package1, vulnerabilities) + assert ProductPackageVEX.objects.count() == 2 def test_get_references_and_rating(self): references = [ @@ -128,7 +140,7 @@ def test_get_references_and_rating(self): "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136", } ] - ref, rate = vex.get_references_and_rating(references) + ref, rate = get_references_and_rating(references) assert json.dumps( ref, @@ -169,7 +181,7 @@ def test_vulnerability_format_vcic_to_cyclonedx1(self): with open(vul_data_path) as f: vcio_vulnerability = json.load(f) - vulnerability = vulnerability_format_vcic_to_cyclonedx(vcio_vulnerability, self.vex1) + vulnerability = vulnerability_format_vcio_to_cyclonedx(vcio_vulnerability, self.vex1) cyclonedx_vul_data_path = os.path.join( os.path.dirname(__file__), "testfiles", "cyclonedx_vul1.json" @@ -188,7 +200,7 @@ def test_vulnerability_format_vcic_to_cyclonedx2(self): with open(vul_data_path) as f: vcio_vulnerability = json.load(f) - vulnerability = vulnerability_format_vcic_to_cyclonedx(vcio_vulnerability, self.vex1) + vulnerability = vulnerability_format_vcio_to_cyclonedx(vcio_vulnerability, self.vex1) cyclonedx_vul_data_path = os.path.join( os.path.dirname(__file__), "testfiles", "cyclonedx_vul2.json" @@ -202,7 +214,7 @@ def test_vulnerability_format_vcic_to_cyclonedx2(self): view_=SchemaVersion1Dot4, ) == json.dumps(cyclonedx_vul) - def test_vex_cyclonedx_export(self): + def test_get_vex_document1(self): vul_data_path = os.path.join(os.path.dirname(__file__), "testfiles", "vcio_vul1.json") with open(vul_data_path) as f: vcio_vulnerability = json.load(f) @@ -211,4 +223,17 @@ def test_vex_cyclonedx_export(self): with open(vex_data_path) as f: vex_data = json.load(f) - assert VEXCycloneDX().export([vcio_vulnerability], [self.vex1]) == json.dumps(vex_data) + assert get_vex_document([vcio_vulnerability], [self.vex1]) == json.dumps(vex_data) + + def test_get_vex_document2(self): + vul_data_path = os.path.join(os.path.dirname(__file__), "testfiles", "vcio_vul2.json") + with open(vul_data_path) as f: + vcio_vulnerability = json.load(f) + + vex_data_path = os.path.join(os.path.dirname(__file__), "testfiles", "vex2.json") + with open(vex_data_path) as f: + vex_data = json.load(f) + + assert get_vex_document( + [vcio_vulnerability], [self.vex2], spec_version="1.5" + ) == json.dumps(vex_data) diff --git a/dejacode_toolkit/tests/testfiles/cyclonedx_vul2.json b/dejacode_toolkit/tests/testfiles/cyclonedx_vul2.json index 2115620..7dfcfd5 100644 --- a/dejacode_toolkit/tests/testfiles/cyclonedx_vul2.json +++ b/dejacode_toolkit/tests/testfiles/cyclonedx_vul2.json @@ -1,7 +1,7 @@ { "affects": [ { - "ref": "urn:cdx:serialNumber/version#bom-ref", + "ref": "pkg:pypi/flask@2.3.2", "versions": [ { "status": "affected", @@ -123,6 +123,14 @@ "status": "affected", "version": "1.0.2-3" }, + { + "status": "unaffected", + "version": "1.0.2-3" + }, + { + "status": "unaffected", + "version": "1.0.2-3+deb10u1" + }, { "status": "affected", "version": "1.0.3" @@ -147,6 +155,14 @@ "status": "affected", "version": "1.1.2-2" }, + { + "status": "unaffected", + "version": "1.1.2-2" + }, + { + "status": "unaffected", + "version": "1.1.2-2+deb11u1" + }, { "status": "affected", "version": "1.1.3" @@ -227,6 +243,10 @@ "status": "affected", "version": "2.2.2-2" }, + { + "status": "unaffected", + "version": "2.2.2-3" + }, { "status": "affected", "version": "2.2.3" @@ -235,6 +255,14 @@ "status": "affected", "version": "2.2.4" }, + { + "status": "unaffected", + "version": "2.2.5" + }, + { + "status": "unaffected", + "version": "2.2.5-1" + }, { "status": "affected", "version": "2.3.0" @@ -243,9 +271,21 @@ "status": "affected", "version": "2.3.1" }, + { + "status": "unaffected", + "version": "2.3.2" + }, { "status": "affected", "version": "2:2.0.1-4.el9" + }, + { + "status": "unaffected", + "version": "3.0.2-1" + }, + { + "status": "unaffected", + "version": "3.0.3-1" } ] } diff --git a/dejacode_toolkit/tests/testfiles/vex2.json b/dejacode_toolkit/tests/testfiles/vex2.json new file mode 100644 index 0000000..bbd3fd6 --- /dev/null +++ b/dejacode_toolkit/tests/testfiles/vex2.json @@ -0,0 +1,331 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "version": 1, + "vulnerabilities": [ + { + "affects": [ + { + "ref": "pkg:pypi/flask@2.3.2", + "versions": [ + { + "status": "affected", + "version": "0.1" + }, + { + "status": "affected", + "version": "0.10" + }, + { + "status": "affected", + "version": "0.10.1" + }, + { + "status": "affected", + "version": "0.11" + }, + { + "status": "affected", + "version": "0.11.1" + }, + { + "status": "affected", + "version": "0.12" + }, + { + "status": "affected", + "version": "0.12.1" + }, + { + "status": "affected", + "version": "0.12.2" + }, + { + "status": "affected", + "version": "0.12.3" + }, + { + "status": "affected", + "version": "0.12.4" + }, + { + "status": "affected", + "version": "0.12.5" + }, + { + "status": "affected", + "version": "0.2" + }, + { + "status": "affected", + "version": "0.3" + }, + { + "status": "affected", + "version": "0.3.1" + }, + { + "status": "affected", + "version": "0.4" + }, + { + "status": "affected", + "version": "0.5" + }, + { + "status": "affected", + "version": "0.5.1" + }, + { + "status": "affected", + "version": "0.5.2" + }, + { + "status": "affected", + "version": "0.6" + }, + { + "status": "affected", + "version": "0.6.1" + }, + { + "status": "affected", + "version": "0.7" + }, + { + "status": "affected", + "version": "0.7.1" + }, + { + "status": "affected", + "version": "0.7.2" + }, + { + "status": "affected", + "version": "0.8" + }, + { + "status": "affected", + "version": "0.8.1" + }, + { + "status": "affected", + "version": "0.9" + }, + { + "status": "affected", + "version": "1.0" + }, + { + "status": "affected", + "version": "1.0.1" + }, + { + "status": "affected", + "version": "1.0.2" + }, + { + "status": "affected", + "version": "1.0.2-3" + }, + { + "status": "unaffected", + "version": "1.0.2-3" + }, + { + "status": "unaffected", + "version": "1.0.2-3+deb10u1" + }, + { + "status": "affected", + "version": "1.0.3" + }, + { + "status": "affected", + "version": "1.0.4" + }, + { + "status": "affected", + "version": "1.1.0" + }, + { + "status": "affected", + "version": "1.1.1" + }, + { + "status": "affected", + "version": "1.1.2" + }, + { + "status": "affected", + "version": "1.1.2-2" + }, + { + "status": "unaffected", + "version": "1.1.2-2" + }, + { + "status": "unaffected", + "version": "1.1.2-2+deb11u1" + }, + { + "status": "affected", + "version": "1.1.3" + }, + { + "status": "affected", + "version": "1.1.4" + }, + { + "status": "affected", + "version": "1:0.10.1-7" + }, + { + "status": "affected", + "version": "1:1.0.2-8" + }, + { + "status": "affected", + "version": "1:1.1.2-6" + }, + { + "status": "affected", + "version": "1:2.0.1-3" + }, + { + "status": "affected", + "version": "2.0.0" + }, + { + "status": "affected", + "version": "2.0.0rc1" + }, + { + "status": "affected", + "version": "2.0.0rc2" + }, + { + "status": "affected", + "version": "2.0.1" + }, + { + "status": "affected", + "version": "2.0.2" + }, + { + "status": "affected", + "version": "2.0.3" + }, + { + "status": "affected", + "version": "2.1.0" + }, + { + "status": "affected", + "version": "2.1.1" + }, + { + "status": "affected", + "version": "2.1.2" + }, + { + "status": "affected", + "version": "2.1.3" + }, + { + "status": "affected", + "version": "2.2.0" + }, + { + "status": "affected", + "version": "2.2.1" + }, + { + "status": "affected", + "version": "2.2.2" + }, + { + "status": "affected", + "version": "2.2.2-2" + }, + { + "status": "unaffected", + "version": "2.2.2-3" + }, + { + "status": "affected", + "version": "2.2.3" + }, + { + "status": "affected", + "version": "2.2.4" + }, + { + "status": "unaffected", + "version": "2.2.5" + }, + { + "status": "unaffected", + "version": "2.2.5-1" + }, + { + "status": "affected", + "version": "2.3.0" + }, + { + "status": "affected", + "version": "2.3.1" + }, + { + "status": "unaffected", + "version": "2.3.2" + }, + { + "status": "affected", + "version": "2:2.0.1-4.el9" + }, + { + "status": "unaffected", + "version": "3.0.2-1" + }, + { + "status": "unaffected", + "version": "3.0.3-1" + } + ] + } + ], + "analysis": { + "detail": "This version of Product DEF has been fixed.", + "state": "resolved" + }, + "cwes": [ + 488, + 539 + ], + "description": "Flask vulnerable to possible disclosure of permanent session cookie due to missing Vary: Cookie header", + "id": "VCID-111c-u9bh-aaac", + "ratings": [ + { + "method": "CVSSv3", + "score": "7.5", + "source": { + "url": "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2023-30861.json" + }, + "vector": "AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N" + } + ], + "references": [ + { + "id": "", + "source": { + "url": "https://access.redhat.com/hydra/rest/securitydata/cve/CVE-2023-30861.json" + } + } + ], + "source": { + "url": "http://public.vulnerablecode.io/api/vulnerabilities/457133?format=json" + } + } + ] +} \ No newline at end of file diff --git a/dejacode_toolkit/vex.py b/dejacode_toolkit/vex.py index 1125448..1549c55 100644 --- a/dejacode_toolkit/vex.py +++ b/dejacode_toolkit/vex.py @@ -1,8 +1,4 @@ import json -from dataclasses import dataclass -from dataclasses import field -from typing import List -from typing import Literal from cyclonedx.model.vulnerability import BomTarget from cyclonedx.model.vulnerability import BomTargetVersionRange @@ -110,7 +106,7 @@ def create_auto_vex(package, vulnerabilities): ProductPackageVEX.objects.bulk_create(vex_objects, ignore_conflicts=True) -def vulnerability_format_vcic_to_cyclonedx(vcio_vulnerability, vex: ProductPackageVEX): +def vulnerability_format_vcio_to_cyclonedx(vcio_vulnerability, vex: ProductPackageVEX): """Change the VCIO format of the vulnerability to CycloneDX and add the vex vulnerability""" vulnerability_source = VulnerabilitySource( url=vcio_vulnerability.get("url"), @@ -143,32 +139,35 @@ def vulnerability_format_vcic_to_cyclonedx(vcio_vulnerability, vex: ProductPacka bom_targets = [] versions = [] - for affected_package in vcio_vulnerability.get("affected_packages") or []: - is_vulnerable = affected_package.get("is_vulnerable") + for affected_fixed_package in ( + vcio_vulnerability.get("affected_packages") + vcio_vulnerability.get("fixed_packages") or [] + ): + is_vulnerable = affected_fixed_package.get("is_vulnerable") status = STATUS_VEX_CYCLONEDX.get(is_vulnerable, ImpactAnalysisAffectedStatus.UNKNOWN) - purl_string = affected_package.get("purl") + purl_string = affected_fixed_package.get("purl") vul_purl = None if purl_string: vul_purl = PackageURL.from_string(purl_string) - - versions.append(BomTargetVersionRange(version=vul_purl.version, status=status)) + versions.append(BomTargetVersionRange(version=vul_purl.version, status=status)) if versions: - bom_target = BomTarget(ref="urn:cdx:serialNumber/version#bom-ref", versions=versions) + bom_target = BomTarget(ref=vex.productpackage.package.package_url, versions=versions) bom_targets.append(bom_target) weaknesses = vcio_vulnerability.get("weaknesses") or [] cwes = get_cwes(weaknesses) vulnerability = Vulnerability( - # bom_ref="", id=vcio_vulnerability.get("vulnerability_id"), source=vulnerability_source, references=vulnerability_reference, ratings=vulnerability_ratings, cwes=cwes, description=vcio_vulnerability.get("summary") or "", + analysis=vulnerability_analysis, + affects=bom_targets, + # bom_ref="", # detail=detail, # recommendation="", # advisories=advisories, @@ -179,8 +178,6 @@ def vulnerability_format_vcic_to_cyclonedx(vcio_vulnerability, vex: ProductPacka # tools=tools, # properties=properties, # Deprecated Parameters kept for backwards compatibility - analysis=vulnerability_analysis, - affects=bom_targets, # source_name=source_name, # source_url=source_url, # recommendations=recommendations, @@ -249,42 +246,36 @@ def get_references_and_rating(references): return cyclonedx_references, cyclonedx_rating -@dataclass -class VEXCycloneDX: - """https://github.com/CycloneDX/bom-examples/tree/master/VEX""" - - vulnerabilities: List[Vulnerability] = field(default_factory=lambda: []) - bomFormat: str = "CycloneDX" - specVersion: Literal["1.4", "1.5", "1.6"] = "1.4" - version: int = 1 - - def export(self, vcio_vulnerabilities, vexs): - self.vulnerabilities = [] - schema_version = { - "1.4": SchemaVersion1Dot4, - "1.5": SchemaVersion1Dot5, - "1.6": SchemaVersion1Dot6, - }.get(self.specVersion) - - for vcio_vulnerability, vex in zip(vcio_vulnerabilities, vexs): - cyclonedx_vulnerability = vulnerability_format_vcic_to_cyclonedx( - vcio_vulnerability, vex - ) - self.vulnerabilities.append( - json.loads( - json.dumps( - cyclonedx_vulnerability, - cls=_SerializableJsonEncoder, - view_=schema_version, - ) +def get_vex_document(vcio_vulnerabilities, vexs, spec_version="1.4", version=1): + schema_version = { + "1.4": SchemaVersion1Dot4, + "1.5": SchemaVersion1Dot5, + "1.6": SchemaVersion1Dot6, + }.get(spec_version, SchemaVersion1Dot4) + + vulnerabilities = [] + if len(vcio_vulnerabilities) != len(vexs): + raise KeyError("Invalid number of vulnerabilities or vexs") + + for vcio_vulnerability, vex in zip(vcio_vulnerabilities, vexs): + if not (vcio_vulnerability and vex): + continue + cyclonedx_vulnerability = vulnerability_format_vcio_to_cyclonedx(vcio_vulnerability, vex) + vulnerabilities.append( + json.loads( + json.dumps( + cyclonedx_vulnerability, + cls=_SerializableJsonEncoder, + view_=schema_version, ) ) - - return json.dumps( - { - "bomFormat": self.bomFormat, - "specVersion": self.specVersion, - "version": self.version, - "vulnerabilities": self.vulnerabilities, - } ) + + return json.dumps( + { + "bomFormat": "CycloneDX", + "specVersion": spec_version, + "version": version, + "vulnerabilities": vulnerabilities, + } + ) diff --git a/dje/views.py b/dje/views.py index 4d34115..9cc85ff 100644 --- a/dje/views.py +++ b/dje/views.py @@ -75,7 +75,7 @@ from component_catalog.license_expression_dje import get_license_objects from dejacode_toolkit.purldb import PurlDB from dejacode_toolkit.scancodeio import ScanCodeIO -from dejacode_toolkit.vex import VEXCycloneDX +from dejacode_toolkit.vex import get_vex_document from dejacode_toolkit.vulnerablecode import VulnerableCode from dje import outputs from dje.copier import COPY_DEFAULT_EXCLUDE @@ -2398,8 +2398,7 @@ def get(self, request, *args, **kwargs): raise Http404 spec_version = self.request.GET.get("spec_version") - vex = VEXCycloneDX(specVersion=spec_version) - vex_json = vex.export(vulnerabilities, vexs) + vex_json = get_vex_document(vulnerabilities, vexs, spec_version=spec_version) response = FileResponse( vex_json, filename=filename,