diff --git a/dojo/db_migrations/0200_findings_add_epss_score_field.py b/dojo/db_migrations/0200_findings_add_epss_score_field.py index e24e6eec969..bd6144bbfc3 100644 --- a/dojo/db_migrations/0200_findings_add_epss_score_field.py +++ b/dojo/db_migrations/0200_findings_add_epss_score_field.py @@ -1,4 +1,3 @@ -# Generated by Django 4.1.11 on 2023-11-08 20:33 from django.db import migrations, models @@ -8,6 +7,21 @@ class Migration(migrations.Migration): 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.', max_length=24), + 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.', max_length=6), + ), + 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.', max_length=6), + ), + migrations.AddField( + model_name='finding_template', + 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.', max_length=6), + ), + migrations.AddField( + model_name='finding_template', + 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.', max_length=6), ), ] diff --git a/dojo/models.py b/dojo/models.py index a09435bbd01..b2f0119a689 100755 --- a/dojo/models.py +++ b/dojo/models.py @@ -2133,8 +2133,11 @@ class Finding(models.Model): 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, - verbose_name=_("EPSS"), + 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.")) + 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], @@ -2445,7 +2448,8 @@ class Meta: models.Index(fields=['test', 'component_name']), models.Index(fields=['cve']), - models.Index(fields=['epss']), + 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/tools/dependency_track/parser.py b/dojo/tools/dependency_track/parser.py index 3150a3f2294..4f3a786668c 100644 --- a/dojo/tools/dependency_track/parser.py +++ b/dojo/tools/dependency_track/parser.py @@ -211,6 +211,16 @@ 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'] + + # Build and return Finding model finding = Finding( title=title, @@ -235,6 +245,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 diff --git a/unittests/scans/wazuh/one_endpoint_finding.json b/unittests/scans/wazuh/one_endpoint_finding.json new file mode 100644 index 00000000000..a3ab190a4b4 --- /dev/null +++ b/unittests/scans/wazuh/one_endpoint_finding.json @@ -0,0 +1,31 @@ +{ + "data": { + "affected_items": [ + { + "architecture": "amd64", + "condition": "Package less than 4.3.2", + "cve": "CVE-1234-123123", + "cvss2_score": 0, + "cvss3_score": 5.5, + "detection_time": "2023-02-08T13:55:10Z", + "external_references": [ + "https://nvd.nist.gov/vuln/detail/CVE-YYYY-XXXXX", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-YYYY-XXXXX" + ], + "name": "asdf", + "published": "2022-09-01", + "severity": "Medium", + "status": "VALID", + "title": "CVE-YYYY-XXXXX affects asdf", + "type": "PACKAGE", + "updated": "2022-09-07", + "version": "4.3.1" + } + ], + "failed_items": [], + "total_affected_items": 1, + "total_failed_items": 0 + }, + "error": 0, + "message": "All selected vulnerabilities were returned" +} \ No newline at end of file diff --git a/unittests/tools/test_wazuh_parser.py b/unittests/tools/test_wazuh_parser.py index 51c026304c4..15baa1102dc 100644 --- a/unittests/tools/test_wazuh_parser.py +++ b/unittests/tools/test_wazuh_parser.py @@ -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")