Skip to content

Commit

Permalink
Add: EPSS scoring info to results
Browse files Browse the repository at this point in the history
Scan results now show EPSS scores, percentiles and CVEs of their VTs if
the enableEPSS option is set to true.
  • Loading branch information
timopollmeier committed Jun 20, 2024
1 parent 5d09434 commit ec97f7a
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/gmp/models/cve.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/


import {isArray, isDefined} from 'gmp/utils/identity';
import {isEmpty} from 'gmp/utils/string';
import {map} from 'gmp/utils/array';
Expand All @@ -27,6 +26,7 @@ class Cve extends Info {

ret.name = element.name;
ret.id = element.name;
ret.epss = element.epss;

return ret;
}
Expand Down
40 changes: 34 additions & 6 deletions src/web/pages/results/__tests__/detailspage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import Detailspage, {ToolBarIcons} from '../detailspage';

const reloadInterval = -1;
const manualUrl = 'test/';
const enableEPSS = true;

// mock entity

Expand All @@ -40,6 +41,24 @@ export const result = Result.fromElement({
type: 'nvt',
name: 'nvt1',
tags: 'cvss_base_vector=AV:N/AC:M/Au:N/C:P/I:N/A:N|summary=This is a mock result|insight=This is just a test|affected=Affects test cases only|impact=No real impact|solution=Keep writing tests|vuldetect=This is the detection method|solution_type=Mitigation',
epss: {
max_severity: {
score: 0.8765,
percentile: 0.8,
cve: {
_id: 'CVE-2019-1234',
severity: 5.0,
},
},
max_epss: {
score: 0.9876,
percentile: 0.9,
cve: {
_id: 'CVE-2020-5678',
severity: 2.0,
},
},
},
refs: {
ref: [
{_type: 'cve', _id: 'CVE-2019-1234'},
Expand Down Expand Up @@ -124,7 +143,7 @@ describe('Result Detailspage tests', () => {
permissions: {
get: getPermissions,
},
settings: {manualUrl, reloadInterval},
settings: {manualUrl, reloadInterval, enableEPSS},
user: {currentSettings, renewSession},
};

Expand Down Expand Up @@ -189,7 +208,16 @@ describe('Result Detailspage tests', () => {
expect(baseElement).toHaveTextContent('QoD80 %');
expect(baseElement).toHaveTextContent('Host109.876.54.321');
expect(baseElement).toHaveTextContent('Location80/tcp');

expect(baseElement).toHaveTextContent('EPSS (CVE with highest severity)');
expect(baseElement).toHaveTextContent('EPSS Score0.87650');
expect(baseElement).toHaveTextContent('EPSS Percentile0.80000');
expect(baseElement).toHaveTextContent('CVECVE-2019-1234');
expect(baseElement).toHaveTextContent('CVE Severity5.0 (Medium)');
expect(baseElement).toHaveTextContent('EPSS (highest EPSS score)');
expect(baseElement).toHaveTextContent('EPSS Score0.98760');
expect(baseElement).toHaveTextContent('EPSS Percentile0.90000');
expect(baseElement).toHaveTextContent('CVECVE-2020-5678');
expect(baseElement).toHaveTextContent('CVE Severity2.0 (Low)');
expect(heading[2]).toHaveTextContent('Summary');
expect(baseElement).toHaveTextContent('This is a mock result');

Expand Down Expand Up @@ -245,7 +273,7 @@ describe('Result Detailspage tests', () => {
permissions: {
get: getPermissions,
},
settings: {manualUrl, reloadInterval},
settings: {manualUrl, reloadInterval, enableEPSS},
user: {currentSettings, renewSession},
};

Expand Down Expand Up @@ -294,7 +322,7 @@ describe('Result Detailspage tests', () => {
users: {
get: getUsers,
},
settings: {manualUrl, reloadInterval},
settings: {manualUrl, reloadInterval, enableEPSS},
user: {currentSettings, renewSession},
};

Expand Down Expand Up @@ -339,7 +367,7 @@ describe('Result ToolBarIcons tests', () => {
const handleResultDownloadClick = testing.fn();
const handleTicketCreateClick = testing.fn();

const gmp = {settings: {manualUrl}};
const gmp = {settings: {manualUrl, enableEPSS}};

const {render} = rendererWith({
gmp,
Expand Down Expand Up @@ -390,7 +418,7 @@ describe('Result ToolBarIcons tests', () => {
const handleResultDownloadClick = testing.fn();
const handleTicketCreateClick = testing.fn();

const gmp = {settings: {manualUrl}};
const gmp = {settings: {manualUrl, enableEPSS}};

const {render} = rendererWith({
gmp,
Expand Down
55 changes: 53 additions & 2 deletions src/web/pages/results/__tests__/row.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,59 @@ import {rendererWith} from 'web/utils/testing';

import Row from '../row';

const gmp = {settings: {enableEPSS: true}};

describe('Should render EPSS fields', () => {
const {render} = rendererWith({gmp, store: true});

test('should render EPSS columns', () => {
const entity = Result.fromElement({
_id: '101',
name: 'Result 1',
host: {__text: '123.456.78.910', hostname: 'foo'},
port: '80/tcp',
severity: 10.0,
qod: {value: 80},
notes: [],
overrides: [],
tickets: [],
nvt: {
epss: {
max_severity: {
score: 0.8765,
percentile: 0.8,
cve: {
_id: 'CVE-2019-1234',
severity: 5.0,
},
},
max_epss: {
score: 0.9876,
percentile: 0.9,
cve: {
_id: 'CVE-2020-5678',
severity: 2.0,
},
},
},
},
});

const {element} = render(
<table>
<tbody>
<Row entity={entity}/>
</tbody>
</table>,
);

expect(element).toHaveTextContent("0.87650");
expect(element).toHaveTextContent("0.80000");
});
});

describe('Delta reports V2 with changed severity, qod and hostname', () => {
const {render} = rendererWith();
const {render} = rendererWith({gmp, store: true});

test('should render Delta Difference icon', () => {
const entity = Result.fromElement({
Expand Down Expand Up @@ -54,7 +105,7 @@ describe('Delta reports V2 with changed severity, qod and hostname', () => {
});

describe('Delta reports V2 with same severity, qod and hostname', () => {
const {render} = rendererWith();
const {render} = rendererWith({gmp, store: true});

test('should not render Delta Difference icon', () => {
const entity = Result.fromElement({
Expand Down
82 changes: 81 additions & 1 deletion src/web/pages/results/detailspage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import _ from 'gmp/locale';

import {MANUAL, TASK_SELECTED, RESULT_ANY} from 'gmp/models/override';

import {isDefined} from 'gmp/utils/identity';
import {isDefined, isNumber} from 'gmp/utils/identity';

import Badge from 'web/components/badge/badge';

Expand Down Expand Up @@ -70,6 +70,7 @@ import compose from 'web/utils/compose';
import {generateFilename} from 'web/utils/render';
import PropTypes from 'web/utils/proptypes';
import useCapabilities from 'web/utils/useCapabilities';
import useGmp from "web/utils/useGmp";

import NoteComponent from '../notes/component';

Expand All @@ -78,6 +79,8 @@ import OverrideComponent from '../overrides/component';
import TicketComponent from '../tickets/component';

import ResultDetails from './details';
import CveLink from "web/components/link/cvelink.jsx";
import Severitybar from "web/components/bar/severitybar.jsx";

export const ToolBarIcons = ({
entity,
Expand Down Expand Up @@ -177,6 +180,9 @@ const Details = ({entity, ...props}) => {
const {notes, overrides, qod, host, userTags} = entity;
const active_notes = notes.filter(active_filter);
const active_overrides = overrides.filter(active_filter);
const epss = entity?.information?.epss
const gmp = useGmp()

return (
<React.Fragment>
<PageTitle title={_('Result: {{name}}', {name: entity.name})} />
Expand Down Expand Up @@ -230,6 +236,80 @@ const Details = ({entity, ...props}) => {
<TableData>{_('Location')}</TableData>
<TableData>{entity.port}</TableData>
</TableRow>
{ gmp.settings.enableEPSS && isDefined(epss?.max_severity) &&
<>
<TableData colSpan="2">
<b>{_('EPSS (CVE with highest severity)')}</b>
</TableData>
<TableRow>
<TableData>{_('EPSS Score')}</TableData>
<TableData>
{isNumber(epss?.max_severity?.score)
? epss?.max_severity?.score.toFixed(5) : _("N/A")}
</TableData>
</TableRow>
<TableRow>
<TableData>{_('EPSS Percentile')}</TableData>
<TableData>
{isNumber(epss?.max_severity?.percentile)
? epss?.max_severity?.percentile.toFixed(5) : _("N/A")}
</TableData>
</TableRow>
<TableRow>
<TableData>{_('CVE')}</TableData>
<TableData>
<CveLink id={epss?.max_severity?.cve?._id}>
{epss?.max_severity?.cve?._id}
</CveLink>
</TableData>
</TableRow>
<TableRow>
<TableData>{_('CVE Severity')}</TableData>
<Severitybar
severity={isDefined(epss?.max_severity?.cve?.severity)
? epss?.max_severity?.cve?.severity : _("N/A")}
/>
</TableRow>
</>
}
{ gmp.settings.enableEPSS && isDefined(epss?.max_epss) &&
<>
<TableData colSpan="2">
<b>{_('EPSS (highest EPSS score)')}</b>
</TableData>
<TableRow>
<TableData>{_('EPSS Score')}</TableData>
<TableData>
{isNumber(epss?.max_epss?.score)
? epss?.max_epss?.score.toFixed(5) : _("N/A")}
</TableData>
</TableRow>
<TableRow>
<TableData>{_('EPSS Percentile')}</TableData>
<TableData>
{isNumber(epss?.max_epss?.percentile)
? epss?.max_epss?.percentile.toFixed(5) : _("N/A")}
</TableData>
</TableRow>
<TableRow>
<TableData>{_('CVE')}</TableData>
<TableData>
<CveLink id={epss?.max_epss?.cve?._id}>
{epss?.max_epss?.cve?._id}
</CveLink>
</TableData>
</TableRow>
<TableRow>
<TableData>{_('CVE Severity')}</TableData>
<TableData>
<Severitybar
severity={isDefined(epss?.max_epss?.cve?.severity)
? epss?.max_epss?.cve?.severity : _("N/A")}
/>
</TableData>
</TableRow>
</>
}
</TableBody>
</InfoTable>
</Layout>
Expand Down
8 changes: 8 additions & 0 deletions src/web/pages/results/filterdialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ const SORT_FIELDS = [
name: 'modified',
displayName: _l('Modified'),
},
{
name: 'epss_score',
displayName: _l('EPSS Score'),
},
{
name: 'epss_percentile',
displayName: _l('EPSS Percentile'),
},
];

const ResultsFilterDialogComponent = ({
Expand Down
18 changes: 17 additions & 1 deletion src/web/pages/results/row.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import React from 'react';

import _ from 'gmp/locale';

import {isDefined} from 'gmp/utils/identity';
import {isDefined, isNumber} from 'gmp/utils/identity';

import {shorten} from 'gmp/utils/string';

Expand Down Expand Up @@ -38,6 +38,7 @@ import EntitiesActions from 'web/entities/actions';
import PropTypes from 'web/utils/proptypes';

import ResultDelta from './delta';
import useGmp from "web/utils/useGmp";

const Row = ({
actionsComponent: ActionsComponent = EntitiesActions,
Expand All @@ -60,6 +61,10 @@ const Row = ({
const deltaSeverity = entity.delta?.result?.severity;
const deltaHostname = entity.delta?.result?.host?.hostname;
const deltaQoD = entity.delta?.result?.qod?.value;
const epssScore = entity?.information?.epss?.max_severity?.score
const epssPercentile = entity?.information?.epss?.max_severity?.percentile
const gmp = useGmp()
console.debug(entity?.information)
return (
<TableRow>
{delta && (
Expand Down Expand Up @@ -140,6 +145,17 @@ const Row = ({
</IconDivider>
</TableData>
<TableData>{entity.port}</TableData>
{
gmp.settings.enableEPSS &&
<>
<TableData>
{isNumber(epssScore) ? epssScore.toFixed(5) : _("N/A")}
</TableData>
<TableData>
{isNumber(epssPercentile) ? epssPercentile.toFixed(5) : _("N/A")}
</TableData>
</>
}
<TableData>
<DateTime date={entity.creationTime} />
</TableData>
Expand Down
Loading

0 comments on commit ec97f7a

Please sign in to comment.