breaking-change(cpescan): Improve Cpe scan (#1290)

* chore(cpescan): enable to pass useJvn to detector.DetectCpeURIsCves()

* review comment

* chore: go mod update go-cve

* feat(cpescan): set JvnVendorProductMatch to confidence If detected by JVN

* add NvdExactVersionMatch andd NvdRoughVersionMatch

* add confidence-over option to report

* sort CveContetens

* fix integration-test
This commit is contained in:
Kota Kanbe
2021-09-07 16:18:59 +09:00
committed by GitHub
parent b9416ae062
commit 3e67f04fe4
24 changed files with 766 additions and 158 deletions

View File

@@ -147,18 +147,37 @@ func (api goCveDictClient) httpGet(key, url string, resChan chan<- response, err
}
}
func (api goCveDictClient) fetchCveDetailsByCpeName(cpeName string) ([]cvemodels.CveDetail, error) {
func (api goCveDictClient) detectCveByCpeURI(cpeURI string, useJVN bool) (cves []cvemodels.CveDetail, err error) {
if api.cnf.IsFetchViaHTTP() {
url, err := util.URLPathJoin(api.cnf.GetURL(), "cpes")
if err != nil {
return nil, err
}
query := map[string]string{"name": cpeName}
query := map[string]string{"name": cpeURI}
logging.Log.Debugf("HTTP Request to %s, query: %#v", url, query)
return api.httpPost(cpeName, url, query)
if cves, err = api.httpPost(cpeURI, url, query); err != nil {
return nil, err
}
} else {
if cves, err = api.driver.GetByCpeURI(cpeURI); err != nil {
return nil, err
}
}
return api.driver.GetByCpeURI(cpeName)
if useJVN {
return cves, nil
}
nvdCves := []cvemodels.CveDetail{}
for _, cve := range cves {
if !cve.HasNvd() {
continue
}
cve.Jvns = []cvemodels.Jvn{}
nvdCves = append(nvdCves, cve)
}
return nvdCves, nil
}
func (api goCveDictClient) httpPost(key, url string, query map[string]string) ([]cvemodels.CveDetail, error) {

View File

@@ -18,12 +18,16 @@ import (
"github.com/future-architect/vuls/oval"
"github.com/future-architect/vuls/reporter"
"github.com/future-architect/vuls/util"
"github.com/knqyf263/go-cpe/common"
"github.com/knqyf263/go-cpe/naming"
cvemodels "github.com/kotakanbe/go-cve-dictionary/models"
"golang.org/x/xerrors"
)
// Cpe :
type Cpe struct {
CpeURI string
UseJVN bool
}
// Detect vulns and fill CVE detailed information
func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
@@ -39,7 +43,16 @@ func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
r.ScannedCves = models.VulnInfos{}
}
if err := DetectLibsCves(&r, config.Conf.TrivyCacheDBDir, config.Conf.NoProgress); err != nil {
return nil, xerrors.Errorf("Failed to fill with Library dependency: %w", err)
}
if err := DetectPkgCves(&r, config.Conf.OvalDict, config.Conf.Gost); err != nil {
return nil, xerrors.Errorf("Failed to detect Pkg CVE: %w", err)
}
cpeURIs, owaspDCXMLPath := []string{}, ""
cpes := []Cpe{}
if len(r.Container.ContainerID) == 0 {
cpeURIs = config.Conf.Servers[r.ServerName].CpeNames
owaspDCXMLPath = config.Conf.Servers[r.ServerName].OwaspDCXMLPath
@@ -59,16 +72,13 @@ func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
}
cpeURIs = append(cpeURIs, cpes...)
}
if err := DetectLibsCves(&r, config.Conf.TrivyCacheDBDir, config.Conf.NoProgress); err != nil {
return nil, xerrors.Errorf("Failed to fill with Library dependency: %w", err)
for _, uri := range cpeURIs {
cpes = append(cpes, Cpe{
CpeURI: uri,
UseJVN: true,
})
}
if err := DetectPkgCves(&r, config.Conf.OvalDict, config.Conf.Gost); err != nil {
return nil, xerrors.Errorf("Failed to detect Pkg CVE: %w", err)
}
if err := DetectCpeURIsCves(&r, cpeURIs, config.Conf.CveDict, config.Conf.LogOpts); err != nil {
if err := DetectCpeURIsCves(&r, cpes, config.Conf.CveDict, config.Conf.LogOpts); err != nil {
return nil, xerrors.Errorf("Failed to detect CVE of `%s`: %w", cpeURIs, err)
}
@@ -137,6 +147,7 @@ func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
for i, r := range rs {
r.ScannedCves = r.ScannedCves.FilterByCvssOver(config.Conf.CvssScoreOver)
r.ScannedCves = r.ScannedCves.FilterUnfixed(config.Conf.IgnoreUnfixed)
r.ScannedCves = r.ScannedCves.FilterByConfidenceOver(config.Conf.ConfidenceScoreOver)
// IgnoreCves
ignoreCves := []string{}
@@ -407,7 +418,7 @@ func detectPkgsCvesWithGost(cnf config.GostConf, r *models.ScanResult) error {
}
// DetectCpeURIsCves detects CVEs of given CPE-URIs
func DetectCpeURIsCves(r *models.ScanResult, cpeURIs []string, cnf config.GoCveDictConf, logOpts logging.LogOpts) error {
func DetectCpeURIsCves(r *models.ScanResult, cpes []Cpe, cnf config.GoCveDictConf, logOpts logging.LogOpts) error {
client, err := newGoCveDictClient(&cnf, logOpts)
if err != nil {
return err
@@ -419,38 +430,34 @@ func DetectCpeURIsCves(r *models.ScanResult, cpeURIs []string, cnf config.GoCveD
}()
nCVEs := 0
for _, name := range cpeURIs {
details, err := client.fetchCveDetailsByCpeName(name)
for _, cpe := range cpes {
details, err := client.detectCveByCpeURI(cpe.CpeURI, cpe.UseJVN)
if err != nil {
return err
}
specified, err := naming.UnbindURI(name)
if err != nil {
return xerrors.Errorf("Failed to unbind. CPE: %s. err: %w", name, err)
}
specifiedVer := specified.GetString(common.AttributeVersion)
for _, detail := range details {
var confidence models.Confidence
switch specifiedVer {
case "NA", "ANY":
confidence = models.CpeVendorProductMatch
default:
confidence = models.CpeVersionMatch
if !detail.HasNvd() && detail.HasJvn() {
confidence = models.CpeVendorProductMatch
advisories := []models.DistroAdvisory{}
if !detail.HasNvd() && detail.HasJvn() {
for _, jvn := range detail.Jvns {
advisories = append(advisories, models.DistroAdvisory{
AdvisoryID: jvn.JvnID,
})
}
}
maxConfidence := getMaxConfidence(detail)
if val, ok := r.ScannedCves[detail.CveID]; ok {
val.CpeURIs = util.AppendIfMissing(val.CpeURIs, name)
val.Confidences.AppendIfMissing(confidence)
val.CpeURIs = util.AppendIfMissing(val.CpeURIs, cpe.CpeURI)
val.Confidences.AppendIfMissing(maxConfidence)
val.DistroAdvisories = advisories
r.ScannedCves[detail.CveID] = val
} else {
v := models.VulnInfo{
CveID: detail.CveID,
CpeURIs: []string{name},
Confidences: models.Confidences{confidence},
CveID: detail.CveID,
CpeURIs: []string{cpe.CpeURI},
Confidences: models.Confidences{maxConfidence},
DistroAdvisories: advisories,
}
r.ScannedCves[detail.CveID] = v
nCVEs++
@@ -461,6 +468,28 @@ func DetectCpeURIsCves(r *models.ScanResult, cpeURIs []string, cnf config.GoCveD
return nil
}
func getMaxConfidence(detail cvemodels.CveDetail) (max models.Confidence) {
if !detail.HasNvd() && detail.HasJvn() {
return models.JvnVendorProductMatch
} else if detail.HasNvd() {
for _, nvd := range detail.Nvds {
confidence := models.Confidence{}
switch nvd.DetectionMethod {
case cvemodels.NvdExactVersionMatch:
confidence = models.NvdExactVersionMatch
case cvemodels.NvdRoughVersionMatch:
confidence = models.NvdRoughVersionMatch
case cvemodels.NvdVendorProductMatch:
confidence = models.NvdVendorProductMatch
}
if max.Score < confidence.Score {
max = confidence
}
}
}
return max
}
// FillCweDict fills CWE
func FillCweDict(r *models.ScanResult) {
uniqCweIDMap := map[string]bool{}

90
detector/detector_test.go Normal file
View File

@@ -0,0 +1,90 @@
//go:build !scanner
// +build !scanner
package detector
import (
"reflect"
"testing"
"github.com/future-architect/vuls/models"
cvemodels "github.com/kotakanbe/go-cve-dictionary/models"
)
func Test_getMaxConfidence(t *testing.T) {
type args struct {
detail cvemodels.CveDetail
}
tests := []struct {
name string
args args
wantMax models.Confidence
}{
{
name: "JvnVendorProductMatch",
args: args{
detail: cvemodels.CveDetail{
Nvds: []cvemodels.Nvd{},
Jvns: []cvemodels.Jvn{{}},
},
},
wantMax: models.JvnVendorProductMatch,
},
{
name: "NvdExactVersionMatch",
args: args{
detail: cvemodels.CveDetail{
Nvds: []cvemodels.Nvd{
{DetectionMethod: cvemodels.NvdRoughVersionMatch},
{DetectionMethod: cvemodels.NvdVendorProductMatch},
{DetectionMethod: cvemodels.NvdExactVersionMatch},
},
Jvns: []cvemodels.Jvn{{DetectionMethod: cvemodels.JvnVendorProductMatch}},
},
},
wantMax: models.NvdExactVersionMatch,
},
{
name: "NvdRoughVersionMatch",
args: args{
detail: cvemodels.CveDetail{
Nvds: []cvemodels.Nvd{
{DetectionMethod: cvemodels.NvdRoughVersionMatch},
{DetectionMethod: cvemodels.NvdVendorProductMatch},
},
Jvns: []cvemodels.Jvn{},
},
},
wantMax: models.NvdRoughVersionMatch,
},
{
name: "NvdVendorProductMatch",
args: args{
detail: cvemodels.CveDetail{
Nvds: []cvemodels.Nvd{
{DetectionMethod: cvemodels.NvdVendorProductMatch},
},
Jvns: []cvemodels.Jvn{{DetectionMethod: cvemodels.JvnVendorProductMatch}},
},
},
wantMax: models.NvdVendorProductMatch,
},
{
name: "empty",
args: args{
detail: cvemodels.CveDetail{
Nvds: []cvemodels.Nvd{},
Jvns: []cvemodels.Jvn{},
},
},
wantMax: models.Confidence{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if gotMax := getMaxConfidence(tt.args.detail); !reflect.DeepEqual(gotMax, tt.wantMax) {
t.Errorf("getMaxConfidence() = %v, want %v", gotMax, tt.wantMax)
}
})
}
}