diff --git a/vulnfeeds/cves/versions.go b/vulnfeeds/cves/versions.go index 6bc05123e7f..bd73baebdb2 100644 --- a/vulnfeeds/cves/versions.go +++ b/vulnfeeds/cves/versions.go @@ -435,11 +435,16 @@ func ExtractVersionInfo(cve CVEItem, validVersions []string) (v VersionInfo, not } gotVersions = true - v.AffectedVersions = append(v.AffectedVersions, AffectedVersion{ + possibleNewAffectedVersion := AffectedVersion{ Introduced: introduced, Fixed: fixed, LastAffected: lastaffected, - }) + } + if slices.Contains(v.AffectedVersions, possibleNewAffectedVersion) { + // Avoid appending duplicates + continue + } + v.AffectedVersions = append(v.AffectedVersions, possibleNewAffectedVersion) } } if !gotVersions { diff --git a/vulnfeeds/cves/versions_test.go b/vulnfeeds/cves/versions_test.go index 6b62d8e6a27..951245881e3 100644 --- a/vulnfeeds/cves/versions_test.go +++ b/vulnfeeds/cves/versions_test.go @@ -1,10 +1,32 @@ package cves import ( + "encoding/json" + "log" + "os" "reflect" "testing" + + "github.com/go-test/deep" ) +// Helper function to load in a specific CVE from sample data. +func loadTestData(CVEID string) CVEItem { + file, err := os.Open("../test_data/nvdcve-1.1-test-data.json") + if err != nil { + log.Fatalf("Failed to load test data") + } + var nvdCves NVDCVE + json.NewDecoder(file).Decode(&nvdCves) + for _, item := range nvdCves.CVEItems { + if item.CVE.CVEDataMeta.ID == CVEID { + return item + } + } + log.Fatalf("test data doesn't contain specified %q", CVEID) + return CVEItem{} +} + func TestParseCPE(t *testing.T) { tests := []struct { description string @@ -452,3 +474,77 @@ func TestNormalizeVersion(t *testing.T) { } } } + +func TestExtractVersionInfo(t *testing.T) { + tests := []struct { + description string + inputCVEItem CVEItem + inputValidVersions []string + expectedVersionInfo VersionInfo + expectedNotes []string + }{ + { + description: "A CVE with multiple affected versions", + inputCVEItem: loadTestData("CVE-2022-32746"), + inputValidVersions: []string{}, + expectedVersionInfo: VersionInfo{ + FixCommits: []GitCommit(nil), + LimitCommits: []GitCommit(nil), + LastAffectedCommits: []GitCommit(nil), + AffectedVersions: []AffectedVersion{ + AffectedVersion{ + Introduced: "4.16.0", + Fixed: "4.16.4", + LastAffected: "", + }, + AffectedVersion{ + Introduced: "4.15.0", + Fixed: "4.15.9", + LastAffected: "", + }, + AffectedVersion{ + Introduced: "4.3.0", + Fixed: "4.14.14", + LastAffected: "", + }, + }, + }, + expectedNotes: []string{}, + }, + { + description: "A CVE with duplicate affected versions squashed", + inputCVEItem: loadTestData("CVE-2022-0090"), + inputValidVersions: []string{}, + expectedVersionInfo: VersionInfo{ + FixCommits: []GitCommit(nil), + LimitCommits: []GitCommit(nil), + LastAffectedCommits: []GitCommit(nil), + AffectedVersions: []AffectedVersion{ + AffectedVersion{ + Introduced: "14.6.0", + Fixed: "14.6.1", + LastAffected: "", + }, + AffectedVersion{ + Introduced: "14.5.0", + Fixed: "14.5.3", + LastAffected: "", + }, + AffectedVersion{ + Introduced: "", + Fixed: "14.4.5", + LastAffected: "", + }, + }, + }, + expectedNotes: []string{}, + }, + } + + for _, tc := range tests { + gotVersionInfo, _ := ExtractVersionInfo(tc.inputCVEItem, tc.inputValidVersions) + if diff := deep.Equal(gotVersionInfo, tc.expectedVersionInfo); diff != nil { + t.Errorf("test %q: VersionInfo for %#v was incorrect: %s", tc.description, tc.inputCVEItem, diff) + } + } +} diff --git a/vulnfeeds/test_data/nvdcve-1.1-test-data.json b/vulnfeeds/test_data/nvdcve-1.1-test-data.json index 0811ed611f1..ef3d0b219e8 100644 --- a/vulnfeeds/test_data/nvdcve-1.1-test-data.json +++ b/vulnfeeds/test_data/nvdcve-1.1-test-data.json @@ -5,6 +5,159 @@ "CVE_data_numberOfCVEs" : "660", "CVE_data_timestamp" : "2022-08-31T05:00Z", "CVE_Items" : [ + { + "cve": { + "data_type": "CVE", + "data_format": "MITRE", + "data_version": "4.0", + "CVE_data_meta": { + "ID": "CVE-2022-0090", + "ASSIGNER": "cve@gitlab.com" + }, + "problemtype": { + "problemtype_data": [ + { + "description": [ + { + "lang": "en", + "value": "CWE-269" + } + ] + } + ] + }, + "references": { + "reference_data": [ + { + "url": "https://hackerone.com/reports/1415964", + "name": "https://hackerone.com/reports/1415964", + "refsource": "MISC", + "tags": [ + "Permissions Required" + ] + }, + { + "url": "https://gitlab.com/gitlab-org/cves/-/blob/master/2022/CVE-2022-0090.json", + "name": "https://gitlab.com/gitlab-org/cves/-/blob/master/2022/CVE-2022-0090.json", + "refsource": "CONFIRM", + "tags": [ + "Third Party Advisory" + ] + }, + { + "url": "https://gitlab.com/gitlab-org/gitaly/-/issues/3948", + "name": "https://gitlab.com/gitlab-org/gitaly/-/issues/3948", + "refsource": "MISC", + "tags": [ + "Broken Link" + ] + } + ] + }, + "description": { + "description_data": [ + { + "lang": "en", + "value": "An issue has been discovered affecting GitLab versions prior to 14.4.5, between 14.5.0 and 14.5.3, and between 14.6.0 and 14.6.1. GitLab is configured in a way that it doesn't ignore replacement references with git sub-commands, allowing a malicious user to spoof the contents of their commits in the UI." + } + ] + } + }, + "configurations": { + "CVE_data_version": "4.0", + "nodes": [ + { + "operator": "OR", + "children": [], + "cpe_match": [ + { + "vulnerable": true, + "cpe23Uri": "cpe:2.3:a:gitlab:gitlab:*:*:*:*:community:*:*:*", + "versionStartIncluding": "14.6.0", + "versionEndExcluding": "14.6.1", + "cpe_name": [] + }, + { + "vulnerable": true, + "cpe23Uri": "cpe:2.3:a:gitlab:gitlab:*:*:*:*:enterprise:*:*:*", + "versionStartIncluding": "14.6.0", + "versionEndExcluding": "14.6.1", + "cpe_name": [] + }, + { + "vulnerable": true, + "cpe23Uri": "cpe:2.3:a:gitlab:gitlab:*:*:*:*:community:*:*:*", + "versionStartIncluding": "14.5.0", + "versionEndExcluding": "14.5.3", + "cpe_name": [] + }, + { + "vulnerable": true, + "cpe23Uri": "cpe:2.3:a:gitlab:gitlab:*:*:*:*:enterprise:*:*:*", + "versionStartIncluding": "14.5.0", + "versionEndExcluding": "14.5.3", + "cpe_name": [] + }, + { + "vulnerable": true, + "cpe23Uri": "cpe:2.3:a:gitlab:gitlab:*:*:*:*:community:*:*:*", + "versionEndExcluding": "14.4.5", + "cpe_name": [] + }, + { + "vulnerable": true, + "cpe23Uri": "cpe:2.3:a:gitlab:gitlab:*:*:*:*:enterprise:*:*:*", + "versionEndExcluding": "14.4.5", + "cpe_name": [] + } + ] + } + ] + }, + "impact": { + "baseMetricV3": { + "cvssV3": { + "version": "3.1", + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N", + "attackVector": "NETWORK", + "attackComplexity": "LOW", + "privilegesRequired": "LOW", + "userInteraction": "NONE", + "scope": "UNCHANGED", + "confidentialityImpact": "NONE", + "integrityImpact": "HIGH", + "availabilityImpact": "NONE", + "baseScore": 6.5, + "baseSeverity": "MEDIUM" + }, + "exploitabilityScore": 2.8, + "impactScore": 3.6 + }, + "baseMetricV2": { + "cvssV2": { + "version": "2.0", + "vectorString": "AV:N/AC:L/Au:N/C:N/I:P/A:N", + "accessVector": "NETWORK", + "accessComplexity": "LOW", + "authentication": "NONE", + "confidentialityImpact": "NONE", + "integrityImpact": "PARTIAL", + "availabilityImpact": "NONE", + "baseScore": 5 + }, + "severity": "MEDIUM", + "exploitabilityScore": 10, + "impactScore": 2.9, + "acInsufInfo": false, + "obtainAllPrivilege": false, + "obtainUserPrivilege": false, + "obtainOtherPrivilege": false, + "userInteractionRequired": false + } + }, + "publishedDate": "2022-01-18T17:15Z", + "lastModifiedDate": "2022-01-25T14:49Z" + }, { "cve" : { "data_type" : "CVE", @@ -41183,4 +41336,4 @@ "publishedDate" : "2022-08-23T04:15Z", "lastModifiedDate" : "2022-08-24T14:26Z" } ] -} \ No newline at end of file +}