Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🎉 introducing EPSS score #9516

Merged
merged 24 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 4.1.13 on 2024-02-06 14:28

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('dojo', '0199_whitesource_to_mend'),
]

operations = [
migrations.AddField(
model_name='finding',
name='epss_percentile',
field=models.FloatField(blank=True, default=0, help_text='EPSS percentile for the CVE. Describes how many CVEs are scored at or below this one.', null=True, verbose_name='EPSS percentile'),
),
migrations.AddField(
model_name='finding',
name='epss_score',
field=models.FloatField(blank=True, default=0, help_text='EPSS score for the CVE. Describes how likely it is the vulnerability will be exploited in the next 30 days.', null=True, 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'),
),
]
9 changes: 8 additions & 1 deletion dojo/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,8 @@ def get_finding_filterset_fields(metrics=False, similar=False):
'unique_id_from_tool',
'vuln_id_from_tool',
'service',
'epss_score',
quirinziessler marked this conversation as resolved.
Show resolved Hide resolved
'epss_percentile'
])

if similar:
Expand Down Expand Up @@ -1446,6 +1448,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',
Expand All @@ -1454,6 +1458,8 @@ class FindingFilter(FindingFilterWithTags):
'mitigated': 'Mitigated Date',
'title': 'Finding Name',
'test__engagement__product__name': 'Product Name',
'epss_score': 'EPSS Score',
'epss_percentile': 'EPSS Percentile',
}
)

Expand All @@ -1468,7 +1474,8 @@ class Meta:
'hash_code',
'reviewers',
'created', 'files', 'sla_start_date', 'cvssv3',
'severity_justification', 'steps_to_reproduce']
'severity_justification', 'steps_to_reproduce',
'epss_score', 'epss_percentile']

def __init__(self, *args, **kwargs):
self.user = None
Expand Down
11 changes: 10 additions & 1 deletion dojo/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2132,6 +2132,13 @@ 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=0, null=True, blank=True,
quirinziessler marked this conversation as resolved.
Show resolved Hide resolved
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."))
quirinziessler marked this conversation as resolved.
Show resolved Hide resolved
epss_percentile = models.FloatField(default=0, 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."))

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,
Expand Down Expand Up @@ -2426,7 +2433,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']),

Expand All @@ -2441,6 +2448,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']),
Expand Down
20 changes: 20 additions & 0 deletions dojo/templates/dojo/findings_list_snippet.html
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,12 @@ <h3 class="has-filters">
<th>
{% trans "Vulnerability Id" %}
</th>
<th>
{% trans "EPSS Score" %}
</th>
<th>
{% trans "EPSS Percentile" %}
</th>
<th class="nowrap">
{% if filter_name == 'Closed' %}
{% comment %} The display field is translated in the function. No need to translate here as well{% endcomment %}
Expand Down Expand Up @@ -593,6 +599,20 @@ <h3 class="has-filters">
{% endif %}
{% endwith %}
</td>
<td class="nowrap">
{% if finding.epss_score %}
quirinziessler marked this conversation as resolved.
Show resolved Hide resolved
{{ finding.epss_score }}
quirinziessler marked this conversation as resolved.
Show resolved Hide resolved
{% else %}
N.A.
{% endif %}
</td>
<td class="nowrap">
{% if finding.epss_percentile %}
{{ finding.epss_percentile }}
{% else %}
N.A.
{% endif %}
</td>
<td class="nowrap">
{% if filter_name == 'Closed' %}
{{ finding.mitigated|date }}
Expand Down
23 changes: 23 additions & 0 deletions dojo/templates/dojo/view_finding.html
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,29 @@ <h3 class="pull-left finding-title">
</div>
</div>

{% if finding.epss_score or finding.epss_percentile %}
quirinziessler marked this conversation as resolved.
Show resolved Hide resolved
<div class="panel panel-default">
<table id="additional_finding_references" class="table-striped table table-condensed table-hover centered">
quirinziessler marked this conversation as resolved.
Show resolved Hide resolved
<tr>
{% if finding.epss_score%}
<th>EPSS Score</th>
{% endif %}
{% if finding.epss_percentile%}
<th>EPSS Percentile</th>
{% endif %}
</tr>
<tr>
{% if finding.epss_score%}
<td>{{ finding.epss_score }}</td>
quirinziessler marked this conversation as resolved.
Show resolved Hide resolved
{% endif %}
{% if finding.epss_percentile%}
<td>{{ finding.epss_percentile }}</td>
{% endif %}
</tr>
</table>
</div>
{% endif %}

{% with finding|additional_vulnerability_ids as additional_vulnerability_ids %}
{% if additional_vulnerability_ids %}
<div class="panel panel-default">
Expand Down
18 changes: 18 additions & 0 deletions dojo/tools/dependency_track/parser.py
quirinziessler marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,18 @@ def _convert_dependency_track_finding_to_dojo_finding(self, dependency_track_fin
analysis = dependency_track_finding.get('analysis')
is_false_positive = True if analysis is not None and analysis.get('state') == 'FALSE_POSITIVE' else False

# Get the EPSS details
if 'epssPercentile' in dependency_track_finding['vulnerability']:
epss_percentile = dependency_track_finding['vulnerability']['epssPercentile']
else:
epss_percentile = None

if 'epssScore' in dependency_track_finding['vulnerability']:
epss_score = dependency_track_finding['vulnerability']['epssScore']
else:
epss_score = None


# Build and return Finding model
finding = Finding(
title=title,
Expand All @@ -235,6 +247,12 @@ def _convert_dependency_track_finding_to_dojo_finding(self, dependency_track_fin

if cvss_score:
finding.cvssv3_score = cvss_score

if epss_score:
finding.epss_score = epss_score

if epss_percentile:
finding.epss_percentile = epss_percentile

return finding

Expand Down
15 changes: 15 additions & 0 deletions unittests/tools/test_wazuh_parser.py
quirinziessler marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,21 @@ def test_parse_one_finding(self):
self.assertEqual(1, len(findings))
self.assertEqual("Medium", finding.severity)
self.assertEqual("CVE-1234-123123", finding.unsaved_vulnerability_ids[0])
self.assertEqual("CVE-YYYY-XXXXX affects asdf (version: 4.3.1)", finding.title)
self.assertEqual("https://nvd.nist.gov/vuln/detail/CVE-YYYY-XXXXX\nhttps://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-YYYY-XXXXX", finding.references)
self.assertEqual("asdf", finding.component_name)

def test_parse_one_endpoint_finding(self):
testfile = open("unittests/scans/wazuh/one_endpoint_finding.json")
parser = WazuhParser()
findings = parser.get_findings(testfile, Test())
for finding in findings:
for endpoint in finding.unsaved_endpoints:
endpoint.clean()
self.assertEqual(1, len(findings))
self.assertEqual("Medium", finding.severity)
self.assertEqual("CVE-1234-123123", finding.unsaved_vulnerability_ids[0])
self.assertEqual("123.123.123.123", finding.unsaved_endpoints)

def test_parse_many_finding(self):
testfile = open("unittests/scans/wazuh/many_findings.json")
Expand Down
Loading