Use CVSS seveirty of distro advisory when no entiry in NVD and OVAL

This commit is contained in:
kota kanbe
2017-07-30 15:21:26 +09:00
parent 8b6a283114
commit 27724a2faf
9 changed files with 740 additions and 692 deletions

View File

@@ -18,7 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
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 {

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}
}
}

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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(