feat(cve/mitre): support go-cve-dictionary:mitre (#1978)
* feat(cve/mitre): support go-cve-dictionary:mitre * chore: adopt reviewer comment * refactor(models): refactor CveContents method
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"cmp"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/future-architect/vuls/constant"
|
||||
)
|
||||
|
||||
@@ -15,18 +19,14 @@ type CveContents map[CveContentType][]CveContent
|
||||
func NewCveContents(conts ...CveContent) CveContents {
|
||||
m := CveContents{}
|
||||
for _, cont := range conts {
|
||||
if cont.Type == Jvn {
|
||||
found := false
|
||||
for _, cveCont := range m[cont.Type] {
|
||||
if cont.SourceLink == cveCont.SourceLink {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
switch cont.Type {
|
||||
case Jvn:
|
||||
if !slices.ContainsFunc(m[cont.Type], func(e CveContent) bool {
|
||||
return cont.SourceLink == e.SourceLink
|
||||
}) {
|
||||
m[cont.Type] = append(m[cont.Type], cont)
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
m[cont.Type] = []CveContent{cont}
|
||||
}
|
||||
}
|
||||
@@ -43,14 +43,7 @@ type CveContentStr struct {
|
||||
func (v CveContents) Except(exceptCtypes ...CveContentType) (values CveContents) {
|
||||
values = CveContents{}
|
||||
for ctype, content := range v {
|
||||
found := false
|
||||
for _, exceptCtype := range exceptCtypes {
|
||||
if ctype == exceptCtype {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
if !slices.Contains(exceptCtypes, ctype) {
|
||||
values[ctype] = content
|
||||
}
|
||||
}
|
||||
@@ -63,43 +56,51 @@ func (v CveContents) PrimarySrcURLs(lang, myFamily, cveID string, confidences Co
|
||||
return
|
||||
}
|
||||
|
||||
if conts, found := v[Nvd]; found {
|
||||
for _, cont := range conts {
|
||||
for _, r := range cont.References {
|
||||
for _, t := range r.Tags {
|
||||
if t == "Vendor Advisory" {
|
||||
values = append(values, CveContentStr{Nvd, r.Link})
|
||||
for _, ctype := range append(append(CveContentTypes{Mitre, Nvd, Jvn}, GetCveContentTypes(myFamily)...), GitHub) {
|
||||
for _, cont := range v[ctype] {
|
||||
switch ctype {
|
||||
case Nvd:
|
||||
for _, r := range cont.References {
|
||||
if slices.Contains(r.Tags, "Vendor Advisory") {
|
||||
if !slices.ContainsFunc(values, func(e CveContentStr) bool {
|
||||
return e.Type == ctype && e.Value == r.Link
|
||||
}) {
|
||||
values = append(values, CveContentStr{
|
||||
Type: ctype,
|
||||
Value: r.Link,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
order := append(append(CveContentTypes{Nvd}, GetCveContentTypes(myFamily)...), GitHub)
|
||||
for _, ctype := range order {
|
||||
if conts, found := v[ctype]; found {
|
||||
for _, cont := range conts {
|
||||
if cont.SourceLink == "" {
|
||||
continue
|
||||
if cont.SourceLink != "" && !slices.ContainsFunc(values, func(e CveContentStr) bool {
|
||||
return e.Type == ctype && e.Value == cont.SourceLink
|
||||
}) {
|
||||
values = append(values, CveContentStr{
|
||||
Type: ctype,
|
||||
Value: cont.SourceLink,
|
||||
})
|
||||
}
|
||||
values = append(values, CveContentStr{ctype, cont.SourceLink})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jvnMatch := false
|
||||
for _, confidence := range confidences {
|
||||
if confidence.DetectionMethod == JvnVendorProductMatchStr {
|
||||
jvnMatch = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if lang == "ja" || jvnMatch {
|
||||
if conts, found := v[Jvn]; found {
|
||||
for _, cont := range conts {
|
||||
if 0 < len(cont.SourceLink) {
|
||||
values = append(values, CveContentStr{Jvn, cont.SourceLink})
|
||||
case Jvn:
|
||||
if lang == "ja" || slices.ContainsFunc(confidences, func(e Confidence) bool {
|
||||
return e.DetectionMethod == JvnVendorProductMatchStr
|
||||
}) {
|
||||
if cont.SourceLink != "" && !slices.ContainsFunc(values, func(e CveContentStr) bool {
|
||||
return e.Type == ctype && e.Value == cont.SourceLink
|
||||
}) {
|
||||
values = append(values, CveContentStr{
|
||||
Type: ctype,
|
||||
Value: cont.SourceLink,
|
||||
})
|
||||
}
|
||||
}
|
||||
default:
|
||||
if cont.SourceLink != "" && !slices.ContainsFunc(values, func(e CveContentStr) bool {
|
||||
return e.Type == ctype && e.Value == cont.SourceLink
|
||||
}) {
|
||||
values = append(values, CveContentStr{
|
||||
Type: ctype,
|
||||
Value: cont.SourceLink,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,7 +109,7 @@ func (v CveContents) PrimarySrcURLs(lang, myFamily, cveID string, confidences Co
|
||||
if len(values) == 0 && strings.HasPrefix(cveID, "CVE") {
|
||||
return []CveContentStr{{
|
||||
Type: Nvd,
|
||||
Value: "https://nvd.nist.gov/vuln/detail/" + cveID,
|
||||
Value: fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", cveID),
|
||||
}}
|
||||
}
|
||||
return values
|
||||
@@ -116,17 +117,10 @@ func (v CveContents) PrimarySrcURLs(lang, myFamily, cveID string, confidences Co
|
||||
|
||||
// PatchURLs returns link of patch
|
||||
func (v CveContents) PatchURLs() (urls []string) {
|
||||
conts, found := v[Nvd]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
for _, cont := range conts {
|
||||
for _, cont := range v[Nvd] {
|
||||
for _, r := range cont.References {
|
||||
for _, t := range r.Tags {
|
||||
if t == "Patch" {
|
||||
urls = append(urls, r.Link)
|
||||
}
|
||||
if slices.Contains(r.Tags, "Patch") && !slices.Contains(urls, r.Link) {
|
||||
urls = append(urls, r.Link)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,21 +139,24 @@ func (v CveContents) Cpes(myFamily string) (values []CveContentCpes) {
|
||||
order = append(order, AllCveContetTypes.Except(order...)...)
|
||||
|
||||
for _, ctype := range order {
|
||||
if conts, found := v[ctype]; found {
|
||||
for _, cont := range conts {
|
||||
if 0 < len(cont.Cpes) {
|
||||
values = append(values, CveContentCpes{
|
||||
Type: ctype,
|
||||
Value: cont.Cpes,
|
||||
})
|
||||
}
|
||||
for _, cont := range v[ctype] {
|
||||
if len(cont.Cpes) == 0 {
|
||||
continue
|
||||
}
|
||||
if !slices.ContainsFunc(values, func(e CveContentCpes) bool {
|
||||
return e.Type == ctype && slices.Equal(e.Value, cont.Cpes)
|
||||
}) {
|
||||
values = append(values, CveContentCpes{
|
||||
Type: ctype,
|
||||
Value: cont.Cpes,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CveContentRefs has CveContentType and Cpes
|
||||
// CveContentRefs has CveContentType and References
|
||||
type CveContentRefs struct {
|
||||
Type CveContentType
|
||||
Value []Reference
|
||||
@@ -171,14 +168,19 @@ func (v CveContents) References(myFamily string) (values []CveContentRefs) {
|
||||
order = append(order, AllCveContetTypes.Except(order...)...)
|
||||
|
||||
for _, ctype := range order {
|
||||
if conts, found := v[ctype]; found {
|
||||
for _, cont := range conts {
|
||||
if 0 < len(cont.References) {
|
||||
values = append(values, CveContentRefs{
|
||||
Type: ctype,
|
||||
Value: cont.References,
|
||||
})
|
||||
}
|
||||
for _, cont := range v[ctype] {
|
||||
if len(cont.References) == 0 {
|
||||
continue
|
||||
}
|
||||
if !slices.ContainsFunc(values, func(e CveContentRefs) bool {
|
||||
return e.Type == ctype && slices.EqualFunc(e.Value, cont.References, func(e1, e2 Reference) bool {
|
||||
return e1.Link == e2.Link && e1.RefID == e2.RefID && e1.Source == e2.Source && slices.Equal(e1.Tags, e2.Tags)
|
||||
})
|
||||
}) {
|
||||
values = append(values, CveContentRefs{
|
||||
Type: ctype,
|
||||
Value: cont.References,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,20 +193,18 @@ func (v CveContents) CweIDs(myFamily string) (values []CveContentStr) {
|
||||
order := GetCveContentTypes(myFamily)
|
||||
order = append(order, AllCveContetTypes.Except(order...)...)
|
||||
for _, ctype := range order {
|
||||
if conts, found := v[ctype]; found {
|
||||
for _, cont := range conts {
|
||||
if 0 < len(cont.CweIDs) {
|
||||
for _, cweID := range cont.CweIDs {
|
||||
for _, val := range values {
|
||||
if val.Value == cweID {
|
||||
continue
|
||||
}
|
||||
}
|
||||
values = append(values, CveContentStr{
|
||||
Type: ctype,
|
||||
Value: cweID,
|
||||
})
|
||||
}
|
||||
for _, cont := range v[ctype] {
|
||||
if len(cont.CweIDs) == 0 {
|
||||
continue
|
||||
}
|
||||
for _, cweID := range cont.CweIDs {
|
||||
if !slices.ContainsFunc(values, func(e CveContentStr) bool {
|
||||
return e.Type == ctype && e.Value == cweID
|
||||
}) {
|
||||
values = append(values, CveContentStr{
|
||||
Type: ctype,
|
||||
Value: cweID,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,52 +213,55 @@ func (v CveContents) CweIDs(myFamily string) (values []CveContentStr) {
|
||||
}
|
||||
|
||||
// UniqCweIDs returns Uniq CweIDs
|
||||
func (v CveContents) UniqCweIDs(myFamily string) (values []CveContentStr) {
|
||||
func (v CveContents) UniqCweIDs(myFamily string) []CveContentStr {
|
||||
uniq := map[string]CveContentStr{}
|
||||
for _, cwes := range v.CweIDs(myFamily) {
|
||||
uniq[cwes.Value] = cwes
|
||||
}
|
||||
for _, cwe := range uniq {
|
||||
values = append(values, cwe)
|
||||
return maps.Values(uniq)
|
||||
}
|
||||
|
||||
// CveContentSSVC has CveContentType and SSVC
|
||||
type CveContentSSVC struct {
|
||||
Type CveContentType
|
||||
Value SSVC
|
||||
}
|
||||
|
||||
func (v CveContents) SSVC() (value []CveContentSSVC) {
|
||||
for _, cont := range v[Mitre] {
|
||||
if cont.SSVC == nil {
|
||||
continue
|
||||
}
|
||||
t := Mitre
|
||||
if s, ok := cont.Optional["source"]; ok {
|
||||
t = CveContentType(fmt.Sprintf("%s(%s)", Mitre, s))
|
||||
}
|
||||
value = append(value, CveContentSSVC{
|
||||
Type: t,
|
||||
Value: *cont.SSVC,
|
||||
})
|
||||
}
|
||||
return values
|
||||
return
|
||||
}
|
||||
|
||||
// Sort elements for integration-testing
|
||||
func (v CveContents) Sort() {
|
||||
for contType, contents := range v {
|
||||
// CVSS3 desc, CVSS2 desc, SourceLink asc
|
||||
sort.Slice(contents, func(i, j int) bool {
|
||||
if contents[i].Cvss3Score > contents[j].Cvss3Score {
|
||||
return true
|
||||
} else if contents[i].Cvss3Score == contents[i].Cvss3Score {
|
||||
if contents[i].Cvss2Score > contents[j].Cvss2Score {
|
||||
return true
|
||||
} else if contents[i].Cvss2Score == contents[i].Cvss2Score {
|
||||
if contents[i].SourceLink < contents[j].SourceLink {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
// CVSS40 desc, CVSS3 desc, CVSS2 desc, SourceLink asc
|
||||
slices.SortFunc(contents, func(a, b CveContent) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(b.Cvss40Score, a.Cvss40Score),
|
||||
cmp.Compare(b.Cvss3Score, a.Cvss3Score),
|
||||
cmp.Compare(b.Cvss2Score, a.Cvss2Score),
|
||||
cmp.Compare(a.SourceLink, b.SourceLink),
|
||||
)
|
||||
})
|
||||
v[contType] = contents
|
||||
}
|
||||
for contType, contents := range v {
|
||||
for cveID, cont := range contents {
|
||||
sort.Slice(cont.References, func(i, j int) bool {
|
||||
return cont.References[i].Link < cont.References[j].Link
|
||||
})
|
||||
sort.Slice(cont.CweIDs, func(i, j int) bool {
|
||||
return cont.CweIDs[i] < cont.CweIDs[j]
|
||||
})
|
||||
for i, ref := range cont.References {
|
||||
// sort v.CveContents[].References[].Tags
|
||||
sort.Slice(ref.Tags, func(j, k int) bool {
|
||||
return ref.Tags[j] < ref.Tags[k]
|
||||
})
|
||||
cont.References[i] = ref
|
||||
slices.SortFunc(cont.References, func(a, b Reference) int { return cmp.Compare(a.Link, b.Link) })
|
||||
for i := range cont.References {
|
||||
slices.Sort(cont.References[i].Tags)
|
||||
}
|
||||
slices.Sort(cont.CweIDs)
|
||||
contents[cveID] = cont
|
||||
}
|
||||
v[contType] = contents
|
||||
@@ -267,23 +270,27 @@ func (v CveContents) Sort() {
|
||||
|
||||
// CveContent has abstraction of various vulnerability information
|
||||
type CveContent struct {
|
||||
Type CveContentType `json:"type"`
|
||||
CveID string `json:"cveID"`
|
||||
Title string `json:"title"`
|
||||
Summary string `json:"summary"`
|
||||
Cvss2Score float64 `json:"cvss2Score"`
|
||||
Cvss2Vector string `json:"cvss2Vector"`
|
||||
Cvss2Severity string `json:"cvss2Severity"`
|
||||
Cvss3Score float64 `json:"cvss3Score"`
|
||||
Cvss3Vector string `json:"cvss3Vector"`
|
||||
Cvss3Severity string `json:"cvss3Severity"`
|
||||
SourceLink string `json:"sourceLink"`
|
||||
Cpes []Cpe `json:"cpes,omitempty"`
|
||||
References References `json:"references,omitempty"`
|
||||
CweIDs []string `json:"cweIDs,omitempty"`
|
||||
Published time.Time `json:"published"`
|
||||
LastModified time.Time `json:"lastModified"`
|
||||
Optional map[string]string `json:"optional,omitempty"`
|
||||
Type CveContentType `json:"type"`
|
||||
CveID string `json:"cveID"`
|
||||
Title string `json:"title"`
|
||||
Summary string `json:"summary"`
|
||||
Cvss2Score float64 `json:"cvss2Score"`
|
||||
Cvss2Vector string `json:"cvss2Vector"`
|
||||
Cvss2Severity string `json:"cvss2Severity"`
|
||||
Cvss3Score float64 `json:"cvss3Score"`
|
||||
Cvss3Vector string `json:"cvss3Vector"`
|
||||
Cvss3Severity string `json:"cvss3Severity"`
|
||||
Cvss40Score float64 `json:"cvss40Score"`
|
||||
Cvss40Vector string `json:"cvss40Vector"`
|
||||
Cvss40Severity string `json:"cvss40Severity"`
|
||||
SSVC *SSVC `json:"ssvc,omitempty"`
|
||||
SourceLink string `json:"sourceLink"`
|
||||
Cpes []Cpe `json:"cpes,omitempty"`
|
||||
References References `json:"references,omitempty"`
|
||||
CweIDs []string `json:"cweIDs,omitempty"`
|
||||
Published time.Time `json:"published"`
|
||||
LastModified time.Time `json:"lastModified"`
|
||||
Optional map[string]string `json:"optional,omitempty"`
|
||||
}
|
||||
|
||||
// Empty checks the content is empty
|
||||
@@ -297,6 +304,8 @@ type CveContentType string
|
||||
// NewCveContentType create CveContentType
|
||||
func NewCveContentType(name string) CveContentType {
|
||||
switch name {
|
||||
case "mitre":
|
||||
return Mitre
|
||||
case "nvd":
|
||||
return Nvd
|
||||
case "jvn":
|
||||
@@ -415,6 +424,9 @@ func GetCveContentTypes(family string) []CveContentType {
|
||||
}
|
||||
|
||||
const (
|
||||
// Mitre is Mitre
|
||||
Mitre CveContentType = "mitre"
|
||||
|
||||
// Nvd is Nvd JSON
|
||||
Nvd CveContentType = "nvd"
|
||||
|
||||
@@ -556,6 +568,7 @@ type CveContentTypes []CveContentType
|
||||
|
||||
// AllCveContetTypes has all of CveContentTypes
|
||||
var AllCveContetTypes = CveContentTypes{
|
||||
Mitre,
|
||||
Nvd,
|
||||
Jvn,
|
||||
Fortinet,
|
||||
@@ -603,14 +616,7 @@ var AllCveContetTypes = CveContentTypes{
|
||||
// Except returns CveContentTypes except for given args
|
||||
func (c CveContentTypes) Except(excepts ...CveContentType) (excepted CveContentTypes) {
|
||||
for _, ctype := range c {
|
||||
found := false
|
||||
for _, except := range excepts {
|
||||
if ctype == except {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
if !slices.Contains(excepts, ctype) {
|
||||
excepted = append(excepted, ctype)
|
||||
}
|
||||
}
|
||||
@@ -633,3 +639,10 @@ type Reference struct {
|
||||
RefID string `json:"refID,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
// SSVC has SSVC decision points
|
||||
type SSVC struct {
|
||||
Exploitation string `json:"exploitation,omitempty"`
|
||||
Automatable string `json:"automatable,omitempty"`
|
||||
TechnicalImpact string `json:"technical_impact,omitempty"`
|
||||
}
|
||||
|
||||
@@ -7,26 +7,37 @@ import (
|
||||
"github.com/future-architect/vuls/constant"
|
||||
)
|
||||
|
||||
func TestExcept(t *testing.T) {
|
||||
var tests = []struct {
|
||||
in CveContents
|
||||
out CveContents
|
||||
}{{
|
||||
in: CveContents{
|
||||
RedHat: []CveContent{{Type: RedHat}},
|
||||
Ubuntu: []CveContent{{Type: Ubuntu}},
|
||||
Debian: []CveContent{{Type: Debian}},
|
||||
func TestCveContents_Except(t *testing.T) {
|
||||
type args struct {
|
||||
exceptCtypes []CveContentType
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
v CveContents
|
||||
args args
|
||||
wantValues CveContents
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
v: CveContents{
|
||||
RedHat: []CveContent{{Type: RedHat}},
|
||||
Ubuntu: []CveContent{{Type: Ubuntu}},
|
||||
Debian: []CveContent{{Type: Debian}},
|
||||
},
|
||||
args: args{
|
||||
exceptCtypes: []CveContentType{Ubuntu, Debian},
|
||||
},
|
||||
wantValues: CveContents{
|
||||
RedHat: []CveContent{{Type: RedHat}},
|
||||
},
|
||||
},
|
||||
out: CveContents{
|
||||
RedHat: []CveContent{{Type: RedHat}},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
actual := tt.in.Except(Ubuntu, Debian)
|
||||
if !reflect.DeepEqual(tt.out, actual) {
|
||||
t.Errorf("\nexpected: %v\n actual: %v\n", tt.out, actual)
|
||||
}
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if gotValues := tt.v.Except(tt.args.exceptCtypes...); !reflect.DeepEqual(gotValues, tt.wantValues) {
|
||||
t.Errorf("CveContents.Except() = %v, want %v", gotValues, tt.wantValues)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,14 +95,14 @@ func TestSourceLinks(t *testing.T) {
|
||||
Type: Nvd,
|
||||
Value: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
|
||||
},
|
||||
{
|
||||
Type: RedHat,
|
||||
Value: "https://access.redhat.com/security/cve/CVE-2017-6074",
|
||||
},
|
||||
{
|
||||
Type: Jvn,
|
||||
Value: "https://jvn.jp/vu/JVNVU93610402/",
|
||||
},
|
||||
{
|
||||
Type: RedHat,
|
||||
Value: "https://access.redhat.com/security/cve/CVE-2017-6074",
|
||||
},
|
||||
},
|
||||
},
|
||||
// lang: en
|
||||
@@ -162,6 +173,294 @@ func TestSourceLinks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCveContents_PatchURLs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
v CveContents
|
||||
wantUrls []string
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
v: CveContents{
|
||||
Nvd: []CveContent{
|
||||
{
|
||||
References: []Reference{
|
||||
{
|
||||
Link: "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c52873e5a1ef72f845526d9f6a50704433f9c625",
|
||||
Source: "cve@mitre.org",
|
||||
Tags: []string{"Patch", "Vendor Advisory"},
|
||||
},
|
||||
{
|
||||
Link: "https://lists.debian.org/debian-lts-announce/2020/01/msg00013.html",
|
||||
Source: "cve@mitre.org",
|
||||
Tags: []string{"Mailing List", "Third Party Advisory"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
References: []Reference{
|
||||
{
|
||||
Link: "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c52873e5a1ef72f845526d9f6a50704433f9c625",
|
||||
Tags: []string{"Patch", "Vendor Advisory"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantUrls: []string{"https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c52873e5a1ef72f845526d9f6a50704433f9c625"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if gotUrls := tt.v.PatchURLs(); !reflect.DeepEqual(gotUrls, tt.wantUrls) {
|
||||
t.Errorf("CveContents.PatchURLs() = %v, want %v", gotUrls, tt.wantUrls)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCveContents_Cpes(t *testing.T) {
|
||||
type args struct {
|
||||
myFamily string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
v CveContents
|
||||
args args
|
||||
wantValues []CveContentCpes
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
v: CveContents{
|
||||
Nvd: []CveContent{{
|
||||
Cpes: []Cpe{{
|
||||
URI: "cpe:/a:microsoft:internet_explorer:8.0.6001:beta",
|
||||
FormattedString: "cpe:2.3:a:microsoft:internet_explorer:8.0.6001:beta:*:*:*:*:*:*",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
args: args{myFamily: "redhat"},
|
||||
wantValues: []CveContentCpes{{
|
||||
Type: Nvd,
|
||||
Value: []Cpe{{
|
||||
URI: "cpe:/a:microsoft:internet_explorer:8.0.6001:beta",
|
||||
FormattedString: "cpe:2.3:a:microsoft:internet_explorer:8.0.6001:beta:*:*:*:*:*:*",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if gotValues := tt.v.Cpes(tt.args.myFamily); !reflect.DeepEqual(gotValues, tt.wantValues) {
|
||||
t.Errorf("CveContents.Cpes() = %v, want %v", gotValues, tt.wantValues)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestCveContents_References(t *testing.T) {
|
||||
type args struct {
|
||||
myFamily string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
v CveContents
|
||||
args args
|
||||
wantValues []CveContentRefs
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
v: CveContents{
|
||||
Mitre: []CveContent{{CveID: "CVE-2024-0001"}},
|
||||
Nvd: []CveContent{
|
||||
{
|
||||
References: []Reference{
|
||||
{
|
||||
Link: "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c52873e5a1ef72f845526d9f6a50704433f9c625",
|
||||
Source: "cve@mitre.org",
|
||||
Tags: []string{"Patch", "Vendor Advisory"},
|
||||
},
|
||||
{
|
||||
Link: "https://lists.debian.org/debian-lts-announce/2020/01/msg00013.html",
|
||||
Source: "cve@mitre.org",
|
||||
Tags: []string{"Mailing List", "Third Party Advisory"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
References: []Reference{
|
||||
{
|
||||
Link: "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c52873e5a1ef72f845526d9f6a50704433f9c625",
|
||||
Tags: []string{"Patch", "Vendor Advisory"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantValues: []CveContentRefs{
|
||||
{
|
||||
Type: Nvd,
|
||||
Value: []Reference{
|
||||
{
|
||||
Link: "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c52873e5a1ef72f845526d9f6a50704433f9c625",
|
||||
Source: "cve@mitre.org",
|
||||
Tags: []string{"Patch", "Vendor Advisory"},
|
||||
},
|
||||
{
|
||||
Link: "https://lists.debian.org/debian-lts-announce/2020/01/msg00013.html",
|
||||
Source: "cve@mitre.org",
|
||||
Tags: []string{"Mailing List", "Third Party Advisory"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: Nvd,
|
||||
Value: []Reference{
|
||||
{
|
||||
Link: "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c52873e5a1ef72f845526d9f6a50704433f9c625",
|
||||
Tags: []string{"Patch", "Vendor Advisory"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if gotValues := tt.v.References(tt.args.myFamily); !reflect.DeepEqual(gotValues, tt.wantValues) {
|
||||
t.Errorf("CveContents.References() = %v, want %v", gotValues, tt.wantValues)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCveContents_CweIDs(t *testing.T) {
|
||||
type args struct {
|
||||
myFamily string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
v CveContents
|
||||
args args
|
||||
wantValues []CveContentStr
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
v: CveContents{
|
||||
Mitre: []CveContent{{CweIDs: []string{"CWE-001"}}},
|
||||
Nvd: []CveContent{
|
||||
{CweIDs: []string{"CWE-001"}},
|
||||
{CweIDs: []string{"CWE-001"}},
|
||||
},
|
||||
},
|
||||
args: args{myFamily: "redhat"},
|
||||
wantValues: []CveContentStr{
|
||||
{
|
||||
Type: Mitre,
|
||||
Value: "CWE-001",
|
||||
},
|
||||
{
|
||||
Type: Nvd,
|
||||
Value: "CWE-001",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if gotValues := tt.v.CweIDs(tt.args.myFamily); !reflect.DeepEqual(gotValues, tt.wantValues) {
|
||||
t.Errorf("CveContents.CweIDs() = %v, want %v", gotValues, tt.wantValues)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCveContents_UniqCweIDs(t *testing.T) {
|
||||
type args struct {
|
||||
myFamily string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
v CveContents
|
||||
args args
|
||||
want []CveContentStr
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
v: CveContents{
|
||||
Mitre: []CveContent{{CweIDs: []string{"CWE-001"}}},
|
||||
Nvd: []CveContent{
|
||||
{CweIDs: []string{"CWE-001"}},
|
||||
{CweIDs: []string{"CWE-001"}},
|
||||
},
|
||||
},
|
||||
args: args{myFamily: "redhat"},
|
||||
want: []CveContentStr{
|
||||
{
|
||||
Type: Nvd,
|
||||
Value: "CWE-001",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.v.UniqCweIDs(tt.args.myFamily); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("CveContents.UniqCweIDs() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCveContents_SSVC(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
v CveContents
|
||||
want []CveContentSSVC
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
v: CveContents{
|
||||
Mitre: []CveContent{
|
||||
{
|
||||
Type: Mitre,
|
||||
CveID: "CVE-2024-5732",
|
||||
Title: "Clash Proxy Port improper authentication",
|
||||
Optional: map[string]string{"source": "CNA"},
|
||||
},
|
||||
{
|
||||
Type: Mitre,
|
||||
CveID: "CVE-2024-5732",
|
||||
Title: "CISA ADP Vulnrichment",
|
||||
SSVC: &SSVC{
|
||||
Exploitation: "none",
|
||||
Automatable: "no",
|
||||
TechnicalImpact: "partial",
|
||||
},
|
||||
Optional: map[string]string{"source": "ADP:CISA-ADP"},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []CveContentSSVC{
|
||||
{
|
||||
Type: "mitre(ADP:CISA-ADP)",
|
||||
Value: SSVC{
|
||||
Exploitation: "none",
|
||||
Automatable: "no",
|
||||
TechnicalImpact: "partial",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.v.SSVC(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("CveContents.SSVC() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCveContents_Sort(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -241,6 +540,48 @@ func TestCveContents_Sort(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort CVSS v4.0",
|
||||
v: CveContents{
|
||||
Mitre: []CveContent{
|
||||
{Cvss40Score: 0},
|
||||
{Cvss40Score: 6.9},
|
||||
},
|
||||
},
|
||||
want: CveContents{
|
||||
Mitre: []CveContent{
|
||||
{Cvss40Score: 6.9},
|
||||
{Cvss40Score: 0},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort CVSS v4.0 and CVSS v3",
|
||||
v: CveContents{
|
||||
Mitre: []CveContent{
|
||||
{
|
||||
Cvss40Score: 0,
|
||||
Cvss3Score: 7.3,
|
||||
},
|
||||
{
|
||||
Cvss40Score: 0,
|
||||
Cvss3Score: 9.8,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: CveContents{
|
||||
Mitre: []CveContent{
|
||||
{
|
||||
Cvss40Score: 0,
|
||||
Cvss3Score: 9.8,
|
||||
},
|
||||
{
|
||||
Cvss40Score: 0,
|
||||
Cvss3Score: 7.3,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -252,6 +593,47 @@ func TestCveContents_Sort(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCveContent_Empty(t *testing.T) {
|
||||
type fields struct {
|
||||
Type CveContentType
|
||||
CveID string
|
||||
Title string
|
||||
Summary string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
fields: fields{
|
||||
Summary: "",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "not empty",
|
||||
fields: fields{
|
||||
Summary: "summary",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := (CveContent{
|
||||
Type: tt.fields.Type,
|
||||
CveID: tt.fields.CveID,
|
||||
Title: tt.fields.Title,
|
||||
Summary: tt.fields.Summary,
|
||||
}).Empty(); got != tt.want {
|
||||
t.Errorf("CveContent.Empty() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCveContentType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -309,3 +691,31 @@ func TestGetCveContentTypes(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCveContentTypes_Except(t *testing.T) {
|
||||
type args struct {
|
||||
excepts []CveContentType
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
c CveContentTypes
|
||||
args args
|
||||
wantExcepted CveContentTypes
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
c: CveContentTypes{Ubuntu, UbuntuAPI},
|
||||
args: args{
|
||||
excepts: []CveContentType{Ubuntu},
|
||||
},
|
||||
wantExcepted: CveContentTypes{UbuntuAPI},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if gotExcepted := tt.c.Except(tt.args.excepts...); !reflect.DeepEqual(gotExcepted, tt.wantExcepted) {
|
||||
t.Errorf("CveContentTypes.Except() = %v, want %v", gotExcepted, tt.wantExcepted)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
120
models/utils.go
120
models/utils.go
@@ -6,6 +6,7 @@ package models
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cvedict "github.com/vulsio/go-cve-dictionary/models"
|
||||
)
|
||||
@@ -178,3 +179,122 @@ func ConvertFortinetToModel(cveID string, fortinets []cvedict.Fortinet) []CveCon
|
||||
}
|
||||
return cves
|
||||
}
|
||||
|
||||
// ConvertMitreToModel convert Mitre to CveContent
|
||||
func ConvertMitreToModel(cveID string, mitres []cvedict.Mitre) []CveContent {
|
||||
var cves []CveContent
|
||||
for _, mitre := range mitres {
|
||||
for _, c := range mitre.Containers {
|
||||
cve := CveContent{
|
||||
Type: Mitre,
|
||||
CveID: cveID,
|
||||
Title: func() string {
|
||||
if c.Title != nil {
|
||||
return *c.Title
|
||||
}
|
||||
return ""
|
||||
}(),
|
||||
Summary: func() string {
|
||||
for _, d := range c.Descriptions {
|
||||
if d.Lang == "en" {
|
||||
return d.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}(),
|
||||
SourceLink: fmt.Sprintf("https://www.cve.org/CVERecord?id=%s", cveID),
|
||||
Published: func() time.Time {
|
||||
if mitre.CVEMetadata.DatePublished != nil {
|
||||
return *mitre.CVEMetadata.DatePublished
|
||||
}
|
||||
return time.Time{}
|
||||
}(),
|
||||
LastModified: func() time.Time {
|
||||
if mitre.CVEMetadata.DateUpdated != nil {
|
||||
return *mitre.CVEMetadata.DateUpdated
|
||||
}
|
||||
if mitre.CVEMetadata.DatePublished != nil {
|
||||
return *mitre.CVEMetadata.DatePublished
|
||||
}
|
||||
return time.Time{}
|
||||
}(),
|
||||
Optional: map[string]string{"source": func() string {
|
||||
if c.ProviderMetadata.ShortName != nil {
|
||||
return fmt.Sprintf("%s:%s", c.ContainerType, *c.ProviderMetadata.ShortName)
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", c.ContainerType, c.ProviderMetadata.OrgID)
|
||||
}()},
|
||||
}
|
||||
|
||||
for _, m := range c.Metrics {
|
||||
if m.CVSSv2 != nil {
|
||||
cve.Cvss2Score = m.CVSSv2.BaseScore
|
||||
cve.Cvss2Vector = m.CVSSv2.VectorString
|
||||
}
|
||||
if m.CVSSv30 != nil {
|
||||
if cve.Cvss3Vector == "" {
|
||||
cve.Cvss3Score = m.CVSSv30.BaseScore
|
||||
cve.Cvss3Vector = m.CVSSv30.VectorString
|
||||
cve.Cvss3Severity = m.CVSSv30.BaseSeverity
|
||||
}
|
||||
}
|
||||
if m.CVSSv31 != nil {
|
||||
cve.Cvss3Score = m.CVSSv31.BaseScore
|
||||
cve.Cvss3Vector = m.CVSSv31.VectorString
|
||||
cve.Cvss3Severity = m.CVSSv31.BaseSeverity
|
||||
}
|
||||
if m.CVSSv40 != nil {
|
||||
cve.Cvss40Score = m.CVSSv40.BaseScore
|
||||
cve.Cvss40Vector = m.CVSSv40.VectorString
|
||||
cve.Cvss40Severity = m.CVSSv40.BaseSeverity
|
||||
}
|
||||
if m.SSVC != nil {
|
||||
cve.SSVC = &SSVC{
|
||||
Exploitation: func() string {
|
||||
if m.SSVC.Exploitation != nil {
|
||||
return *m.SSVC.Exploitation
|
||||
}
|
||||
return ""
|
||||
}(),
|
||||
Automatable: func() string {
|
||||
if m.SSVC.Automatable != nil {
|
||||
return *m.SSVC.Automatable
|
||||
}
|
||||
return ""
|
||||
}(),
|
||||
TechnicalImpact: func() string {
|
||||
if m.SSVC.TechnicalImpact != nil {
|
||||
return *m.SSVC.TechnicalImpact
|
||||
}
|
||||
return ""
|
||||
}(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range c.References {
|
||||
cve.References = append(cve.References, Reference{
|
||||
Link: r.Link,
|
||||
Source: r.Source,
|
||||
Tags: func() []string {
|
||||
if len(r.Tags) > 0 {
|
||||
return strings.Split(r.Tags, ",")
|
||||
}
|
||||
return nil
|
||||
}(),
|
||||
})
|
||||
}
|
||||
|
||||
for _, p := range c.ProblemTypes {
|
||||
for _, d := range p.Descriptions {
|
||||
if d.CweID != nil {
|
||||
cve.CweIDs = append(cve.CweIDs, *d.CweID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cves = append(cves, cve)
|
||||
}
|
||||
}
|
||||
return cves
|
||||
}
|
||||
|
||||
@@ -123,8 +123,7 @@ func (v VulnInfos) FilterIgnorePkgs(ignorePkgsRegexps []string) (_ VulnInfos, nF
|
||||
// FindScoredVulns return scored vulnerabilities
|
||||
func (v VulnInfos) FindScoredVulns() (_ VulnInfos, nFiltered int) {
|
||||
return v.Find(func(vv VulnInfo) bool {
|
||||
if 0 < vv.MaxCvss2Score().Value.Score ||
|
||||
0 < vv.MaxCvss3Score().Value.Score {
|
||||
if 0 < vv.MaxCvss2Score().Value.Score || 0 < vv.MaxCvss3Score().Value.Score || 0 < vv.MaxCvss40Score().Value.Score {
|
||||
return true
|
||||
}
|
||||
nFiltered++
|
||||
@@ -152,7 +151,10 @@ func (v VulnInfos) ToSortedSlice() (sorted []VulnInfo) {
|
||||
func (v VulnInfos) CountGroupBySeverity() map[string]int {
|
||||
m := map[string]int{}
|
||||
for _, vInfo := range v {
|
||||
score := vInfo.MaxCvss3Score().Value.Score
|
||||
score := vInfo.MaxCvss40Score().Value.Score
|
||||
if score < 0.1 {
|
||||
score = vInfo.MaxCvss3Score().Value.Score
|
||||
}
|
||||
if score < 0.1 {
|
||||
score = vInfo.MaxCvss2Score().Value.Score
|
||||
}
|
||||
@@ -417,7 +419,7 @@ func (v VulnInfo) Titles(lang, myFamily string) (values []CveContentStr) {
|
||||
}
|
||||
}
|
||||
|
||||
order := append(GetCveContentTypes(string(Trivy)), append(CveContentTypes{Fortinet, Nvd}, GetCveContentTypes(myFamily)...)...)
|
||||
order := append(GetCveContentTypes(string(Trivy)), append(CveContentTypes{Fortinet, Nvd, Mitre}, GetCveContentTypes(myFamily)...)...)
|
||||
order = append(order, AllCveContetTypes.Except(append(order, Jvn)...)...)
|
||||
for _, ctype := range order {
|
||||
if conts, found := v.CveContents[ctype]; found {
|
||||
@@ -464,7 +466,7 @@ func (v VulnInfo) Summaries(lang, myFamily string) (values []CveContentStr) {
|
||||
}
|
||||
}
|
||||
|
||||
order := append(append(GetCveContentTypes(string(Trivy)), GetCveContentTypes(myFamily)...), Fortinet, Nvd, GitHub)
|
||||
order := append(append(GetCveContentTypes(string(Trivy)), GetCveContentTypes(myFamily)...), Fortinet, Nvd, Mitre, GitHub)
|
||||
order = append(order, AllCveContetTypes.Except(append(order, Jvn)...)...)
|
||||
for _, ctype := range order {
|
||||
if conts, found := v.CveContents[ctype]; found {
|
||||
@@ -510,7 +512,7 @@ func (v VulnInfo) Summaries(lang, myFamily string) (values []CveContentStr) {
|
||||
|
||||
// Cvss2Scores returns CVSS V2 Scores
|
||||
func (v VulnInfo) Cvss2Scores() (values []CveContentCvss) {
|
||||
order := append([]CveContentType{RedHatAPI, RedHat, Nvd, Jvn}, GetCveContentTypes(string(Trivy))...)
|
||||
order := append([]CveContentType{RedHatAPI, RedHat, Nvd, Mitre, Jvn}, GetCveContentTypes(string(Trivy))...)
|
||||
for _, ctype := range order {
|
||||
if conts, found := v.CveContents[ctype]; found {
|
||||
for _, cont := range conts {
|
||||
@@ -535,7 +537,7 @@ func (v VulnInfo) Cvss2Scores() (values []CveContentCvss) {
|
||||
|
||||
// Cvss3Scores returns CVSS V3 Score
|
||||
func (v VulnInfo) Cvss3Scores() (values []CveContentCvss) {
|
||||
order := append([]CveContentType{RedHatAPI, RedHat, SUSE, Microsoft, Fortinet, Nvd, Jvn}, GetCveContentTypes(string(Trivy))...)
|
||||
order := append([]CveContentType{RedHatAPI, RedHat, SUSE, Microsoft, Fortinet, Nvd, Mitre, Jvn}, GetCveContentTypes(string(Trivy))...)
|
||||
for _, ctype := range order {
|
||||
if conts, found := v.CveContents[ctype]; found {
|
||||
for _, cont := range conts {
|
||||
@@ -606,9 +608,37 @@ func (v VulnInfo) Cvss3Scores() (values []CveContentCvss) {
|
||||
return
|
||||
}
|
||||
|
||||
// Cvss40Scores returns CVSS V4 Score
|
||||
func (v VulnInfo) Cvss40Scores() (values []CveContentCvss) {
|
||||
for _, ctype := range []CveContentType{Mitre} {
|
||||
if conts, found := v.CveContents[ctype]; found {
|
||||
for _, cont := range conts {
|
||||
if cont.Cvss40Score == 0 && cont.Cvss40Severity == "" {
|
||||
continue
|
||||
}
|
||||
// https://nvd.nist.gov/vuln-metrics/cvss
|
||||
values = append(values, CveContentCvss{
|
||||
Type: ctype,
|
||||
Value: Cvss{
|
||||
Type: CVSS40,
|
||||
Score: cont.Cvss40Score,
|
||||
Vector: cont.Cvss40Vector,
|
||||
Severity: strings.ToUpper(cont.Cvss40Severity),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MaxCvssScore returns max CVSS Score
|
||||
// If there is no CVSS Score, return Severity as a numerical value.
|
||||
func (v VulnInfo) MaxCvssScore() CveContentCvss {
|
||||
v40Max := v.MaxCvss40Score()
|
||||
if v40Max.Type != Unknown {
|
||||
return v40Max
|
||||
}
|
||||
v3Max := v.MaxCvss3Score()
|
||||
if v3Max.Type != Unknown {
|
||||
return v3Max
|
||||
@@ -616,6 +646,20 @@ func (v VulnInfo) MaxCvssScore() CveContentCvss {
|
||||
return v.MaxCvss2Score()
|
||||
}
|
||||
|
||||
// MaxCvss40Score returns Max CVSS V4.0 Score
|
||||
func (v VulnInfo) MaxCvss40Score() CveContentCvss {
|
||||
max := CveContentCvss{
|
||||
Type: Unknown,
|
||||
Value: Cvss{Type: CVSS40},
|
||||
}
|
||||
for _, cvss := range v.Cvss40Scores() {
|
||||
if max.Value.Score < cvss.Value.Score {
|
||||
max = cvss
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
// MaxCvss3Score returns Max CVSS V3 Score
|
||||
func (v VulnInfo) MaxCvss3Score() CveContentCvss {
|
||||
max := CveContentCvss{
|
||||
@@ -648,17 +692,14 @@ func (v VulnInfo) MaxCvss2Score() CveContentCvss {
|
||||
func (v VulnInfo) AttackVector() string {
|
||||
for _, conts := range v.CveContents {
|
||||
for _, cont := range conts {
|
||||
if strings.HasPrefix(cont.Cvss2Vector, "AV:N") ||
|
||||
strings.Contains(cont.Cvss3Vector, "AV:N") {
|
||||
switch {
|
||||
case strings.HasPrefix(cont.Cvss2Vector, "AV:N") || strings.Contains(cont.Cvss3Vector, "AV:N") || strings.Contains(cont.Cvss40Vector, "AV:N"):
|
||||
return "AV:N"
|
||||
} else if strings.HasPrefix(cont.Cvss2Vector, "AV:A") ||
|
||||
strings.Contains(cont.Cvss3Vector, "AV:A") {
|
||||
case strings.HasPrefix(cont.Cvss2Vector, "AV:A") || strings.Contains(cont.Cvss3Vector, "AV:A") || strings.Contains(cont.Cvss40Vector, "AV:A"):
|
||||
return "AV:A"
|
||||
} else if strings.HasPrefix(cont.Cvss2Vector, "AV:L") ||
|
||||
strings.Contains(cont.Cvss3Vector, "AV:L") {
|
||||
case strings.HasPrefix(cont.Cvss2Vector, "AV:L") || strings.Contains(cont.Cvss3Vector, "AV:L") || strings.Contains(cont.Cvss40Vector, "AV:L"):
|
||||
return "AV:L"
|
||||
} else if strings.Contains(cont.Cvss3Vector, "AV:P") {
|
||||
// no AV:P in CVSS v2
|
||||
case strings.Contains(cont.Cvss3Vector, "AV:P") || strings.Contains(cont.Cvss40Vector, "AV:P"): // no AV:P in CVSS v2
|
||||
return "AV:P"
|
||||
}
|
||||
}
|
||||
@@ -724,6 +765,9 @@ const (
|
||||
|
||||
// CVSS3 means CVSS version3
|
||||
CVSS3 CvssType = "3"
|
||||
|
||||
// CVSS40 means CVSS version4.0
|
||||
CVSS40 CvssType = "4.0"
|
||||
)
|
||||
|
||||
// Cvss has CVSS Score
|
||||
|
||||
@@ -917,6 +917,50 @@ func TestMaxCvssScores(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
// 6 : CVSSv4.0 and CVSSv3.1
|
||||
{
|
||||
in: VulnInfo{
|
||||
CveContents: CveContents{
|
||||
Mitre: []CveContent{
|
||||
{
|
||||
Type: Mitre,
|
||||
Cvss40Score: 6.9,
|
||||
Cvss40Vector: "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N",
|
||||
Cvss40Severity: "MEDIUM",
|
||||
Cvss3Score: 7.3,
|
||||
Cvss3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L",
|
||||
Cvss3Severity: "HIGH",
|
||||
Optional: map[string]string{"source": "CNA"},
|
||||
},
|
||||
},
|
||||
Nvd: []CveContent{
|
||||
{
|
||||
Type: Nvd,
|
||||
Cvss3Score: 9.8,
|
||||
Cvss3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
Cvss3Severity: "CRITICAL",
|
||||
Optional: map[string]string{"source": "nvd@nist.gov"},
|
||||
},
|
||||
{
|
||||
Type: Nvd,
|
||||
Cvss3Score: 7.3,
|
||||
Cvss3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L",
|
||||
Cvss3Severity: "HIGH",
|
||||
Optional: map[string]string{"source": "cna@vuldb.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
out: CveContentCvss{
|
||||
Type: Mitre,
|
||||
Value: Cvss{
|
||||
Type: CVSS40,
|
||||
Score: 6.9,
|
||||
Severity: "MEDIUM",
|
||||
Vector: "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N",
|
||||
},
|
||||
},
|
||||
},
|
||||
// Empty
|
||||
{
|
||||
in: VulnInfo{},
|
||||
@@ -1859,3 +1903,109 @@ func TestVulnInfo_PatchStatus(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVulnInfo_Cvss40Scores(t *testing.T) {
|
||||
type fields struct {
|
||||
CveID string
|
||||
CveContents CveContents
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want []CveContentCvss
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
fields: fields{
|
||||
CveID: "CVE-2024-5732",
|
||||
CveContents: CveContents{
|
||||
Mitre: []CveContent{
|
||||
{
|
||||
Type: Mitre,
|
||||
Cvss40Score: 6.9,
|
||||
Cvss40Vector: "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N",
|
||||
Cvss40Severity: "MEDIUM",
|
||||
Cvss3Score: 7.3,
|
||||
Cvss3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L",
|
||||
Cvss3Severity: "HIGH",
|
||||
Optional: map[string]string{"source": "CNA"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []CveContentCvss{
|
||||
{
|
||||
Type: Mitre,
|
||||
Value: Cvss{
|
||||
Type: CVSS40,
|
||||
Score: 6.9,
|
||||
Severity: "MEDIUM",
|
||||
Vector: "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := (VulnInfo{
|
||||
CveID: tt.fields.CveID,
|
||||
CveContents: tt.fields.CveContents,
|
||||
}).Cvss40Scores(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("VulnInfo.Cvss40Scores() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVulnInfo_MaxCvss40Score(t *testing.T) {
|
||||
type fields struct {
|
||||
CveID string
|
||||
CveContents CveContents
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want CveContentCvss
|
||||
}{
|
||||
{
|
||||
name: "happy",
|
||||
fields: fields{
|
||||
CveID: "CVE-2024-5732",
|
||||
CveContents: CveContents{
|
||||
Mitre: []CveContent{
|
||||
{
|
||||
Type: Mitre,
|
||||
Cvss40Score: 6.9,
|
||||
Cvss40Vector: "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N",
|
||||
Cvss40Severity: "MEDIUM",
|
||||
Cvss3Score: 7.3,
|
||||
Cvss3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L",
|
||||
Cvss3Severity: "HIGH",
|
||||
Optional: map[string]string{"source": "CNA"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: CveContentCvss{
|
||||
Type: Mitre,
|
||||
Value: Cvss{
|
||||
Type: CVSS40,
|
||||
Score: 6.9,
|
||||
Severity: "MEDIUM",
|
||||
Vector: "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := (VulnInfo{
|
||||
CveID: tt.fields.CveID,
|
||||
CveContents: tt.fields.CveContents,
|
||||
}).MaxCvss40Score(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("VulnInfo.MaxsCvss40Score() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user