From a31974a3c0d334b4a5d6ace28212dfc619115e25 Mon Sep 17 00:00:00 2001 From: Kota Kanbe Date: Sun, 21 May 2017 23:04:21 +0900 Subject: [PATCH] Use Severity ranking in OVAL when the CVSS scores are empty. --- models/cvecontents.go | 64 +++++++++++++ models/cvecontents_test.go | 16 ++++ models/scanresults.go | 15 +-- models/scanresults_test.go | 190 +++++++++++++++++++++++++++++++++++++ 4 files changed, 278 insertions(+), 7 deletions(-) diff --git a/models/cvecontents.go b/models/cvecontents.go index 3e549672..d0322089 100644 --- a/models/cvecontents.go +++ b/models/cvecontents.go @@ -106,6 +106,7 @@ func (v CveContents) Cvss2Scores() (values []CveContentCvss2) { }) } } + return } @@ -136,9 +137,69 @@ func (v CveContents) MaxCvss2Score() CveContentCvss2 { max = cont.Cvss2Score } } + if 0 < max { + return value + } + + // If CVSS score isn't on NVD, RedHat and JVN use OVAL's Severity information. + // Convert severity to cvss srore, then returns max severity. + // Only Ubuntu, RedHat and Oracle OVAL has severity data. + order = []CveContentType{Ubuntu, RedHat, Oracle} + for _, ctype := range order { + if cont, found := v[ctype]; found && 0 < len(cont.Severity) { + score := 0.0 + switch cont.Type { + case Ubuntu: + score = severityToScoreForUbuntu(cont.Severity) + case Oracle, RedHat: + score = severityToScoreForRedHat(cont.Severity) + } + if max < score { + value = CveContentCvss2{ + Type: ctype, + Value: Cvss2{ + Score: score, + Vector: cont.Cvss2Vector, + Severity: cont.Severity, + }, + } + } + max = score + } + } return value } +// Convert Severity to Score for Ubuntu OVAL +func severityToScoreForUbuntu(severity string) float64 { + switch strings.ToUpper(severity) { + case "HIGH": + return 10.0 + case "MEDIUM": + return 6.9 + case "LOW": + return 3.9 + } + return 0 +} + +// Convert Severity to Score for RedHat, Oracle OVAL +// https://access.redhat.com/security/updates/classification +// Since I don't know the definition, Use the definition of CVSSv3 +func severityToScoreForRedHat(severity string) float64 { + switch strings.ToUpper(severity) { + case "CRITICAL": + return 10.0 + case "IMPORTANT": + return 8.9 + case "MODERATE": + return 6.9 + case "LOW": + return 3.9 + } + return 0 +} + // CveContentCvss3 has CveContentType and Cvss3 type CveContentCvss3 struct { Type CveContentType @@ -477,6 +538,9 @@ const ( // Ubuntu is Ubuntu Ubuntu CveContentType = "ubuntu" + // Oracle is Oracle Linux + Oracle CveContentType = "oracle" + // Unknown is Unknown Unknown CveContentType = "unknown" ) diff --git a/models/cvecontents_test.go b/models/cvecontents_test.go index 1a178811..2428259b 100644 --- a/models/cvecontents_test.go +++ b/models/cvecontents_test.go @@ -158,6 +158,22 @@ func TestMaxCvss2Scores(t *testing.T) { }, }, }, + // Severity in OVAL + { + in: CveContents{ + Ubuntu: { + Type: Ubuntu, + Severity: "HIGH", + }, + }, + out: CveContentCvss2{ + Type: Ubuntu, + Value: Cvss2{ + Score: 10, + Severity: "HIGH", + }, + }, + }, // Empty { in: CveContents{}, diff --git a/models/scanresults.go b/models/scanresults.go index d3c45ecd..c6f207a1 100644 --- a/models/scanresults.go +++ b/models/scanresults.go @@ -166,13 +166,14 @@ func (r ScanResult) FilterByCvssOver(over float64) ScanResult { // TODO: Filter by ignore cves??? filtered := r.ScannedCves.Find(func(v VulnInfo) bool { - //TODO in the case of only oval, no cvecontents - values := v.CveContents.Cvss2Scores() - for _, vals := range values { - score := vals.Value.Score - if over <= score { - return true - } + v2Max := v.CveContents.MaxCvss2Score() + v3Max := v.CveContents.MaxCvss3Score() + max := v2Max.Value.Score + if max < v3Max.Value.Score { + max = v3Max.Value.Score + } + if over <= max { + return true } return false }) diff --git a/models/scanresults_test.go b/models/scanresults_test.go index fced7012..5cd44b8e 100644 --- a/models/scanresults_test.go +++ b/models/scanresults_test.go @@ -15,3 +15,193 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ package models + +import ( + "reflect" + "testing" + "time" + + "github.com/k0kubun/pp" +) + +func TestFilterByCvssOver(t *testing.T) { + type in struct { + over float64 + rs ScanResult + } + var tests = []struct { + in in + out ScanResult + }{ + { + in: in{ + over: 7.0, + rs: ScanResult{ + ScannedCves: VulnInfos{ + "CVE-2017-0001": { + CveID: "CVE-2017-0001", + CveContents: NewCveContents( + CveContent{ + Type: NVD, + CveID: "CVE-2017-0001", + Cvss2Score: 7.1, + LastModified: time.Time{}, + }, + ), + }, + "CVE-2017-0002": { + CveID: "CVE-2017-0002", + CveContents: NewCveContents( + CveContent{ + Type: NVD, + CveID: "CVE-2017-0002", + Cvss2Score: 6.9, + LastModified: time.Time{}, + }, + ), + }, + "CVE-2017-0003": { + CveID: "CVE-2017-0003", + CveContents: NewCveContents( + CveContent{ + Type: NVD, + CveID: "CVE-2017-0003", + Cvss2Score: 6.9, + LastModified: time.Time{}, + }, + CveContent{ + Type: JVN, + CveID: "CVE-2017-0003", + Cvss2Score: 7.2, + LastModified: time.Time{}, + }, + ), + }, + }, + }, + }, + out: ScanResult{ + ScannedCves: VulnInfos{ + "CVE-2017-0001": { + CveID: "CVE-2017-0001", + CveContents: NewCveContents( + CveContent{ + Type: NVD, + CveID: "CVE-2017-0001", + Cvss2Score: 7.1, + LastModified: time.Time{}, + }, + ), + }, + "CVE-2017-0003": { + CveID: "CVE-2017-0003", + CveContents: NewCveContents( + CveContent{ + Type: NVD, + CveID: "CVE-2017-0003", + Cvss2Score: 6.9, + LastModified: time.Time{}, + }, + CveContent{ + Type: JVN, + CveID: "CVE-2017-0003", + Cvss2Score: 7.2, + LastModified: time.Time{}, + }, + ), + }, + }, + }, + }, + // OVAL Severity + { + in: in{ + over: 7.0, + rs: ScanResult{ + ScannedCves: VulnInfos{ + "CVE-2017-0001": { + CveID: "CVE-2017-0001", + CveContents: NewCveContents( + CveContent{ + Type: Ubuntu, + CveID: "CVE-2017-0001", + Severity: "HIGH", + LastModified: time.Time{}, + }, + ), + }, + "CVE-2017-0002": { + CveID: "CVE-2017-0002", + CveContents: NewCveContents( + CveContent{ + Type: RedHat, + CveID: "CVE-2017-0002", + Severity: "CRITICAL", + LastModified: time.Time{}, + }, + ), + }, + "CVE-2017-0003": { + CveID: "CVE-2017-0003", + CveContents: NewCveContents( + CveContent{ + Type: Oracle, + CveID: "CVE-2017-0003", + Severity: "IMPORTANT", + LastModified: time.Time{}, + }, + ), + }, + }, + }, + }, + out: ScanResult{ + ScannedCves: VulnInfos{ + "CVE-2017-0001": { + CveID: "CVE-2017-0001", + CveContents: NewCveContents( + CveContent{ + Type: Ubuntu, + CveID: "CVE-2017-0001", + Severity: "HIGH", + LastModified: time.Time{}, + }, + ), + }, + "CVE-2017-0002": { + CveID: "CVE-2017-0002", + CveContents: NewCveContents( + CveContent{ + Type: RedHat, + CveID: "CVE-2017-0002", + Severity: "CRITICAL", + LastModified: time.Time{}, + }, + ), + }, + "CVE-2017-0003": { + CveID: "CVE-2017-0003", + CveContents: NewCveContents( + CveContent{ + Type: Oracle, + CveID: "CVE-2017-0003", + Severity: "IMPORTANT", + LastModified: time.Time{}, + }, + ), + }, + }, + }, + }, + } + for _, tt := range tests { + actual := tt.in.rs.FilterByCvssOver(tt.in.over) + for k := range tt.out.ScannedCves { + if !reflect.DeepEqual(tt.out.ScannedCves[k], actual.ScannedCves[k]) { + o := pp.Sprintf("%v", tt.out.ScannedCves[k]) + a := pp.Sprintf("%v", actual.ScannedCves[k]) + t.Errorf("[%s] expected: %v\n actual: %v\n", k, o, a) + } + } + } +}