From f7fdd1f8b64c8796230c0b689931ab22c3e9414b Mon Sep 17 00:00:00 2001 From: Timo Pollmeier Date: Fri, 31 May 2024 11:32:25 +0200 Subject: [PATCH] Add: EPSS scores from referenced CVEs to VTs The VTs can now contain the maximum EPSS scores of the referenced CVE(s) with the highest severity and of any referenced EPSS score. This will provide an estimation of how likely a vulnerability detected by a VT is to be exploited. --- public/locales/gsa-de.json | 8 ++ src/web/pages/cves/filterdialog.jsx | 8 ++ src/web/pages/cves/table.jsx | 2 +- src/web/pages/nvts/__tests__/detailspage.jsx | 25 ++++++ src/web/pages/nvts/__tests__/listpage.jsx | 36 +++++++-- src/web/pages/nvts/__tests__/row.jsx | 17 ++++ src/web/pages/nvts/details.jsx | 81 +++++++++++++++++++- src/web/pages/nvts/filterdialog.jsx | 8 ++ src/web/pages/nvts/row.jsx | 11 ++- src/web/pages/nvts/table.jsx | 32 +++++++- 10 files changed, 216 insertions(+), 12 deletions(-) diff --git a/public/locales/gsa-de.json b/public/locales/gsa-de.json index 0d2498e56c..63e98a4f62 100644 --- a/public/locales/gsa-de.json +++ b/public/locales/gsa-de.json @@ -204,6 +204,7 @@ "CVE Filter": "CVE-Filter", "CVE List": "CVE-Liste", "CVE Scanner": "CVE-Scanner", + "CVE Severity": "CVE-Schweregrad", "CVE: {{name}}": "CVE: {{name}}", "CVEs": "CVEs", "CVEs by CVSS (Total: {{count}})": "CVEs nach CVSS (Gesamt: {{count}})", @@ -487,6 +488,11 @@ "Duration of last Scan": "Dauer des letzten Scans", "Dynamic": "Dynamisch", "Dynamic Severity": "Dynamischer Schweregrad", + "EPSS": "EPSS", + "EPSS Percentile": "EPSS-Perzentil", + "EPSS Score": "EPSS-Score", + "EPSS (CVE with highest severity)": "EPSS (CVE mit höchstem Schweregrad)", + "EPSS (highest EPSS score)": "EPSS (Höchster EPSS-Score)", "ESXi": "ESXi", "ESXi Credential": "ESXi-Anmeldedaten", "ESXi authentication was successful": "ESXi-Authentifizierung war erfolgreich", @@ -1133,6 +1139,7 @@ "Password only": "Nur Passwort", "Password: Use existing Password": "Passwort: Verwende bestehendes Passwort", "Path": "Pfad", + "Percentile": "Perzentil", "Performance": "Leistungsdaten", "Period": "Zeitintervall", "Permission": "Berechtigung", @@ -1403,6 +1410,7 @@ "Schedules Filter": "Zeitpläne-Filter", "Schedules List": "Zeitplanliste", "Scope": "Reichweite", + "Score": "Score", "Scoring": "Scoring", "Search by content": "Suche nach Inhalt", "SecInfo": "Sicherheitsinfos", diff --git a/src/web/pages/cves/filterdialog.jsx b/src/web/pages/cves/filterdialog.jsx index 24b89fc168..9de784482f 100644 --- a/src/web/pages/cves/filterdialog.jsx +++ b/src/web/pages/cves/filterdialog.jsx @@ -56,6 +56,14 @@ const SORT_FIELDS = [ name: 'severity', displayName: _l('Severity'), }, + { + name: 'epss_score', + displayName: _l('EPSS Score'), + }, + { + name: 'epss_percentile', + displayName: _l('EPSS Percentile'), + }, ]; export default createFilterDialog({ diff --git a/src/web/pages/cves/table.jsx b/src/web/pages/cves/table.jsx index 3bcce860a0..d19695baf9 100644 --- a/src/web/pages/cves/table.jsx +++ b/src/web/pages/cves/table.jsx @@ -91,7 +91,7 @@ const Header = ({ title={_('Severity')} /> - {"EPSS"} + {_("EPSS")} {isDefined(actionsColumn) ? ( actionsColumn diff --git a/src/web/pages/nvts/__tests__/detailspage.jsx b/src/web/pages/nvts/__tests__/detailspage.jsx index bf4af8d931..d99519502c 100644 --- a/src/web/pages/nvts/__tests__/detailspage.jsx +++ b/src/web/pages/nvts/__tests__/detailspage.jsx @@ -67,6 +67,23 @@ const nvt = NVT.fromElement({ _type: 'VendorFix', __text: 'This is a description', }, + epss: { + max_severity: { + score: 0.8765, + percentile: 0.9, + cve: { + _id: 'CVE-2020-1234', + severity: 10.0, + }, + }, + max_epss: { + score: 0.9876, + percentile: 0.8, + cve: { + _id: 'CVE-2020-5678', + }, + }, + }, timeout: '', refs: { ref: [ @@ -308,6 +325,14 @@ describe('Nvt Detailspage tests', () => { expect(element).toHaveTextContent('CVSS Origin'); expect(element).toHaveTextContent('N/A'); + expect(element).toHaveTextContent('EPSS (CVE with highest severity)'); + expect(element).toHaveTextContent('EPSS Score'); + expect(element).toHaveTextContent('0.87650'); + expect(element).toHaveTextContent('EPSS Percentile'); + expect(element).toHaveTextContent('0.90000'); + expect(element).toHaveTextContent('EPSS (highest EPSS score)'); + expect(element).toHaveTextContent('0.98760'); + expect(element).toHaveTextContent('Insight'); expect(element).toHaveTextContent('Foo'); diff --git a/src/web/pages/nvts/__tests__/listpage.jsx b/src/web/pages/nvts/__tests__/listpage.jsx index 51c6f978c9..596e206220 100644 --- a/src/web/pages/nvts/__tests__/listpage.jsx +++ b/src/web/pages/nvts/__tests__/listpage.jsx @@ -45,6 +45,23 @@ const nvt = NVT.fromElement({ _type: 'VendorFix', __text: 'This is a description', }, + epss: { + max_severity: { + score: 0.8765, + percentile: 0.9, + cve: { + _id: 'CVE-2020-1234', + severity: 10.0, + }, + }, + max_epss: { + score: 0.9876, + percentile: 0.8, + cve: { + _id: 'CVE-2020-5678', + }, + }, + }, refs: { ref: [ {_type: 'cve', _id: 'CVE-2020-1234'}, @@ -192,16 +209,21 @@ describe('NvtsPage tests', () => { expect(header[4]).toHaveTextContent('CVE'); expect(header[6]).toHaveTextContent('Severity'); expect(header[7]).toHaveTextContent('QoD'); + expect(header[8]).toHaveTextContent('EPSS'); + expect(header[9]).toHaveTextContent('Score'); + expect(header[10]).toHaveTextContent('Percentile'); const row = baseElement.querySelectorAll('tr'); - expect(row[1]).toHaveTextContent('foo'); - expect(row[1]).toHaveTextContent('bar'); - expect(row[1]).toHaveTextContent('Mon, Jun 24, 2019 1:55 PM CEST'); - expect(row[1]).toHaveTextContent('Mon, Jun 24, 2019 12:12 PM CEST'); - expect(row[1]).toHaveTextContent('CVE-2020-1234'); - expect(row[1]).toHaveTextContent('CVE-2020-5678'); - expect(row[1]).toHaveTextContent('80 %'); + expect(row[2]).toHaveTextContent('foo'); + expect(row[2]).toHaveTextContent('bar'); + expect(row[2]).toHaveTextContent('Mon, Jun 24, 2019 1:55 PM CEST'); + expect(row[2]).toHaveTextContent('Mon, Jun 24, 2019 12:12 PM CEST'); + expect(row[2]).toHaveTextContent('CVE-2020-1234'); + expect(row[2]).toHaveTextContent('CVE-2020-5678'); + expect(row[2]).toHaveTextContent('80 %'); + expect(row[2]).toHaveTextContent('0.87650'); + expect(row[2]).toHaveTextContent('0.90000'); }); test('should allow to bulk action on page contents', async () => { diff --git a/src/web/pages/nvts/__tests__/row.jsx b/src/web/pages/nvts/__tests__/row.jsx index febff4333a..75be014716 100644 --- a/src/web/pages/nvts/__tests__/row.jsx +++ b/src/web/pages/nvts/__tests__/row.jsx @@ -48,6 +48,23 @@ const entity = NVT.fromElement({ _type: 'VendorFix', __text: 'This is a description', }, + epss: { + max_severity: { + score: 0.8765, + percentile: 0.9, + cve: { + _id: 'CVE-2020-1234', + severity: 10.0, + }, + }, + max_epss: { + score: 0.9876, + percentile: 0.8, + cve: { + _id: 'CVE-2020-5678', + }, + }, + }, refs: { ref: [ {_type: 'cve', _id: 'CVE-2020-1234'}, diff --git a/src/web/pages/nvts/details.jsx b/src/web/pages/nvts/details.jsx index 78b3c1b390..0355a4db93 100644 --- a/src/web/pages/nvts/details.jsx +++ b/src/web/pages/nvts/details.jsx @@ -19,7 +19,7 @@ import React from 'react'; import _ from 'gmp/locale'; -import {isDefined} from 'gmp/utils/identity'; +import {isDefined, isNumber} from 'gmp/utils/identity'; import {TAG_NA} from 'gmp/models/nvt'; @@ -44,9 +44,11 @@ import TableRow from 'web/components/table/row'; import References from './references'; import Solution from './solution'; import Pre from './preformatted'; +import CveLink from "web/components/link/cvelink.jsx"; const NvtDetails = ({entity, links = true}) => { const { + epss, tags = {}, severity, qod, @@ -67,6 +69,9 @@ const NvtDetails = ({entity, links = true}) => { + + {_('CVSS')} + {_('CVSS Base')} @@ -104,6 +109,80 @@ const NvtDetails = ({entity, links = true}) => { )} + { isDefined(epss?.max_severity) && + <> + + {_('EPSS (CVE with highest severity)')} + + + {_('EPSS Score')} + + {isNumber(epss?.max_severity?.score) + ? epss?.max_severity?.score.toFixed(5) : _("N/A")} + + + + {_('EPSS Percentile')} + + {isNumber(epss?.max_severity?.percentile) + ? epss?.max_severity?.percentile.toFixed(5) : _("N/A")} + + + + {_('CVE')} + + + {epss?.max_severity?.cve?._id} + + + + + {_('CVE Severity')} + + + + } + { isDefined(epss?.max_epss) && + <> + + {_('EPSS (highest EPSS score)')} + + + {_('EPSS Score')} + + {isNumber(epss?.max_epss?.score) + ? epss?.max_epss?.score.toFixed(5) : _("N/A")} + + + + {_('EPSS Percentile')} + + {isNumber(epss?.max_epss?.percentile) + ? epss?.max_epss?.percentile.toFixed(5) : _("N/A")} + + + + {_('CVE')} + + + {epss?.max_epss?.cve?._id} + + + + + {_('CVE Severity')} + + + + + + } diff --git a/src/web/pages/nvts/filterdialog.jsx b/src/web/pages/nvts/filterdialog.jsx index 1fa7f54a1a..900b887c2d 100644 --- a/src/web/pages/nvts/filterdialog.jsx +++ b/src/web/pages/nvts/filterdialog.jsx @@ -56,6 +56,14 @@ const SORT_FIELDS = [ name: 'qod', displayName: _l('QoD'), }, + { + name: 'epss_score', + displayName: _l('EPSS Score'), + }, + { + name: 'epss_percentile', + displayName: _l('EPSS Percentile'), + }, ]; export default createFilterDialog({ diff --git a/src/web/pages/nvts/row.jsx b/src/web/pages/nvts/row.jsx index 892155c26e..7955065258 100644 --- a/src/web/pages/nvts/row.jsx +++ b/src/web/pages/nvts/row.jsx @@ -20,7 +20,7 @@ import React from 'react'; import Filter from 'gmp/models/filter.js'; -import {isDefined} from 'gmp/utils/identity'; +import {isDefined, isNumber} from 'gmp/utils/identity'; import SeverityBar from 'web/components/bar/severitybar'; @@ -42,6 +42,7 @@ import EntitiesActions from 'web/entities/actions'; import {RowDetailsToggle} from 'web/entities/row'; import PropTypes from 'web/utils/proptypes'; +import {_} from "gmp/locale/lang.js"; const Row = ({ actionsComponent: ActionsComponent = EntitiesActions, @@ -101,6 +102,14 @@ const Row = ({ {entity.qod && } + + {isNumber(entity?.epss?.max_severity?.score) + ? entity.epss?.max_severity?.score.toFixed(5) : _("N/A")} + + + {isNumber(entity?.epss?.max_severity?.percentile) + ? entity.epss?.max_severity?.percentile.toFixed(5) : _("N/A")} + ); diff --git a/src/web/pages/nvts/table.jsx b/src/web/pages/nvts/table.jsx index 9b53df6ae1..c4e10bff3e 100644 --- a/src/web/pages/nvts/table.jsx +++ b/src/web/pages/nvts/table.jsx @@ -51,7 +51,8 @@ const Header = ({ - + {sort ? ( @@ -102,6 +107,7 @@ const Header = ({ + + {_("EPSS")} + {actionsColumn} + + + + ); };