From aabc6a04f3c0c44b5577f91e1bb32a8be1f40913 Mon Sep 17 00:00:00 2001 From: James Telfer <792299+jamestelfer@users.noreply.github.com> Date: Thu, 23 Nov 2023 09:18:39 +1100 Subject: [PATCH] feat: add CVSS3 scores to report CVSS3 scores are now shown preferentially, only falling back to CVSS2 when a CVSS3 score is not present. --- src/finding/summary.go | 35 ++++++++++++++++++- src/finding/summary_test.go | 28 +++++++++++++-- src/report/annotation.gohtml | 21 +++++++---- src/report/annotation_test.go | 10 ++++++ .../TestReports/findings_included.golden | 18 +++++++--- .../testdata/TestReports/image_label.golden | 2 ++ .../TestReports/no_vulnerabilities.golden | 2 ++ .../TestReports/some_findings_ignored.golden | 25 +++++++++---- 8 files changed, 120 insertions(+), 21 deletions(-) diff --git a/src/finding/summary.go b/src/finding/summary.go index 3f629d58..c0016e7a 100644 --- a/src/finding/summary.go +++ b/src/finding/summary.go @@ -2,6 +2,7 @@ package finding import ( "net/url" + "regexp" "slices" "strings" "time" @@ -26,6 +27,7 @@ type Detail struct { PackageName string PackageVersion string CVSS2 CVSSScore + CVSS3 CVSSScore Ignore *findingconfig.Ignore } @@ -132,6 +134,9 @@ func findingToDetail(finding types.ImageScanFinding) Detail { cvss2Vector := findingAttributeValue(finding, "CVSS2_VECTOR") cvss2VectorURL := cvss2VectorURL(cvss2Vector) + cvss3Vector := findingAttributeValue(finding, "CVSS3_VECTOR") + cvss3Vector, cvss3VectorURL := cvss3VectorURL(cvss3Vector) + return Detail{ Name: name, URI: uri, @@ -144,7 +149,11 @@ func findingToDetail(finding types.ImageScanFinding) Detail { Vector: cvss2Vector, VectorURL: cvss2VectorURL, }, - } + CVSS3: CVSSScore{ + Score: findingAttributeValue(finding, "CVSS3_SCORE"), + Vector: cvss3Vector, + VectorURL: cvss3VectorURL, + }} } func findingAttributeValue(finding types.ImageScanFinding, name string) string { @@ -184,3 +193,27 @@ func cvss2VectorURL(cvss2Vector string) string { } return cvss2VectorURL } + +// CVSS3 vector have their version at the front: we need to split this out to +// pass to the calculator URL +var cvss3VectorPattern = regexp.MustCompile(`^CVSS:([\d.]+)/(.+)$`) + +func cvss3VectorURL(versionedVector string) (string, string) { + vector := versionedVector + vectorURL := "" + + if versionedVector != "" { + version := "3.1" + + if matches := cvss3VectorPattern.FindStringSubmatch(versionedVector); matches != nil { + version = matches[1] + vector = matches[2] + } + + vectorURL = "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator" + + "?vector=" + url.QueryEscape(vector) + + "&version=" + url.QueryEscape(version) + } + + return vector, vectorURL +} diff --git a/src/finding/summary_test.go b/src/finding/summary_test.go index d3d6e1d7..8abb142d 100644 --- a/src/finding/summary_test.go +++ b/src/finding/summary_test.go @@ -64,18 +64,19 @@ func TestSummarize(t *testing.T) { }), }, { - name: "findings with CVSS2 scores", + name: "findings with CVSS2 and CVSS3 scores", data: types.ImageScanFindings{ Findings: []types.ImageScanFinding{ fscore("CVE-2019-5188", "HIGH", "1.2", "AV:L/AC:L/Au:N/C:P/I:P/A:P"), fscore("INVALID-CVE", "CRITICAL", "", ""), - fscore("CVE-2019-5189", "HIGH", "", ""), + fscore("CVE-2019-5189", "HIGH", "6", ""), + fscore3("CVE-2019-5189", "HIGH", "9", "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N"), }, }, expected: autogold.Expect(finding.Summary{ Counts: map[types.FindingSeverity]finding.SeverityCount{ types.FindingSeverity("CRITICAL"): {Included: 1}, - types.FindingSeverity("HIGH"): {Included: 2}, + types.FindingSeverity("HIGH"): {Included: 3}, }, Details: []finding.Detail{ { @@ -94,6 +95,16 @@ func TestSummarize(t *testing.T) { { Name: "CVE-2019-5189", Severity: types.FindingSeverity("HIGH"), + CVSS2: finding.CVSSScore{Score: "6"}, + }, + { + Name: "CVE-2019-5189", + Severity: types.FindingSeverity("HIGH"), + CVSS3: finding.CVSSScore{ + Score: "9", + Vector: "AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", + VectorURL: "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AN&version=3.1", + }, }, }, Ignored: []finding.Detail{}, @@ -208,6 +219,17 @@ func fscore(name string, severity types.FindingSeverity, cvss2 string, vector st } } +func fscore3(name string, severity types.FindingSeverity, score string, vector string) types.ImageScanFinding { + return types.ImageScanFinding{ + Name: &name, + Severity: severity, + Attributes: []types.Attribute{ + {Key: aws.String("CVSS3_SCORE"), Value: &score}, + {Key: aws.String("CVSS3_VECTOR"), Value: &vector}, + }, + } +} + func i(id string) findingconfig.Ignore { return findingconfig.Ignore{ID: id} } diff --git a/src/report/annotation.gohtml b/src/report/annotation.gohtml index 4bebbe88..1ae1ee66 100644 --- a/src/report/annotation.gohtml +++ b/src/report/annotation.gohtml @@ -42,27 +42,33 @@ be wrapped in

tag by the Markdown renderer in Buildkite. {{ define "findingName" }}{{ if .Description }}

{{ template "findingNameLink" . }}
{{ .Description }}
{{ else }}{{ template "findingNameLink" . }}{{ end }}{{ end }} {{ define "findingIgnoreUntil" }}{{ if .Until | hasUntilValue }}{{ .Until }}{{ else }}
(indefinitely)
{{ end }}{{ end }} {{ define "findingIgnore"}}{{ if .Reason }}
{{ template "findingIgnoreUntil" . }}
{{ .Reason }}
{{ else }}{{ template "findingIgnoreUntil" . }}{{ end }}{{ end }} -{{ define "cvssScore" -}}{{ .Score | nbsp}}{{ if .Vector }} ({{ .Vector }}){{ end -}}{{ end }} +{{ define "cvssScore" }}{{ .Score | nbsp}}{{ end }} +{{ define "cvssVector" }}{{ if .Vector }}{{ .Vector }}{{ else }} {{end}}{{ end }} +{{ define "cvssCells" }} +{{ if .CVSS3.Score }}{{ template "cvssScore" .CVSS3 }}{{ template "cvssVector" .CVSS3 }}{{ + else +}}{{ if .CVSS2.Score }}{{ template "cvssScore" .CVSS2 }} (*CVSS2){{ end }}{{ template "cvssVector" .CVSS2 }}{{ end }} +{{ end }} {{ if (or .FindingSummary.Details .FindingSummary.Ignored) }}
Vulnerability details
+

All listed scores are CVSS3 unless otherwise noted.

{{ if .FindingSummary.Details }} - + + {{ range $f := .FindingSummary.Details | sortFindings }} - +{{ template "cvssCells" $f }} {{ end }}
CVE Severity AffectsCVSS2 score (vector)CVSS scoreCVSS vector
{{ template "findingName" . }} {{ $f.Severity | string | lowerCase | titleCase }} {{ $f.PackageName | nbsp }} {{ $f.PackageVersion | nbsp }}{{ template "cvssScore" $f.CVSS2 }}
@@ -77,7 +83,8 @@ be wrapped in

tag by the Markdown renderer in Buildkite. Severity Ignored until Affects -CVSS2 score (vector) +CVSS score +CVSS vector {{ range $f := .FindingSummary.Ignored | sortFindings }} @@ -85,7 +92,7 @@ be wrapped in

tag by the Markdown renderer in Buildkite. {{ $f.Severity | string | lowerCase | titleCase }} {{ template "findingIgnore" $f.Ignore }} {{ $f.PackageName | nbsp }} {{ $f.PackageVersion | nbsp }} -{{ template "cvssScore" $f.CVSS2 }} +{{ template "cvssCells" $f }} {{ end }} diff --git a/src/report/annotation_test.go b/src/report/annotation_test.go index 7245c07a..fe4aa044 100644 --- a/src/report/annotation_test.go +++ b/src/report/annotation_test.go @@ -88,6 +88,11 @@ func TestReports(t *testing.T) { Vector: "AV:L/AC:L/Au:N/C:P/I:P/A:P", VectorURL: "https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator?vector=%28AV%3AL%2FAC%3AL%2FAu%3AN%2FC%3AP%2FI%3AP%2FA%3AP%29", }, + CVSS3: finding.CVSSScore{ + Score: "9", + Vector: "AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", + VectorURL: "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AN&version=3.1", + }, }, { Name: "CVE-2019-5200", @@ -180,6 +185,11 @@ func TestReports(t *testing.T) { Score: "10.0", Vector: "AV:L/AC:L/Au:N/C:P/I:P/A:P", }, + CVSS3: finding.CVSSScore{ + Score: "9", + Vector: "AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", + VectorURL: "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV%3AN%2FAC%3AL%2FPR%3AN%2FUI%3AN%2FS%3AU%2FC%3AH%2FI%3AH%2FA%3AN&version=3.1", + }, Ignore: &findingconfig.Ignore{ ID: "CVE-2019-5300", Until: findingconfig.MustParseUntil("2023-12-31"), diff --git a/src/report/testdata/TestReports/findings_included.golden b/src/report/testdata/TestReports/findings_included.golden index 53da65ba..c3e5800d 100644 --- a/src/report/testdata/TestReports/findings_included.golden +++ b/src/report/testdata/TestReports/findings_included.golden @@ -45,37 +45,47 @@ + +

Vulnerability details
+

All listed scores are CVSS3 unless otherwise noted.

- + + - + + + - + + + - + + +
CVE Severity AffectsCVSS2 score (vector)CVSS scoreCVSS vector
CVE-2019-5200
Another vulnerability.
Critical 5200-package 5200-version10.0 (AV:L/AC:L/Au:N/C:P/I:P/A:P)10.0 (*CVSS2)AV:L/AC:L/Au:N/C:P/I:P/A:P
CVE-2019-5188
A code execution vulnerability exists in the directory rehashing functionality of E2fsprogs e2fsck 1.45.4. A specially crafted ext4 directory can cause an out-of-bounds write on the stack, resulting in code execution. An attacker can corrupt a partition to trigger this vulnerability.
High e2fsprogs 1.44.1-1ubuntu1.14.6 (AV:L/AC:L/Au:N/C:P/I:P/A:P)9AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
CVE-2019-5300
Another vulnerability.
Aa-Bogus-Severity 5300-package 5300-version10.0 (AV:L/AC:L/Au:N/C:P/I:P/A:P)10.0 (*CVSS2)AV:L/AC:L/Au:N/C:P/I:P/A:P
diff --git a/src/report/testdata/TestReports/image_label.golden b/src/report/testdata/TestReports/image_label.golden index 0065eb83..44692e92 100644 --- a/src/report/testdata/TestReports/image_label.golden +++ b/src/report/testdata/TestReports/image_label.golden @@ -15,6 +15,8 @@ + +

scan completed: | source updated: diff --git a/src/report/testdata/TestReports/no_vulnerabilities.golden b/src/report/testdata/TestReports/no_vulnerabilities.golden index c56dd03c..f0c4ad78 100644 --- a/src/report/testdata/TestReports/no_vulnerabilities.golden +++ b/src/report/testdata/TestReports/no_vulnerabilities.golden @@ -14,6 +14,8 @@ + +

scan completed: | source updated: diff --git a/src/report/testdata/TestReports/some_findings_ignored.golden b/src/report/testdata/TestReports/some_findings_ignored.golden index 586e4162..f1cdb937 100644 --- a/src/report/testdata/TestReports/some_findings_ignored.golden +++ b/src/report/testdata/TestReports/some_findings_ignored.golden @@ -45,30 +45,38 @@ + +

Vulnerability details
+

All listed scores are CVSS3 unless otherwise noted.

- + + - + + + - + + +
CVE Severity AffectsCVSS2 score (vector)CVSS scoreCVSS vector
CVE-2019-5200
Another vulnerability.
Critical 5200-package 5200-version10.0 (AV:L/AC:L/Au:N/C:P/I:P/A:P)10.0 (*CVSS2)AV:L/AC:L/Au:N/C:P/I:P/A:P
CVE-2019-5188
A code execution vulnerability exists in the directory rehashing functionality of E2fsprogs e2fsck 1.45.4. A specially crafted ext4 directory can cause an out-of-bounds write on the stack, resulting in code execution. An attacker can corrupt a partition to trigger this vulnerability.
High e2fsprogs 1.44.1-1ubuntu1.14.6 (AV:L/AC:L/Au:N/C:P/I:P/A:P)4.6 (*CVSS2)AV:L/AC:L/Au:N/C:P/I:P/A:P
@@ -83,7 +91,8 @@ Severity Ignored until Affects -CVSS2 score (vector) +CVSS score +CVSS vector @@ -91,7 +100,9 @@ Critical
2023-12-31
Ignored to give the base image a chance to be updated
5300-package 5300-version -10.0 (AV:L/AC:L/Au:N/C:P/I:P/A:P) + +9AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N + @@ -99,7 +110,9 @@ Low
(indefinitely)
100-package 100-version -4.0 (AV:L/AC:L/Au:N/C:P/I:P/A:P) + +4.0 (*CVSS2)AV:L/AC:L/Au:N/C:P/I:P/A:P +