mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-12 07:40:48 -08:00
feat(report): add CVSS vectors in sarif report (#9157)
This commit is contained in:
8
integration/testdata/alpine-310.sarif.golden
vendored
8
integration/testdata/alpine-310.sarif.golden
vendored
@@ -27,6 +27,10 @@
|
||||
"markdown": "**Vulnerability CVE-2019-1549**\n| Severity | Package | Fixed Version | Link |\n| --- | --- | --- | --- |\n|MEDIUM|libssl1.1|1.1.1d-r0|[CVE-2019-1549](https://avd.aquasec.com/nvd/cve-2019-1549)|\n\nOpenSSL 1.1.1 introduced a rewritten random number generator (RNG). This was intended to include protection in the event of a fork() system call in order to ensure that the parent and child processes did not share the same RNG state. However this protection was not being used in the default case. A partial mitigation for this issue is that the output from a high precision timer is mixed into the RNG state so the likelihood of a parent and child process sharing state is significantly reduced. If an application already calls OPENSSL_init_crypto() explicitly using OPENSSL_INIT_ATFORK then this problem does not occur at all. Fixed in OpenSSL 1.1.1d (Affected 1.1.1-1.1.1c)."
|
||||
},
|
||||
"properties": {
|
||||
"cvssv2_score": 5,
|
||||
"cvssv2_vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N",
|
||||
"cvssv3_baseScore": 5.3,
|
||||
"cvssv3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
|
||||
"precision": "very-high",
|
||||
"security-severity": "5.3",
|
||||
"tags": [
|
||||
@@ -54,6 +58,10 @@
|
||||
"markdown": "**Vulnerability CVE-2019-1551**\n| Severity | Package | Fixed Version | Link |\n| --- | --- | --- | --- |\n|MEDIUM|libssl1.1|1.1.1d-r2|[CVE-2019-1551](https://avd.aquasec.com/nvd/cve-2019-1551)|\n\nThere is an overflow bug in the x64_64 Montgomery squaring procedure used in exponentiation with 512-bit moduli. No EC algorithms are affected. Analysis suggests that attacks against 2-prime RSA1024, 3-prime RSA1536, and DSA1024 as a result of this defect would be very difficult to perform and are not believed likely. Attacks against DH512 are considered just feasible. However, for an attack the target would have to re-use the DH512 private key, which is not recommended anyway. Also applications directly using the low level API BN_mod_exp may be affected if they use BN_FLG_CONSTTIME. Fixed in OpenSSL 1.1.1e (Affected 1.1.1-1.1.1d). Fixed in OpenSSL 1.0.2u (Affected 1.0.2-1.0.2t)."
|
||||
},
|
||||
"properties": {
|
||||
"cvssv2_score": 5,
|
||||
"cvssv2_vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N",
|
||||
"cvssv3_baseScore": 5.3,
|
||||
"cvssv3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
|
||||
"precision": "very-high",
|
||||
"security-severity": "5.3",
|
||||
"tags": [
|
||||
|
||||
@@ -67,6 +67,7 @@ type sarifData struct {
|
||||
locationMessage string
|
||||
message string
|
||||
cvssScore string
|
||||
cvssData map[string]any
|
||||
locations []location
|
||||
}
|
||||
|
||||
@@ -88,15 +89,7 @@ func (sw *SarifWriter) addSarifRule(data *sarifData) {
|
||||
WithDefaultConfiguration(&sarif.ReportingConfiguration{
|
||||
Level: toSarifErrorLevel(data.severity),
|
||||
}).
|
||||
WithProperties(sarif.Properties{
|
||||
"tags": []string{
|
||||
data.title,
|
||||
"security",
|
||||
data.severity,
|
||||
},
|
||||
"precision": "very-high",
|
||||
"security-severity": data.cvssScore,
|
||||
})
|
||||
WithProperties(toProperties(data.title, data.severity, data.cvssScore, data.cvssData))
|
||||
if data.url != nil && data.url.String() != "" {
|
||||
r.WithHelpURI(data.url.String())
|
||||
}
|
||||
@@ -158,11 +151,13 @@ func (sw *SarifWriter) Write(_ context.Context, report types.Report) error {
|
||||
if vuln.PkgPath != "" {
|
||||
path = ToPathUri(vuln.PkgPath, res.Class)
|
||||
}
|
||||
cvssData, cvssScore := toCVSSData(vuln)
|
||||
sw.addSarifResult(&sarifData{
|
||||
title: "vulnerability",
|
||||
vulnerabilityId: vuln.VulnerabilityID,
|
||||
severity: vuln.Severity,
|
||||
cvssScore: getCVSSScore(vuln),
|
||||
cvssScore: cvssScore,
|
||||
cvssData: cvssData,
|
||||
url: toUri(vuln.PrimaryURL),
|
||||
resourceClass: res.Class,
|
||||
artifactLocation: toUri(path),
|
||||
@@ -414,14 +409,28 @@ func (sw *SarifWriter) getLocations(name, version, path string, pkgs []ftypes.Pa
|
||||
return locs
|
||||
}
|
||||
|
||||
func getCVSSScore(vuln types.DetectedVulnerability) string {
|
||||
// Take the vendor score
|
||||
// toCVSSData extracts CVSS data from the vulnerability and returns it along with the score.
|
||||
// If CVSS V3 Score is not available, it returns an empty CVSSData struct and a score based on severity.
|
||||
func toCVSSData(vuln types.DetectedVulnerability) (map[string]any, string) {
|
||||
score := severityToScore(vuln.Severity)
|
||||
var data = make(map[string]any)
|
||||
|
||||
// Note: 'cvssv3_baseScore' uses a hybrid naming convention (snake_case + camelCase)
|
||||
// Reference: https://docs.aws.amazon.com/codecatalyst/latest/userguide/test.sarif.html
|
||||
if cvss, ok := vuln.CVSS[vuln.SeveritySource]; ok {
|
||||
return fmt.Sprintf("%.1f", cvss.V3Score)
|
||||
data["cvssv2_vector"] = cvss.V2Vector
|
||||
data["cvssv2_score"] = cvss.V2Score
|
||||
data["cvssv3_vector"] = cvss.V3Vector
|
||||
data["cvssv3_baseScore"] = cvss.V3Score
|
||||
data["cvssv40_vector"] = cvss.V40Vector
|
||||
data["cvssv40_baseScore"] = cvss.V40Score
|
||||
|
||||
if cvss.V3Score != 0 {
|
||||
score = fmt.Sprintf("%.1f", cvss.V3Score)
|
||||
}
|
||||
}
|
||||
|
||||
// Converts severity to score
|
||||
return severityToScore(vuln.Severity)
|
||||
return data, score
|
||||
}
|
||||
|
||||
func severityToScore(severity string) string {
|
||||
@@ -438,3 +447,31 @@ func severityToScore(severity string) string {
|
||||
return "0.0"
|
||||
}
|
||||
}
|
||||
|
||||
func toProperties(title, severity, cvssScore string, cvssData map[string]any) sarif.Properties {
|
||||
properties := sarif.Properties{
|
||||
"tags": []string{
|
||||
title,
|
||||
"security",
|
||||
severity,
|
||||
},
|
||||
"precision": "very-high",
|
||||
"security-severity": cvssScore,
|
||||
}
|
||||
|
||||
for key, value := range cvssData {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
case float64:
|
||||
if v == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
properties[key] = value
|
||||
}
|
||||
|
||||
return properties
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -57,3 +60,117 @@ func Test_clearURI(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakePropertiesMarshal(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
severity string
|
||||
cvssScore string
|
||||
cvssData map[string]any
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "no CVSS data",
|
||||
title: "test",
|
||||
severity: "HIGH",
|
||||
cvssScore: "5.0",
|
||||
cvssData: make(map[string]any),
|
||||
expected: `{
|
||||
"precision": "very-high",
|
||||
"security-severity": "5.0",
|
||||
"tags": ["test", "security", "HIGH"]
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "only CVSS v2",
|
||||
title: "test",
|
||||
severity: "CRITICAL",
|
||||
cvssScore: "4.0",
|
||||
cvssData: map[string]any{
|
||||
"cvssv2_vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N",
|
||||
"cvssv2_score": 5.0,
|
||||
},
|
||||
expected: `{
|
||||
"cvssv2_score": 5,
|
||||
"cvssv2_vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N",
|
||||
"precision": "very-high",
|
||||
"security-severity": "4.0",
|
||||
"tags": ["test", "security", "CRITICAL"]
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "only CVSS v3",
|
||||
title: "test",
|
||||
severity: "CRITICAL",
|
||||
cvssScore: "9.8",
|
||||
cvssData: map[string]any{
|
||||
"cvssv3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"cvssv3_baseScore": 9.8,
|
||||
},
|
||||
expected: `{
|
||||
"cvssv3_baseScore": 9.8,
|
||||
"cvssv3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"precision": "very-high",
|
||||
"security-severity": "9.8",
|
||||
"tags": ["test", "security", "CRITICAL"]
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "only CVSS v4",
|
||||
title: "test",
|
||||
severity: "LOW",
|
||||
cvssScore: "3.5",
|
||||
cvssData: map[string]any{
|
||||
"cvssv40_vector": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
|
||||
"cvssv40_baseScore": 3.5,
|
||||
},
|
||||
expected: `{
|
||||
"cvssv40_baseScore": 3.5,
|
||||
"cvssv40_vector": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
|
||||
"precision": "very-high",
|
||||
"security-severity": "3.5",
|
||||
"tags": ["test", "security", "LOW"]
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "all CVSS versions",
|
||||
title: "test",
|
||||
severity: "HIGH",
|
||||
cvssScore: "8.1",
|
||||
cvssData: map[string]any{
|
||||
"cvssv2_vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P",
|
||||
"cvssv2_score": 7.5,
|
||||
"cvssv3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"cvssv3_baseScore": 9.8,
|
||||
"cvssv40_vector": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
|
||||
"cvssv40_baseScore": 9.3,
|
||||
},
|
||||
expected: `{
|
||||
"cvssv2_score": 7.5,
|
||||
"cvssv2_vector": "AV:N/AC:L/Au:N/C:P/I:P/A:P",
|
||||
"cvssv3_baseScore": 9.8,
|
||||
"cvssv3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"cvssv40_baseScore": 9.3,
|
||||
"cvssv40_vector": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
|
||||
"precision": "very-high",
|
||||
"security-severity": "8.1",
|
||||
"tags": ["test", "security", "HIGH"]
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := toProperties(tt.title, tt.severity, tt.cvssScore, tt.cvssData)
|
||||
|
||||
actualJSON, err := json.Marshal(result)
|
||||
require.NoError(t, err)
|
||||
|
||||
var expectedJSON bytes.Buffer
|
||||
err = json.Compact(&expectedJSON, []byte(tt.expected))
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, expectedJSON.String(), string(actualJSON))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,8 @@ func TestReportWriter_Sarif(t *testing.T) {
|
||||
},
|
||||
"precision": "very-high",
|
||||
"security-severity": "7.5",
|
||||
"cvssv3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
|
||||
"cvssv3_baseScore": 7.5,
|
||||
},
|
||||
Help: &sarif.MultiformatMessageString{
|
||||
Text: lo.ToPtr("Vulnerability CVE-2020-0001\nSeverity: HIGH\nPackage: foo\nFixed Version: 3.4.5\nLink: [CVE-2020-0001](https://avd.aquasec.com/nvd/cve-2020-0001)\nbaz"),
|
||||
|
||||
Reference in New Issue
Block a user