diff --git a/models/cvecontents.go b/models/cvecontents.go index 6fa75853..fc3566f6 100644 --- a/models/cvecontents.go +++ b/models/cvecontents.go @@ -18,7 +18,6 @@ along with this program. If not, see . package models import ( - "fmt" "strings" "time" ) @@ -59,274 +58,6 @@ func (v CveContents) Except(exceptCtypes ...CveContentType) (values CveContents) return } -// CveContentCvss has CveContentType and Cvss2 -type CveContentCvss struct { - Type CveContentType - Value Cvss -} - -// CvssType Represent the type of CVSS -type CvssType string - -const ( - // CVSS2 means CVSS vesion2 - CVSS2 CvssType = "2" - - // CVSS3 means CVSS vesion3 - CVSS3 CvssType = "3" -) - -// Cvss has CVSS Score -type Cvss struct { - Type CvssType - Score float64 - Vector string - Severity string -} - -// Format CVSS Score and Vector -func (c Cvss) Format() string { - switch c.Type { - case CVSS2: - return fmt.Sprintf("%3.1f/%s", c.Score, c.Vector) - case CVSS3: - return fmt.Sprintf("%3.1f/CVSS:3.0/%s", c.Score, c.Vector) - } - return "" -} - -func cvss2ScoreToSeverity(score float64) string { - if 7.0 <= score { - return "HIGH" - } else if 4.0 <= score { - return "MEDIUM" - } - return "LOW" -} - -// Cvss2Scores returns CVSS V2 Scores -func (v CveContents) Cvss2Scores() (values []CveContentCvss) { - order := []CveContentType{NVD, RedHat, JVN} - for _, ctype := range order { - if cont, found := v[ctype]; found && 0 < cont.Cvss2Score { - // https://nvd.nist.gov/vuln-metrics/cvss - sev := cont.Severity - if ctype == NVD { - sev = cvss2ScoreToSeverity(cont.Cvss2Score) - } - values = append(values, CveContentCvss{ - Type: ctype, - Value: Cvss{ - Type: CVSS2, - Score: cont.Cvss2Score, - Vector: cont.Cvss2Vector, - Severity: sev, - }, - }) - } - } - - return -} - -// MaxCvss2Score returns Max CVSS V2 Score -func (v CveContents) MaxCvss2Score() CveContentCvss { - order := []CveContentType{NVD, RedHat, JVN} - max := 0.0 - value := CveContentCvss{ - Type: Unknown, - Value: Cvss{Type: CVSS2}, - } - for _, ctype := range order { - if cont, found := v[ctype]; found && max < cont.Cvss2Score { - // https://nvd.nist.gov/vuln-metrics/cvss - sev := cont.Severity - if ctype == NVD { - sev = cvss2ScoreToSeverity(cont.Cvss2Score) - } - value = CveContentCvss{ - Type: ctype, - Value: Cvss{ - Type: CVSS2, - Score: cont.Cvss2Score, - Vector: cont.Cvss2Vector, - Severity: sev, - }, - } - 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 = CveContentCvss{ - Type: ctype, - Value: Cvss{ - Type: 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 -// Use the definition of CVSSv3 because the exact definition of severity and score is not described. -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 -// Value Cvss3 -// } - -// Cvss3 has CVSS v3 Score, Vector and Severity -// type Cvss3 struct { -// Score float64 -// Vector string -// Severity string -// } - -// Format CVSS Score and Vector -// func (c Cvss3) Format() string { -// return fmt.Sprintf("%3.1f/CVSS:3.0/%s", c.Score, c.Vector) -// } - -// func cvss3ScoreToSeverity(score float64) string { -// if 9.0 <= score { -// return "CRITICAL" -// } else if 7.0 <= score { -// return "HIGH" -// } else if 4.0 <= score { -// return "MEDIUM" -// } -// return "LOW" -// } - -// Cvss3Scores returns CVSS V3 Score -func (v CveContents) Cvss3Scores() (values []CveContentCvss) { - // TODO implement NVD - order := []CveContentType{RedHat} - for _, ctype := range order { - if cont, found := v[ctype]; found && 0 < cont.Cvss3Score { - // https://nvd.nist.gov/vuln-metrics/cvss - sev := cont.Severity - values = append(values, CveContentCvss{ - Type: ctype, - Value: Cvss{ - Type: CVSS3, - Score: cont.Cvss3Score, - Vector: cont.Cvss3Vector, - Severity: sev, - }, - }) - } - } - return -} - -// MaxCvss3Score returns Max CVSS V3 Score -func (v CveContents) MaxCvss3Score() CveContentCvss { - // TODO implement NVD - order := []CveContentType{RedHat} - max := 0.0 - value := CveContentCvss{ - Type: Unknown, - Value: Cvss{Type: CVSS3}, - } - for _, ctype := range order { - if cont, found := v[ctype]; found && max < cont.Cvss3Score { - // https://nvd.nist.gov/vuln-metrics/cvss - sev := cont.Severity - value = CveContentCvss{ - Type: ctype, - Value: Cvss{ - Type: CVSS3, - Score: cont.Cvss3Score, - Vector: cont.Cvss3Vector, - Severity: sev, - }, - } - max = cont.Cvss3Score - } - } - return value -} - -// MaxCvssScore returns max CVSS Score -// If there is no CVSS Score, return Severity as a numerical value. -func (v CveContents) MaxCvssScore() CveContentCvss { - v3Max := v.MaxCvss3Score() - v2Max := v.MaxCvss2Score() - max := v3Max - if max.Value.Score < v2Max.Value.Score { - max = v2Max - } - return max -} - -// FormatMaxCvssScore returns Max CVSS Score -func (v CveContents) FormatMaxCvssScore() string { - v2Max := v.MaxCvss2Score() - v3Max := v.MaxCvss3Score() - if v2Max.Value.Score <= v3Max.Value.Score { - return fmt.Sprintf("%3.1f %s (%s)", - v3Max.Value.Score, - strings.ToUpper(v3Max.Value.Severity), - v3Max.Type) - } - return fmt.Sprintf("%3.1f %s (%s)", - v2Max.Value.Score, - strings.ToUpper(v2Max.Value.Severity), - v2Max.Type) -} - // Titles returns tilte (TUI) func (v CveContents) Titles(lang, myFamily string) (values []CveContentStr) { if lang == "ja" { @@ -417,21 +148,23 @@ func (v CveContents) SourceLinks(lang, myFamily, cveID string) (values []CveCont return values } +/* // Severities returns Severities -// func (v CveContents) Severities(myFamily string) (values []CveContentStr) { -// order := CveContentTypes{NVD, NewCveContentType(myFamily)} -// order = append(order, AllCveContetTypes.Except(append(order)...)...) +func (v CveContents) Severities(myFamily string) (values []CveContentStr) { + order := CveContentTypes{NVD, NewCveContentType(myFamily)} + order = append(order, AllCveContetTypes.Except(append(order)...)...) -// for _, ctype := range order { -// if cont, found := v[ctype]; found && 0 < len(cont.Severity) { -// values = append(values, CveContentStr{ -// Type: ctype, -// Value: cont.Severity, -// }) -// } -// } -// return -// } + for _, ctype := range order { + if cont, found := v[ctype]; found && 0 < len(cont.Severity) { + values = append(values, CveContentStr{ + Type: ctype, + Value: cont.Severity, + }) + } + } + return +} +*/ // CveContentCpes has CveContentType and Value type CveContentCpes struct { diff --git a/models/cvecontents_test.go b/models/cvecontents_test.go index f7e942ac..03a186f6 100644 --- a/models/cvecontents_test.go +++ b/models/cvecontents_test.go @@ -44,392 +44,6 @@ func TestExcept(t *testing.T) { } } -func TestCvss2Scores(t *testing.T) { - var tests = []struct { - in CveContents - out []CveContentCvss - }{ - { - in: CveContents{ - JVN: { - Type: JVN, - Severity: "HIGH", - Cvss2Score: 8.2, - Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", - }, - RedHat: { - Type: RedHat, - Severity: "HIGH", - Cvss2Score: 8.0, - Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", - }, - NVD: { - Type: NVD, - Cvss2Score: 8.1, - Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", - // Severity is NIOT included in NVD - }, - }, - out: []CveContentCvss{ - { - Type: NVD, - Value: Cvss{ - Type: CVSS2, - Score: 8.1, - Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", - Severity: "HIGH", - }, - }, - { - Type: RedHat, - Value: Cvss{ - Type: CVSS2, - Score: 8.0, - Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", - Severity: "HIGH", - }, - }, - { - Type: JVN, - Value: Cvss{ - Type: CVSS2, - Score: 8.2, - Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", - Severity: "HIGH", - }, - }, - }, - }, - // Empty - { - in: CveContents{}, - out: nil, - }, - } - for _, tt := range tests { - actual := tt.in.Cvss2Scores() - if !reflect.DeepEqual(tt.out, actual) { - t.Errorf("\nexpected: %v\n actual: %v\n", tt.out, actual) - } - } -} - -func TestMaxCvss2Scores(t *testing.T) { - var tests = []struct { - in CveContents - out CveContentCvss - }{ - { - in: CveContents{ - JVN: { - Type: JVN, - Severity: "HIGH", - Cvss2Score: 8.2, - Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", - }, - RedHat: { - Type: RedHat, - Severity: "HIGH", - Cvss2Score: 8.0, - Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", - }, - NVD: { - Type: NVD, - Cvss2Score: 8.1, - Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", - // Severity is NIOT included in NVD - }, - }, - out: CveContentCvss{ - Type: JVN, - Value: Cvss{ - Type: CVSS2, - Score: 8.2, - Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", - Severity: "HIGH", - }, - }, - }, - // Severity in OVAL - { - in: CveContents{ - Ubuntu: { - Type: Ubuntu, - Severity: "HIGH", - }, - }, - out: CveContentCvss{ - Type: Ubuntu, - Value: Cvss{ - Type: CVSS2, - Score: 10, - Severity: "HIGH", - }, - }, - }, - // Empty - { - in: CveContents{}, - out: CveContentCvss{ - Type: Unknown, - Value: Cvss{ - Type: CVSS2, - Score: 0.0, - Vector: "", - Severity: "", - }, - }, - }, - } - for _, tt := range tests { - actual := tt.in.MaxCvss2Score() - if !reflect.DeepEqual(tt.out, actual) { - t.Errorf("\nexpected: %v\n actual: %v\n", tt.out, actual) - } - } -} - -func TestCvss3Scores(t *testing.T) { - var tests = []struct { - in CveContents - out []CveContentCvss - }{ - { - in: CveContents{ - RedHat: { - Type: RedHat, - Severity: "HIGH", - Cvss3Score: 8.0, - Cvss3Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", - }, - NVD: { - Type: NVD, - Cvss3Score: 8.1, - Cvss3Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", - // Severity is NIOT included in NVD - }, - }, - out: []CveContentCvss{ - { - Type: RedHat, - Value: Cvss{ - Type: CVSS3, - Score: 8.0, - Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", - Severity: "HIGH", - }, - }, - }, - }, - // Empty - { - in: CveContents{}, - out: nil, - }, - } - for _, tt := range tests { - actual := tt.in.Cvss3Scores() - if !reflect.DeepEqual(tt.out, actual) { - t.Errorf("\nexpected: %v\n actual: %v\n", tt.out, actual) - } - } -} - -func TestMaxCvss3Scores(t *testing.T) { - var tests = []struct { - in CveContents - out CveContentCvss - }{ - { - in: CveContents{ - RedHat: { - Type: RedHat, - Severity: "HIGH", - Cvss3Score: 8.0, - Cvss3Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", - }, - }, - out: CveContentCvss{ - Type: RedHat, - Value: Cvss{ - Type: CVSS3, - Score: 8.0, - Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", - Severity: "HIGH", - }, - }, - }, - // Empty - { - in: CveContents{}, - out: CveContentCvss{ - Type: Unknown, - Value: Cvss{ - Type: CVSS3, - Score: 0.0, - Vector: "", - Severity: "", - }, - }, - }, - } - for _, tt := range tests { - actual := tt.in.MaxCvss3Score() - if !reflect.DeepEqual(tt.out, actual) { - t.Errorf("\nexpected: %v\n actual: %v\n", tt.out, actual) - } - } -} - -func TestMaxCvssScores(t *testing.T) { - var tests = []struct { - in CveContents - out CveContentCvss - }{ - { - in: CveContents{ - NVD: { - Type: NVD, - Cvss3Score: 7.0, - }, - RedHat: { - Type: RedHat, - Cvss2Score: 8.0, - }, - }, - out: CveContentCvss{ - Type: RedHat, - Value: Cvss{ - Type: CVSS2, - Score: 8.0, - }, - }, - }, - { - in: CveContents{ - RedHat: { - Type: RedHat, - Cvss3Score: 8.0, - }, - }, - out: CveContentCvss{ - Type: RedHat, - Value: Cvss{ - Type: CVSS3, - Score: 8.0, - }, - }, - }, - { - in: CveContents{ - Ubuntu: { - Type: Ubuntu, - Severity: "HIGH", - }, - }, - out: CveContentCvss{ - Type: Ubuntu, - Value: Cvss{ - Type: CVSS2, - Score: 10.0, - Severity: "HIGH", - }, - }, - }, - { - in: CveContents{ - Ubuntu: { - Type: Ubuntu, - Severity: "MEDIUM", - }, - NVD: { - Type: NVD, - Cvss2Score: 7.0, - }, - }, - out: CveContentCvss{ - Type: NVD, - Value: Cvss{ - Type: CVSS2, - Score: 7.0, - Severity: "HIGH", - }, - }, - }, - // Empty - { - in: CveContents{}, - out: CveContentCvss{ - Type: Unknown, - Value: Cvss{ - Type: CVSS3, - Score: 0, - }, - }, - }, - } - for i, tt := range tests { - actual := tt.in.MaxCvssScore() - if !reflect.DeepEqual(tt.out, actual) { - t.Errorf("\n[%d] expected: %v\n actual: %v\n", i, tt.out, actual) - } - } -} - -func TestFormatMaxCvssScore(t *testing.T) { - var tests = []struct { - in CveContents - out string - }{ - { - in: CveContents{ - JVN: { - Type: JVN, - Severity: "HIGH", - Cvss2Score: 8.3, - }, - RedHat: { - Type: RedHat, - Severity: "HIGH", - Cvss3Score: 8.0, - }, - NVD: { - Type: NVD, - Cvss2Score: 8.1, - // Severity is NIOT included in NVD - }, - }, - out: "8.3 HIGH (jvn)", - }, - { - in: CveContents{ - JVN: { - Type: JVN, - Severity: "HIGH", - Cvss2Score: 8.3, - }, - RedHat: { - Type: RedHat, - Severity: "HIGH", - Cvss2Score: 8.0, - Cvss3Score: 9.9, - }, - NVD: { - Type: NVD, - Cvss2Score: 8.1, - }, - }, - out: "9.9 HIGH (redhat)", - }, - } - for _, tt := range tests { - actual := tt.in.FormatMaxCvssScore() - if !reflect.DeepEqual(tt.out, actual) { - t.Errorf("\nexpected: %v\n actual: %v\n", tt.out, actual) - } - } -} - func TestTitles(t *testing.T) { type in struct { lang string diff --git a/models/scanresults.go b/models/scanresults.go index 284724fb..082ff2f1 100644 --- a/models/scanresults.go +++ b/models/scanresults.go @@ -140,8 +140,8 @@ func (r ScanResult) ConvertJvnToModel(cveID string, jvn cvedict.Jvn) *CveContent // FilterByCvssOver is filter function. func (r ScanResult) FilterByCvssOver(over float64) ScanResult { filtered := r.ScannedCves.Find(func(v VulnInfo) bool { - v2Max := v.CveContents.MaxCvss2Score() - v3Max := v.CveContents.MaxCvss3Score() + v2Max := v.MaxCvss2Score() + v3Max := v.MaxCvss3Score() max := v2Max.Value.Score if max < v3Max.Value.Score { max = v3Max.Value.Score diff --git a/models/vulninfos.go b/models/vulninfos.go index d14df562..5b458cb3 100644 --- a/models/vulninfos.go +++ b/models/vulninfos.go @@ -45,8 +45,8 @@ func (v VulnInfos) Find(f func(VulnInfo) bool) VulnInfos { // FindScoredVulns return scored vulnerabilities func (v VulnInfos) FindScoredVulns() VulnInfos { return v.Find(func(vv VulnInfo) bool { - if 0 < vv.CveContents.MaxCvss2Score().Value.Score || - 0 < vv.CveContents.MaxCvss3Score().Value.Score { + if 0 < vv.MaxCvss2Score().Value.Score || + 0 < vv.MaxCvss3Score().Value.Score { return true } return false @@ -59,8 +59,8 @@ func (v VulnInfos) ToSortedSlice() (sorted []VulnInfo) { sorted = append(sorted, v[k]) } sort.Slice(sorted, func(i, j int) bool { - maxI := sorted[i].CveContents.MaxCvssScore() - maxJ := sorted[j].CveContents.MaxCvssScore() + maxI := sorted[i].MaxCvssScore() + maxJ := sorted[j].MaxCvssScore() if maxI.Value.Score != maxJ.Value.Score { return maxJ.Value.Score < maxI.Value.Score } @@ -73,9 +73,9 @@ func (v VulnInfos) ToSortedSlice() (sorted []VulnInfo) { func (v VulnInfos) CountGroupBySeverity() map[string]int { m := map[string]int{} for _, vInfo := range v { - score := vInfo.CveContents.MaxCvss2Score().Value.Score + score := vInfo.MaxCvss2Score().Value.Score if score < 0.1 { - score = vInfo.CveContents.MaxCvss3Score().Value.Score + score = vInfo.MaxCvss3Score().Value.Score } switch { case 7.0 <= score: @@ -114,6 +114,296 @@ type VulnInfo struct { CveContents CveContents } +// Cvss2Scores returns CVSS V2 Scores +func (v VulnInfo) Cvss2Scores() (values []CveContentCvss) { + order := []CveContentType{NVD, RedHat, JVN} + for _, ctype := range order { + if cont, found := v.CveContents[ctype]; found && 0 < cont.Cvss2Score { + // https://nvd.nist.gov/vuln-metrics/cvss + sev := cont.Severity + if ctype == NVD { + sev = cvss2ScoreToSeverity(cont.Cvss2Score) + } + values = append(values, CveContentCvss{ + Type: ctype, + Value: Cvss{ + Type: CVSS2, + Score: cont.Cvss2Score, + Vector: cont.Cvss2Vector, + Severity: strings.ToUpper(sev), + }, + }) + } + } + + for _, adv := range v.DistroAdvisories { + if adv.Severity != "" { + values = append(values, CveContentCvss{ + Type: "Vendor", + Value: Cvss{ + Type: CVSS2, + Score: severityToV2ScoreRoughly(adv.Severity), + Vector: "-", + Severity: strings.ToUpper(adv.Severity), + }, + }) + } + } + + return +} + +// Cvss3Scores returns CVSS V3 Score +func (v VulnInfo) Cvss3Scores() (values []CveContentCvss) { + // TODO implement NVD + order := []CveContentType{RedHat} + for _, ctype := range order { + if cont, found := v.CveContents[ctype]; found && 0 < cont.Cvss3Score { + // https://nvd.nist.gov/vuln-metrics/cvss + sev := cont.Severity + values = append(values, CveContentCvss{ + Type: ctype, + Value: Cvss{ + Type: CVSS3, + Score: cont.Cvss3Score, + Vector: cont.Cvss3Vector, + Severity: sev, + }, + }) + } + } + return +} + +// MaxCvss3Score returns Max CVSS V3 Score +func (v VulnInfo) MaxCvss3Score() CveContentCvss { + // TODO implement NVD + order := []CveContentType{RedHat} + max := 0.0 + value := CveContentCvss{ + Type: Unknown, + Value: Cvss{Type: CVSS3}, + } + for _, ctype := range order { + if cont, found := v.CveContents[ctype]; found && max < cont.Cvss3Score { + // https://nvd.nist.gov/vuln-metrics/cvss + sev := cont.Severity + value = CveContentCvss{ + Type: ctype, + Value: Cvss{ + Type: CVSS3, + Score: cont.Cvss3Score, + Vector: cont.Cvss3Vector, + Severity: sev, + }, + } + max = cont.Cvss3Score + } + } + return value +} + +// MaxCvssScore returns max CVSS Score +// If there is no CVSS Score, return Severity as a numerical value. +func (v VulnInfo) MaxCvssScore() CveContentCvss { + v3Max := v.MaxCvss3Score() + v2Max := v.MaxCvss2Score() + max := v3Max + if max.Value.Score < v2Max.Value.Score { + max = v2Max + } + return max +} + +// MaxCvss2Score returns Max CVSS V2 Score +func (v VulnInfo) MaxCvss2Score() CveContentCvss { + order := []CveContentType{NVD, RedHat, JVN} + max := 0.0 + value := CveContentCvss{ + Type: Unknown, + Value: Cvss{Type: CVSS2}, + } + for _, ctype := range order { + if cont, found := v.CveContents[ctype]; found && max < cont.Cvss2Score { + // https://nvd.nist.gov/vuln-metrics/cvss + sev := cont.Severity + if ctype == NVD { + sev = cvss2ScoreToSeverity(cont.Cvss2Score) + } + value = CveContentCvss{ + Type: ctype, + Value: Cvss{ + Type: CVSS2, + Score: cont.Cvss2Score, + Vector: cont.Cvss2Vector, + Severity: sev, + }, + } + max = cont.Cvss2Score + } + } + if 0 < max { + return value + } + + // If CVSS score isn't on NVD, RedHat and JVN, use OVAL and advisory Severity. + // Convert severity to cvss srore roughly, then returns max severity. + // Only Ubuntu, RedHat and Oracle OVAL has severity data in OVAL. + order = []CveContentType{Ubuntu, RedHat, Oracle} + for _, ctype := range order { + if cont, found := v.CveContents[ctype]; found && 0 < len(cont.Severity) { + score := severityToV2ScoreRoughly(cont.Severity) + if max < score { + value = CveContentCvss{ + Type: ctype, + Value: Cvss{ + Type: CVSS2, + Score: score, + Vector: cont.Cvss2Vector, + Severity: cont.Severity, + }, + } + } + max = score + } + } + + // Only RedHat, Oracle and Amazon has severity data in advisory. + for _, adv := range v.DistroAdvisories { + if adv.Severity != "" { + score := severityToV2ScoreRoughly(adv.Severity) + if max < score { + value = CveContentCvss{ + Type: "Vendor", + Value: Cvss{ + Type: CVSS2, + Score: score, + Vector: "-", + Severity: adv.Severity, + }, + } + } + } + } + return value +} + +// CveContentCvss has CveContentType and Cvss2 +type CveContentCvss struct { + Type CveContentType + Value Cvss +} + +// CvssType Represent the type of CVSS +type CvssType string + +const ( + // CVSS2 means CVSS vesion2 + CVSS2 CvssType = "2" + + // CVSS3 means CVSS vesion3 + CVSS3 CvssType = "3" +) + +// Cvss has CVSS Score +type Cvss struct { + Type CvssType + Score float64 + Vector string + Severity string +} + +// Format CVSS Score and Vector +func (c Cvss) Format() string { + switch c.Type { + case CVSS2: + return fmt.Sprintf("%3.1f/%s", c.Score, c.Vector) + case CVSS3: + return fmt.Sprintf("%3.1f/CVSS:3.0/%s", c.Score, c.Vector) + } + return "" +} + +func cvss2ScoreToSeverity(score float64) string { + if 7.0 <= score { + return "HIGH" + } else if 4.0 <= score { + return "MEDIUM" + } + return "LOW" +} + +// Amazon Linux Security Advisory +// Critical, Important, Medium, Low +// https://alas.aws.amazon.com/ +// +// RedHat, Oracle OVAL +// Critical, Important, Moderate, Low +// https://access.redhat.com/security/updates/classification +// +// Ubuntu OVAL +// Critical, High, Medium, Low +// https://wiki.ubuntu.com/Bugs/Importance +// https://people.canonical.com/~ubuntu-security/cve/priority.html +func severityToV2ScoreRoughly(severity string) float64 { + switch strings.ToUpper(severity) { + case "CRITICAL": + return 10.0 + case "IMPORTANT", "HIGH": + return 8.9 + case "MODERATE", "MEDIUM": + return 6.9 + case "LOW": + return 3.9 + } + return 0 +} + +// CveContentCvss3 has CveContentType and Cvss3 +// type CveContentCvss3 struct { +// Type CveContentType +// Value Cvss3 +// } + +// Cvss3 has CVSS v3 Score, Vector and Severity +// type Cvss3 struct { +// Score float64 +// Vector string +// Severity string +// } + +// Format CVSS Score and Vector +// func (c Cvss3) Format() string { +// return fmt.Sprintf("%3.1f/CVSS:3.0/%s", c.Score, c.Vector) +// } + +// func cvss3ScoreToSeverity(score float64) string { +// if 9.0 <= score { +// return "CRITICAL" +// } else if 7.0 <= score { +// return "HIGH" +// } else if 4.0 <= score { +// return "MEDIUM" +// } +// return "LOW" +// } + +// FormatMaxCvssScore returns Max CVSS Score +func (v VulnInfo) FormatMaxCvssScore() string { + v2Max := v.MaxCvss2Score() + v3Max := v.MaxCvss3Score() + if v2Max.Value.Score <= v3Max.Value.Score { + return fmt.Sprintf("%3.1f %s (%s)", + v3Max.Value.Score, + strings.ToUpper(v3Max.Value.Severity), + v3Max.Type) + } + return fmt.Sprintf("%3.1f %s (%s)", + v2Max.Value.Score, + strings.ToUpper(v2Max.Value.Severity), + v2Max.Type) +} + // Cvss2CalcURL returns CVSS v2 caluclator's URL func (v VulnInfo) Cvss2CalcURL() string { return "https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator?name=" + v.CveID diff --git a/models/vulninfos_test.go b/models/vulninfos_test.go index 7b4f8f40..067b5ed3 100644 --- a/models/vulninfos_test.go +++ b/models/vulninfos_test.go @@ -247,3 +247,411 @@ func TestToSortedSlice(t *testing.T) { } } } + +func TestCvss2Scores(t *testing.T) { + var tests = []struct { + in VulnInfo + out []CveContentCvss + }{ + { + in: VulnInfo{ + CveContents: CveContents{ + JVN: { + Type: JVN, + Severity: "HIGH", + Cvss2Score: 8.2, + Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", + }, + RedHat: { + Type: RedHat, + Severity: "HIGH", + Cvss2Score: 8.0, + Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", + }, + NVD: { + Type: NVD, + Cvss2Score: 8.1, + Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", + // Severity is NIOT included in NVD + }, + }, + }, + out: []CveContentCvss{ + { + Type: NVD, + Value: Cvss{ + Type: CVSS2, + Score: 8.1, + Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", + Severity: "HIGH", + }, + }, + { + Type: RedHat, + Value: Cvss{ + Type: CVSS2, + Score: 8.0, + Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", + Severity: "HIGH", + }, + }, + { + Type: JVN, + Value: Cvss{ + Type: CVSS2, + Score: 8.2, + Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", + Severity: "HIGH", + }, + }, + }, + }, + // Empty + { + in: VulnInfo{}, + out: nil, + }, + } + for _, tt := range tests { + actual := tt.in.Cvss2Scores() + if !reflect.DeepEqual(tt.out, actual) { + t.Errorf("\nexpected: %v\n actual: %v\n", tt.out, actual) + } + } +} + +func TestMaxCvss2Scores(t *testing.T) { + var tests = []struct { + in VulnInfo + out CveContentCvss + }{ + { + in: VulnInfo{ + CveContents: CveContents{ + JVN: { + Type: JVN, + Severity: "HIGH", + Cvss2Score: 8.2, + Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", + }, + RedHat: { + Type: RedHat, + Severity: "HIGH", + Cvss2Score: 8.0, + Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", + }, + NVD: { + Type: NVD, + Cvss2Score: 8.1, + Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", + // Severity is NIOT included in NVD + }, + }, + }, + out: CveContentCvss{ + Type: JVN, + Value: Cvss{ + Type: CVSS2, + Score: 8.2, + Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", + Severity: "HIGH", + }, + }, + }, + // Severity in OVAL + { + in: VulnInfo{ + CveContents: CveContents{ + Ubuntu: { + Type: Ubuntu, + Severity: "HIGH", + }, + }, + }, + out: CveContentCvss{ + Type: Ubuntu, + Value: Cvss{ + Type: CVSS2, + Score: 8.9, + Severity: "HIGH", + }, + }, + }, + // Empty + { + in: VulnInfo{}, + out: CveContentCvss{ + Type: Unknown, + Value: Cvss{ + Type: CVSS2, + Score: 0.0, + Vector: "", + Severity: "", + }, + }, + }, + } + for _, tt := range tests { + actual := tt.in.MaxCvss2Score() + if !reflect.DeepEqual(tt.out, actual) { + t.Errorf("\nexpected: %v\n actual: %v\n", tt.out, actual) + } + } +} + +func TestCvss3Scores(t *testing.T) { + var tests = []struct { + in VulnInfo + out []CveContentCvss + }{ + { + in: VulnInfo{ + CveContents: CveContents{ + RedHat: { + Type: RedHat, + Severity: "HIGH", + Cvss3Score: 8.0, + Cvss3Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + }, + NVD: { + Type: NVD, + Cvss3Score: 8.1, + Cvss3Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + // Severity is NIOT included in NVD + }, + }, + }, + out: []CveContentCvss{ + { + Type: RedHat, + Value: Cvss{ + Type: CVSS3, + Score: 8.0, + Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + Severity: "HIGH", + }, + }, + }, + }, + // Empty + { + in: VulnInfo{}, + out: nil, + }, + } + for _, tt := range tests { + actual := tt.in.Cvss3Scores() + if !reflect.DeepEqual(tt.out, actual) { + t.Errorf("\nexpected: %v\n actual: %v\n", tt.out, actual) + } + } +} + +func TestMaxCvss3Scores(t *testing.T) { + var tests = []struct { + in VulnInfo + out CveContentCvss + }{ + { + in: VulnInfo{ + CveContents: CveContents{ + RedHat: { + Type: RedHat, + Severity: "HIGH", + Cvss3Score: 8.0, + Cvss3Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + }, + }, + }, + out: CveContentCvss{ + Type: RedHat, + Value: Cvss{ + Type: CVSS3, + Score: 8.0, + Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", + Severity: "HIGH", + }, + }, + }, + // Empty + { + in: VulnInfo{}, + out: CveContentCvss{ + Type: Unknown, + Value: Cvss{ + Type: CVSS3, + Score: 0.0, + Vector: "", + Severity: "", + }, + }, + }, + } + for _, tt := range tests { + actual := tt.in.MaxCvss3Score() + if !reflect.DeepEqual(tt.out, actual) { + t.Errorf("\nexpected: %v\n actual: %v\n", tt.out, actual) + } + } +} + +func TestMaxCvssScores(t *testing.T) { + var tests = []struct { + in VulnInfo + out CveContentCvss + }{ + { + in: VulnInfo{ + CveContents: CveContents{ + NVD: { + Type: NVD, + Cvss3Score: 7.0, + }, + RedHat: { + Type: RedHat, + Cvss2Score: 8.0, + }, + }, + }, + out: CveContentCvss{ + Type: RedHat, + Value: Cvss{ + Type: CVSS2, + Score: 8.0, + }, + }, + }, + { + in: VulnInfo{ + CveContents: CveContents{ + RedHat: { + Type: RedHat, + Cvss3Score: 8.0, + }, + }, + }, + out: CveContentCvss{ + Type: RedHat, + Value: Cvss{ + Type: CVSS3, + Score: 8.0, + }, + }, + }, + { + in: VulnInfo{ + CveContents: CveContents{ + Ubuntu: { + Type: Ubuntu, + Severity: "HIGH", + }, + }, + }, + out: CveContentCvss{ + Type: Ubuntu, + Value: Cvss{ + Type: CVSS2, + Score: 8.9, + Severity: "HIGH", + }, + }, + }, + { + in: VulnInfo{ + CveContents: CveContents{ + Ubuntu: { + Type: Ubuntu, + Severity: "MEDIUM", + }, + NVD: { + Type: NVD, + Cvss2Score: 7.0, + }, + }, + }, + out: CveContentCvss{ + Type: NVD, + Value: Cvss{ + Type: CVSS2, + Score: 7.0, + Severity: "HIGH", + }, + }, + }, + // Empty + { + in: VulnInfo{}, + out: CveContentCvss{ + Type: Unknown, + Value: Cvss{ + Type: CVSS3, + Score: 0, + }, + }, + }, + } + for i, tt := range tests { + actual := tt.in.MaxCvssScore() + if !reflect.DeepEqual(tt.out, actual) { + t.Errorf("\n[%d] expected: %v\n actual: %v\n", i, tt.out, actual) + } + } +} + +func TestFormatMaxCvssScore(t *testing.T) { + var tests = []struct { + in VulnInfo + out string + }{ + { + in: VulnInfo{ + CveContents: CveContents{ + JVN: { + Type: JVN, + Severity: "HIGH", + Cvss2Score: 8.3, + }, + RedHat: { + Type: RedHat, + Severity: "HIGH", + Cvss3Score: 8.0, + }, + NVD: { + Type: NVD, + Cvss2Score: 8.1, + // Severity is NIOT included in NVD + }, + }, + }, + out: "8.3 HIGH (jvn)", + }, + { + in: VulnInfo{ + CveContents: CveContents{ + JVN: { + Type: JVN, + Severity: "HIGH", + Cvss2Score: 8.3, + }, + RedHat: { + Type: RedHat, + Severity: "HIGH", + Cvss2Score: 8.0, + Cvss3Score: 9.9, + }, + NVD: { + Type: NVD, + Cvss2Score: 8.1, + }, + }, + }, + out: "9.9 HIGH (redhat)", + }, + } + for _, tt := range tests { + actual := tt.in.FormatMaxCvssScore() + if !reflect.DeepEqual(tt.out, actual) { + t.Errorf("\nexpected: %v\n actual: %v\n", tt.out, actual) + } + } +} diff --git a/report/report.go b/report/report.go index 594c2035..1206a900 100644 --- a/report/report.go +++ b/report/report.go @@ -158,10 +158,13 @@ func fillWithOval(r *models.ScanResult) (err error) { ovalClient = oval.NewCentOS() //use RedHat's OVAL ovalFamily = c.RedHat - //TODO implement OracleLinux + //TODO // case c.Oracle: // ovalClient = oval.New() // ovalFamily = c.Oracle + // case c.Suse: + // ovalClient = oval.New() + // ovalFamily = c.Oracle case c.Amazon, c.Oracle, c.Raspbian, c.FreeBSD: return nil default: diff --git a/report/slack.go b/report/slack.go index 38ddf1b3..53af4673 100644 --- a/report/slack.go +++ b/report/slack.go @@ -216,7 +216,7 @@ func toSlackAttachments(r models.ScanResult) (attaches []*attachment) { Short: true, }, }, - Color: color(vinfo.CveContents.MaxCvssScore().Value.Score), + Color: color(vinfo.MaxCvssScore().Value.Score), } attaches = append(attaches, &a) } @@ -238,9 +238,9 @@ func color(cvssScore float64) string { } func attachmentText(vinfo models.VulnInfo, osFamily string) string { - maxCvss := vinfo.CveContents.MaxCvssScore() + maxCvss := vinfo.MaxCvssScore() vectors := []string{} - for _, cvss := range vinfo.CveContents.Cvss2Scores() { + for _, cvss := range vinfo.Cvss2Scores() { calcURL := "" switch cvss.Value.Type { case models.CVSS2: diff --git a/report/tui.go b/report/tui.go index bdf52348..1405e6d1 100644 --- a/report/tui.go +++ b/report/tui.go @@ -641,7 +641,7 @@ func summaryLines() string { summary := vinfo.CveContents.Summaries( config.Conf.Lang, currentScanResult.Family)[0].Value cvssScore := fmt.Sprintf("| %4.1f", - vinfo.CveContents.MaxCvssScore().Value.Score) + vinfo.MaxCvssScore().Value.Score) var cols []string cols = []string{ @@ -794,7 +794,7 @@ func detailLines() (string, error) { data := dataForTmpl{ CveID: vinfo.CveID, - Cvsses: append(vinfo.CveContents.Cvss3Scores(), vinfo.CveContents.Cvss2Scores()...), + Cvsses: append(vinfo.Cvss3Scores(), vinfo.Cvss2Scores()...), Summary: fmt.Sprintf("%s (%s)", summary.Value, summary.Type), Confidence: vinfo.Confidence, Cwes: vinfo.CveContents.CweIDs(r.Family), @@ -818,7 +818,7 @@ const mdTemplate = ` CVSS Scores -------------- {{range .Cvsses -}} -* {{.Value.Format}} ({{.Type}}) +* {{.Value.Severity}} {{.Value.Format}} ({{.Type}}) {{end}} Summary diff --git a/report/util.go b/report/util.go index f52c9f66..30a3cb91 100644 --- a/report/util.go +++ b/report/util.go @@ -120,18 +120,18 @@ func formatShortPlainText(r models.ScanResult) string { } cvsses := "" - for _, cvss := range vuln.CveContents.Cvss2Scores() { + for _, cvss := range vuln.Cvss2Scores() { cvsses += fmt.Sprintf("%s (%s)\n", cvss.Value.Format(), cvss.Type) } cvsses += vuln.Cvss2CalcURL() + "\n" - for _, cvss := range vuln.CveContents.Cvss3Scores() { + for _, cvss := range vuln.Cvss3Scores() { cvsses += fmt.Sprintf("%s (%s)\n", cvss.Value.Format(), cvss.Type) } - if 0 < len(vuln.CveContents.Cvss3Scores()) { + if 0 < len(vuln.Cvss3Scores()) { cvsses += vuln.Cvss3CalcURL() + "\n" } - maxCvss := vuln.CveContents.FormatMaxCvssScore() + maxCvss := vuln.FormatMaxCvssScore() rightCol := fmt.Sprintf(`%s %s --- @@ -186,17 +186,17 @@ func formatFullPlainText(r models.ScanResult) string { for _, vuln := range vulns.ToSortedSlice() { table.AddRow(vuln.CveID) table.AddRow("----------------") - table.AddRow("Max Score", vuln.CveContents.FormatMaxCvssScore()) - for _, cvss := range vuln.CveContents.Cvss2Scores() { + table.AddRow("Max Score", vuln.FormatMaxCvssScore()) + for _, cvss := range vuln.Cvss2Scores() { table.AddRow(cvss.Type, cvss.Value.Format()) } - for _, cvss := range vuln.CveContents.Cvss3Scores() { + for _, cvss := range vuln.Cvss3Scores() { table.AddRow(cvss.Type, cvss.Value.Format()) } - if 0 < len(vuln.CveContents.Cvss2Scores()) { + if 0 < len(vuln.Cvss2Scores()) { table.AddRow("CVSSv2 Calc", vuln.Cvss2CalcURL()) } - if 0 < len(vuln.CveContents.Cvss3Scores()) { + if 0 < len(vuln.Cvss3Scores()) { table.AddRow("CVSSv3 Calc", vuln.Cvss3CalcURL()) } table.AddRow("Summary", vuln.CveContents.Summaries(