diff --git a/vulnerabilities/severity_systems.py b/vulnerabilities/severity_systems.py index de0d45f69..6260750b2 100644 --- a/vulnerabilities/severity_systems.py +++ b/vulnerabilities/severity_systems.py @@ -37,6 +37,9 @@ def compute(self, scoring_elements: str) -> str: """ return NotImplementedError + def get(self, scoring_elements: str): + return NotImplementedError + @dataclasses.dataclass(order=True) class Cvssv2ScoringSystem(ScoringSystem): @@ -49,6 +52,10 @@ def compute(self, scoring_elements: str) -> str: """ return str(CVSS2(vector=scoring_elements).base_score) + def get(self, scoring_elements: str) -> dict: + scoring_elements = scoring_elements.strip() + return CVSS2(vector=scoring_elements).as_json() + CVSSV2 = Cvssv2ScoringSystem( identifier="cvssv2", @@ -71,6 +78,10 @@ def compute(self, scoring_elements: str) -> str: """ return str(CVSS3(vector=scoring_elements).base_score) + def get(self, scoring_elements: str) -> dict: + scoring_elements = scoring_elements.strip() + return CVSS3(vector=scoring_elements).as_json() + CVSSV3 = Cvssv3ScoringSystem( identifier="cvssv3", diff --git a/vulnerabilities/templates/vulnerability_details.html b/vulnerabilities/templates/vulnerability_details.html index a7c1a4d4f..4f16c32ff 100644 --- a/vulnerabilities/templates/vulnerability_details.html +++ b/vulnerabilities/templates/vulnerability_details.html @@ -2,6 +2,7 @@ {% load humanize %} {% load widget_tweaks %} {% load static %} +{% load show_cvss %} {% block title %} VulnerableCode Vulnerability Details - {{ vulnerability.vulnerability_id }} @@ -52,6 +53,13 @@ +
  • + + + Severities vectors ({{ severity_vectors|length }}) + + +
  • @@ -309,7 +317,63 @@ - +
    + {% for severity_vector in severity_vectors %} + {% if severity_vector.version == '2.0' %} + Vector: {{ severity_vector.vectorString }} + + + + + + + + + + + + + + + + + + + +
    Exploitability (E)Access Vector (AV)Access Complexity (AC)Authentication (Au)Confidentiality Impact (C)Integrity Impact (I)Availability Impact (A)
    {{ severity_vector.exploitability|cvss_printer:"high,functional,unproven,proof_of_concept,not_defined" }}{{ severity_vector.accessVector|cvss_printer:"local,adjacent_network,network" }}{{ severity_vector.accessComplexity|cvss_printer:"high,medium,low" }}{{ severity_vector.authentication|cvss_printer:"multiple,single,none" }}{{ severity_vector.confidentialityImpact|cvss_printer:"none,partial,complete" }}{{ severity_vector.integrityImpact|cvss_printer:"none,partial,complete" }}{{ severity_vector.availabilityImpact|cvss_printer:"none,partial,complete" }}
    + {% elif severity_vector.version == '3.1' or severity_vector.version == '3.0'%} + Vector: {{ severity_vector.vectorString }} + + + + + + + + + + + + + + + + + + + + + +
    Attack Vector (AV)Attack Complexity (AC)Privileges Required (PR)User Interaction (UI)Scope (S)Confidentiality Impact (C)Integrity Impact (I)Availability Impact (A)
    {{ severity_vector.attackVector|cvss_printer:"network,adjacent_network,local,physical"}}{{ severity_vector.attackComplexity|cvss_printer:"low,high" }}{{ severity_vector.privilegesRequired|cvss_printer:"none,low,high" }}{{ severity_vector.userInteraction|cvss_printer:"none,required"}}{{ severity_vector.scope|cvss_printer:"unchanged,changed" }}{{ severity_vector.confidentialityImpact|cvss_printer:"high,low,none" }}{{ severity_vector.integrityImpact|cvss_printer:"high,low,none" }}{{ severity_vector.availabilityImpact|cvss_printer:"high,low,none" }}
    + {% endif %} + {% empty %} + + + There are no known CVSS vectors. + + + {% endfor %} +
    diff --git a/vulnerabilities/templatetags/__init__.py b/vulnerabilities/templatetags/__init__.py new file mode 100644 index 000000000..bdac1cd30 --- /dev/null +++ b/vulnerabilities/templatetags/__init__.py @@ -0,0 +1,8 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# VulnerableCode is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/vulnerablecode for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# diff --git a/vulnerabilities/templatetags/show_cvss.py b/vulnerabilities/templatetags/show_cvss.py new file mode 100644 index 000000000..52533d0f2 --- /dev/null +++ b/vulnerabilities/templatetags/show_cvss.py @@ -0,0 +1,17 @@ +from django import template +from django.utils.safestring import mark_safe + +register = template.Library() + + +@register.filter(is_safe=True) +def cvss_printer(selected_vector, vector_values): + """highlight the selected vector value and return a list of paragraphs""" + p_list = [] + selected_vector = selected_vector.lower() + for vector_value in vector_values.split(","): + if selected_vector == vector_value: + p_list.append(f"

    {selected_vector}

    ") + else: + p_list.append(f"

    {vector_value}

    ") + return mark_safe("".join(p_list)) diff --git a/vulnerabilities/tests/test_get_serverity.py b/vulnerabilities/tests/test_get_serverity.py new file mode 100644 index 000000000..4ac3e646f --- /dev/null +++ b/vulnerabilities/tests/test_get_serverity.py @@ -0,0 +1,100 @@ +import pytest +from cvss.exceptions import CVSS2MalformedError +from cvss.exceptions import CVSS3MalformedError + +from vulnerabilities.severity_systems import CVSSV2 +from vulnerabilities.severity_systems import CVSSV3 +from vulnerabilities.templatetags.show_cvss import cvss_printer + + +def test_get_cvss2_vector_values(): + assert ( + CVSSV2.get("AV:N/AC:L/Au:N/C:P/I:N/A:N ") + == CVSSV2.get("AV:N/AC:L/Au:N/C:P/I:N/A:N") + == { + "accessComplexity": "LOW", + "accessVector": "NETWORK", + "authentication": "NONE", + "availabilityImpact": "NONE", + "availabilityRequirement": "NOT_DEFINED", + "baseScore": 5.0, + "collateralDamagePotential": "NOT_DEFINED", + "confidentialityImpact": "PARTIAL", + "confidentialityRequirement": "NOT_DEFINED", + "environmentalScore": 0.0, + "exploitability": "NOT_DEFINED", + "integrityImpact": "NONE", + "integrityRequirement": "NOT_DEFINED", + "remediationLevel": "NOT_DEFINED", + "reportConfidence": "NOT_DEFINED", + "targetDistribution": "NOT_DEFINED", + "temporalScore": 0.0, + "vectorString": "AV:N/AC:L/Au:N/C:P/I:N/A:N", + "version": "2.0", + } + ) + + with pytest.raises(CVSS2MalformedError): + CVSSV2.get("") + + with pytest.raises(CVSS2MalformedError): + CVSSV2.get("AV:N/AffgL/Au:N/C:P/I:N/A:N ") + + +def test_get_cvss3_vector_values(): + assert ( + CVSSV3.get("CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H ") + == CVSSV3.get("CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H") + == { + "attackComplexity": "LOW", + "attackVector": "NETWORK", + "availabilityImpact": "HIGH", + "availabilityRequirement": "NOT_DEFINED", + "baseScore": 9.1, + "baseSeverity": "CRITICAL", + "confidentialityImpact": "HIGH", + "confidentialityRequirement": "NOT_DEFINED", + "environmentalScore": 9.1, + "environmentalSeverity": "CRITICAL", + "exploitCodeMaturity": "NOT_DEFINED", + "integrityImpact": "HIGH", + "integrityRequirement": "NOT_DEFINED", + "modifiedAttackComplexity": "LOW", + "modifiedAttackVector": "NETWORK", + "modifiedAvailabilityImpact": "HIGH", + "modifiedConfidentialityImpact": "HIGH", + "modifiedIntegrityImpact": "HIGH", + "modifiedPrivilegesRequired": "HIGH", + "modifiedScope": "CHANGED", + "modifiedUserInteraction": "NONE", + "privilegesRequired": "HIGH", + "remediationLevel": "NOT_DEFINED", + "reportConfidence": "NOT_DEFINED", + "scope": "CHANGED", + "temporalScore": 9.1, + "temporalSeverity": "CRITICAL", + "userInteraction": "NONE", + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H", + "version": "3.1", + } + ) + + with pytest.raises(CVSS3MalformedError): + CVSSV3.get("CVSS:3.7/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H ") + + with pytest.raises(CVSS3MalformedError): + CVSSV3.get("") + + +def test_blank_cvss_printer(): + result = cvss_printer("", "") + assert result == "

    " + + +def test_cvss_printer(): + result = cvss_printer("HIGH", "high,medium,low") + assert result == ( + "

    high

    " + "

    medium

    " + "

    low

    " + ) diff --git a/vulnerabilities/views.py b/vulnerabilities/views.py index 026ce1b09..391c165e7 100644 --- a/vulnerabilities/views.py +++ b/vulnerabilities/views.py @@ -6,9 +6,11 @@ # See https://github.com/nexB/vulnerablecode for support or download. # See https://aboutcode.org for more information about nexB OSS projects. # - +import logging from datetime import datetime +from cvss.exceptions import CVSS2MalformedError +from cvss.exceptions import CVSS3MalformedError from django.contrib import messages from django.core.exceptions import ValidationError from django.core.mail import send_mail @@ -26,6 +28,7 @@ from vulnerabilities.forms import PackageSearchForm from vulnerabilities.forms import VulnerabilitySearchForm from vulnerabilities.models import VulnerabilityStatusType +from vulnerabilities.severity_systems import SCORING_SYSTEMS from vulnerabilities.utils import get_severity_range from vulnerablecode.settings import env @@ -132,6 +135,16 @@ def get_context_data(self, **kwargs): weakness_object for weakness_object in weaknesses if weakness_object.weakness ] status = self.object.get_status_label + + severity_vectors = [] + for s in self.object.severities: + if s.scoring_elements and s.scoring_system in SCORING_SYSTEMS: + try: + vector_values = SCORING_SYSTEMS[s.scoring_system].get(s.scoring_elements) + severity_vectors.append(vector_values) + except (CVSS2MalformedError, CVSS3MalformedError, NotImplementedError): + logging.error(f"CVSSMalformedError for {s.scoring_elements}") + context.update( { "vulnerability": self.object, @@ -140,6 +153,7 @@ def get_context_data(self, **kwargs): "severity_score_range": get_severity_range( {s.value for s in self.object.severities} ), + "severity_vectors": severity_vectors, "references": self.object.references.all(), "aliases": self.object.aliases.all(), "affected_packages": self.object.affected_packages.all(),