diff --git a/dojo/db_migrations/0203_alter_finding_options_finding_epss_percentile_and_more.py b/dojo/db_migrations/0203_alter_finding_options_finding_epss_percentile_and_more.py new file mode 100644 index 00000000000..00bf8b5e2ba --- /dev/null +++ b/dojo/db_migrations/0203_alter_finding_options_finding_epss_percentile_and_more.py @@ -0,0 +1,36 @@ +# Generated by Django 4.1.13 on 2024-02-11 15:32 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dojo', '0202_alter_dojo_group_social_provider'), + ] + + operations = [ + migrations.AlterModelOptions( + name='finding', + options={'ordering': ('numerical_severity', '-date', 'title', 'epss_score', 'epss_percentile')}, + ), + migrations.AddField( + model_name='finding', + name='epss_percentile', + field=models.FloatField(blank=True, default=None, help_text='EPSS percentile for the CVE. Describes how many CVEs are scored at or below this one.', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1.0)], verbose_name='EPSS percentile'), + ), + migrations.AddField( + model_name='finding', + name='epss_score', + field=models.FloatField(blank=True, default=None, help_text='EPSS score for the CVE. Describes how likely it is the vulnerability will be exploited in the next 30 days.', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1.0)], verbose_name='EPSS Score'), + ), + migrations.AddIndex( + model_name='finding', + index=models.Index(fields=['epss_score'], name='dojo_findin_epss_sc_e40540_idx'), + ), + migrations.AddIndex( + model_name='finding', + index=models.Index(fields=['epss_percentile'], name='dojo_findin_epss_pe_567499_idx'), + ), + ] diff --git a/dojo/filters.py b/dojo/filters.py index 723c52337f3..2cdfe7a57af 100644 --- a/dojo/filters.py +++ b/dojo/filters.py @@ -322,6 +322,8 @@ def get_finding_filterset_fields(metrics=False, similar=False): 'unique_id_from_tool', 'vuln_id_from_tool', 'service', + 'epss_score', + 'epss_percentile' ]) if similar: @@ -1443,6 +1445,8 @@ class FindingFilter(FindingFilterWithTags): ('test__engagement__product__name', 'test__engagement__product__name'), ('service', 'service'), + ('epss_score', 'epss_score'), + ('epss_percentile', 'epss_percentile'), ), field_labels={ 'numerical_severity': 'Severity', @@ -1451,6 +1455,8 @@ class FindingFilter(FindingFilterWithTags): 'mitigated': 'Mitigated Date', 'title': 'Finding Name', 'test__engagement__product__name': 'Product Name', + 'epss_score': 'EPSS Score', + 'epss_percentile': 'EPSS Percentile', } ) @@ -1464,7 +1470,8 @@ class Meta: 'numerical_severity', 'line', 'duplicate_finding', 'hash_code', 'reviewers', 'created', 'files', 'sla_start_date', 'sla_expiration_date', 'cvssv3', - 'severity_justification', 'steps_to_reproduce'] + 'severity_justification', 'steps_to_reproduce', + 'epss_score', 'epss_percentile'] def __init__(self, *args, **kwargs): self.user = None diff --git a/dojo/models.py b/dojo/models.py index 1ed97a2b69e..90da3b5c1f0 100755 --- a/dojo/models.py +++ b/dojo/models.py @@ -13,7 +13,7 @@ from django.contrib.auth.models import Group from django.db.models.expressions import Case, When from django.urls import reverse -from django.core.validators import RegexValidator, validate_ipv46_address +from django.core.validators import RegexValidator, validate_ipv46_address, MinValueValidator, MaxValueValidator from django.core.files.base import ContentFile from django.core.exceptions import ValidationError from django.db import models, connection @@ -2207,6 +2207,14 @@ class Finding(models.Model): blank=False, verbose_name=_("Vulnerability Id"), help_text=_("An id of a vulnerability in a security advisory associated with this finding. Can be a Common Vulnerabilities and Exposures (CVE) or from other sources.")) + epss_score = models.FloatField(default=None, null=True, blank=True, + verbose_name=_("EPSS Score"), + help_text=_("EPSS score for the CVE. Describes how likely it is the vulnerability will be exploited in the next 30 days."), + validators=[MinValueValidator(0.0), MaxValueValidator(1.0)]) + epss_percentile = models.FloatField(default=None, null=True, blank=True, + verbose_name=_("EPSS percentile"), + help_text=_("EPSS percentile for the CVE. Describes how many CVEs are scored at or below this one."), + validators=[MinValueValidator(0.0), MaxValueValidator(1.0)]) cvssv3_regex = RegexValidator(regex=r'^AV:[NALP]|AC:[LH]|PR:[UNLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]', message="CVSS must be entered in format: 'AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H'") cvssv3 = models.TextField(validators=[cvssv3_regex], max_length=117, @@ -2501,7 +2509,7 @@ class Finding(models.Model): 'High': 1, 'Critical': 0} class Meta: - ordering = ('numerical_severity', '-date', 'title') + ordering = ('numerical_severity', '-date', 'title', 'epss_score', 'epss_percentile') indexes = [ models.Index(fields=['test', 'active', 'verified']), @@ -2516,6 +2524,8 @@ class Meta: models.Index(fields=['test', 'component_name']), models.Index(fields=['cve']), + models.Index(fields=['epss_score']), + models.Index(fields=['epss_percentile']), models.Index(fields=['cwe']), models.Index(fields=['out_of_scope']), models.Index(fields=['false_p']), diff --git a/dojo/templates/dojo/findings_list_snippet.html b/dojo/templates/dojo/findings_list_snippet.html index 248b7491942..514c6e1a96c 100644 --- a/dojo/templates/dojo/findings_list_snippet.html +++ b/dojo/templates/dojo/findings_list_snippet.html @@ -2,6 +2,7 @@ {% load display_tags %} {% load authorization_tags %} {% load get_endpoint_status %} +{% load multiply %} {% load static %} {% load i18n %} {% block findings_list %} @@ -322,6 +323,9 @@

{% trans "Vulnerability Id" %} + + {% trans "EPSS Score" %} / {% trans "Percentile" %} + {% if filter_name == 'Closed' %} {% comment %} The display field is translated in the function. No need to translate here as well{% endcomment %} @@ -593,6 +597,19 @@

{% endif %} {% endwith %} + + {% if finding.epss_score is not None %} + {{ finding.epss_score|multiply:100|floatformat:"2" }}% + {% else %} + N.A. + {% endif %} + / + {% if finding.epss_percentile is not None %} + {{ finding.epss_percentile|multiply:100|floatformat:"2" }}% + {% else %} + N.A. + {% endif %} + {% if filter_name == 'Closed' %} {{ finding.mitigated|date }} @@ -721,6 +738,7 @@

}}, { "data": "cwe" }, { "data": "cve" }, + { "data": "epss"}, { "data": "found_date" }, { "data": "finding_age" }, {% if system_settings.enable_finding_sla %} diff --git a/dojo/templates/dojo/view_finding.html b/dojo/templates/dojo/view_finding.html index 5a49cbd5bbf..9517bc0eab3 100755 --- a/dojo/templates/dojo/view_finding.html +++ b/dojo/templates/dojo/view_finding.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load display_tags %} +{% load multiply %} {% load authorization_tags %} {% load humanize %} {% load static %} @@ -268,8 +269,17 @@

Date Mitigated Mitigated By {% endif %} - CWE - Vulnerability Id + CWE + Vulnerability Id + {% if finding.epss_score != None or finding.epss_percentile != None %} + {% if finding.epss_score != None and finding.epss_percentile != None %} + EPSS Score / Percentile + {% elif finding.epss_score != None and finding.epss_percentile == None %} + EPSS Score + {% elif finding.epss_score == None and finding.epss_percentile != None %} + EPSS Percentile + {% endif %} + {% endif %} Found by {% if finding.vuln_id_from_tool %} Vuln ID from tool @@ -421,7 +431,16 @@

{% endif %} {% endif %} - + {% if finding.epss_score != None or finding.epss_percentile != None %} + {% if finding.epss_score != None and finding.epss_percentile != None %} + {{ finding.epss_score|multiply:100|floatformat:"3" }}% / {{ finding.epss_percentile|multiply:100|floatformat:"3" }}% + {% elif finding.epss_score != None and finding.epss_percentile == None %} + {{ finding.epss_score|multiply:100|floatformat:"3" }}% + {% elif finding.epss_score == None and finding.epss_percentile != None %} + {{ finding.epss_percentile|multiply:100|floatformat:"3" }}% + {% endif %} + {% endif %} + {% if found_by %} {% for scanner in found_by %} {{ scanner }} diff --git a/dojo/templatetags/multiply.py b/dojo/templatetags/multiply.py new file mode 100644 index 00000000000..641fa4889cc --- /dev/null +++ b/dojo/templatetags/multiply.py @@ -0,0 +1,8 @@ +from django import template + +register = template.Library() + + +@register.filter +def multiply(value, arg): + return value * arg