From 3e67f04fe48edeb30a97a9c0598a14173dca7b42 Mon Sep 17 00:00:00 2001 From: Kota Kanbe Date: Tue, 7 Sep 2021 16:18:59 +0900 Subject: [PATCH] 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 --- GNUmakefile | 2 +- config/config.go | 29 +++---- detector/cve_client.go | 27 ++++++- detector/detector.go | 93 +++++++++++++++-------- detector/detector_test.go | 90 ++++++++++++++++++++++ go.mod | 92 ++++++++++++++++++++++- go.sum | 17 +++-- integration/int-config.toml | 32 ++++++-- integration/int-redis-config.toml | 23 +++++- models/cvecontents.go | 53 ++++++++++++- models/cvecontents_test.go | 121 +++++++++++++++++++++++++++++- models/scanresults.go | 22 +----- models/scanresults_test.go | 109 +++++++++++++++++++++++++++ models/vulninfos.go | 78 +++++++++++-------- models/vulninfos_test.go | 93 ++++++++++++++++++++--- reporter/slack.go | 2 +- reporter/util.go | 2 +- scanner/debian.go | 8 +- scanner/debian_test.go | 16 +--- subcmds/discover.go | 1 + subcmds/report.go | 4 + subcmds/server.go | 4 + subcmds/tui.go | 4 + tui/tui.go | 2 +- 24 files changed, 766 insertions(+), 158 deletions(-) create mode 100644 detector/detector_test.go diff --git a/GNUmakefile b/GNUmakefile index 4992c0b0..01ea9b2f 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -92,7 +92,7 @@ NOW=$(shell date --iso-8601=seconds) NOW_JSON_DIR := '${BASE_DIR}/$(NOW)' ONE_SEC_AFTER=$(shell date -d '+1 second' --iso-8601=seconds) ONE_SEC_AFTER_JSON_DIR := '${BASE_DIR}/$(ONE_SEC_AFTER)' -LIBS := 'gemfile' 'pipfile' 'poetry' 'composer' 'packagelock' 'yarn' 'cargo' 'gomod' 'rails' 'cpe_vendor_product_match' +LIBS := 'gemfile' 'pipfile' 'poetry' 'composer' 'packagelock' 'yarn' 'cargo' 'gomod' 'nvd_exact' 'nvd_rough' 'nvd_vendor_product' 'nvd_match_no_jvn' 'jvn_vendor_product' 'jvn_vendor_product_nover' diff: # git clone git@github.com:vulsio/vulsctl.git diff --git a/config/config.go b/config/config.go index df8f2299..935f37c8 100644 --- a/config/config.go +++ b/config/config.go @@ -69,17 +69,17 @@ type ScanOpts struct { // ReportOpts is options for report type ReportOpts struct { - // refactored - CvssScoreOver float64 `json:"cvssScoreOver,omitempty"` - TrivyCacheDBDir string `json:"trivyCacheDBDir,omitempty"` - NoProgress bool `json:"noProgress,omitempty"` - RefreshCve bool `json:"refreshCve,omitempty"` - IgnoreUnfixed bool `json:"ignoreUnfixed,omitempty"` - IgnoreUnscoredCves bool `json:"ignoreUnscoredCves,omitempty"` - DiffPlus bool `json:"diffPlus,omitempty"` - DiffMinus bool `json:"diffMinus,omitempty"` - Diff bool `json:"diff,omitempty"` - Lang string `json:"lang,omitempty"` + CvssScoreOver float64 `json:"cvssScoreOver,omitempty"` + ConfidenceScoreOver int `json:"confidenceScoreOver,omitempty"` + TrivyCacheDBDir string `json:"trivyCacheDBDir,omitempty"` + NoProgress bool `json:"noProgress,omitempty"` + RefreshCve bool `json:"refreshCve,omitempty"` + IgnoreUnfixed bool `json:"ignoreUnfixed,omitempty"` + IgnoreUnscoredCves bool `json:"ignoreUnscoredCves,omitempty"` + DiffPlus bool `json:"diffPlus,omitempty"` + DiffMinus bool `json:"diffMinus,omitempty"` + Diff bool `json:"diff,omitempty"` + Lang string `json:"lang,omitempty"` } // ValidateOnConfigtest validates @@ -236,12 +236,13 @@ type ServerInfo struct { FindLock bool `toml:"findLock,omitempty" json:"findLock,omitempty"` Type string `toml:"type,omitempty" json:"type,omitempty"` // "pseudo" or "" IgnoredJSONKeys []string `toml:"ignoredJSONKeys,omitempty" json:"ignoredJSONKeys,omitempty"` - IPv4Addrs []string `toml:"-" json:"ipv4Addrs,omitempty"` - IPv6Addrs []string `toml:"-" json:"ipv6Addrs,omitempty"` - IPSIdentifiers map[string]string `toml:"-" json:"ipsIdentifiers,omitempty"` WordPress *WordPressConf `toml:"wordpress,omitempty" json:"wordpress,omitempty"` PortScan *PortScanConf `toml:"portscan,omitempty" json:"portscan,omitempty"` + IPv4Addrs []string `toml:"-" json:"ipv4Addrs,omitempty"` + IPv6Addrs []string `toml:"-" json:"ipv6Addrs,omitempty"` + IPSIdentifiers map[string]string `toml:"-" json:"ipsIdentifiers,omitempty"` + // internal use LogMsgAnsiColor string `toml:"-" json:"-"` // DebugLog Color Container Container `toml:"-" json:"-"` diff --git a/detector/cve_client.go b/detector/cve_client.go index 377292b2..6fd44764 100644 --- a/detector/cve_client.go +++ b/detector/cve_client.go @@ -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) { diff --git a/detector/detector.go b/detector/detector.go index fb10fb36..b29d4739 100644 --- a/detector/detector.go +++ b/detector/detector.go @@ -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{} diff --git a/detector/detector_test.go b/detector/detector_test.go new file mode 100644 index 00000000..1770c86c --- /dev/null +++ b/detector/detector_test.go @@ -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) + } + }) + } +} diff --git a/go.mod b/go.mod index 0296611a..c9b28052 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/future-architect/vuls -go 1.16 +go 1.17 require ( github.com/Azure/azure-sdk-for-go v50.2.0+incompatible @@ -37,7 +37,7 @@ require ( github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936 github.com/knqyf263/gost v0.2.0 - github.com/kotakanbe/go-cve-dictionary v0.7.1 + github.com/kotakanbe/go-cve-dictionary v0.7.2-0.20210907024016-69922490c76a github.com/kotakanbe/go-pingscanner v0.1.0 github.com/kotakanbe/goval-dictionary v0.3.6-0.20210625044258-9be85404d7dd github.com/kotakanbe/logrus-prefixed-formatter v0.0.0-20180123152602-928f7356cb96 @@ -57,13 +57,97 @@ require ( github.com/takuzoo3868/go-msfdb v0.1.6 github.com/vulsio/go-exploitdb v0.2.0 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect - golang.org/x/net v0.0.0-20210825183410-e898025ed96a // indirect + golang.org/x/net v0.0.0-20210902165921-8d991716f632 // indirect golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 gorm.io/driver/mysql v1.1.2 // indirect gorm.io/gorm v1.21.14 // indirect k8s.io/utils v0.0.0-20210111153108-fddb29f9d009 ) + +require ( + github.com/Azure/go-autorest/autorest v0.10.2 // indirect + github.com/Azure/go-autorest/autorest/adal v0.8.3 // indirect + github.com/Azure/go-autorest/autorest/date v0.2.0 // indirect + github.com/Azure/go-autorest/logger v0.1.0 // indirect + github.com/Azure/go-autorest/tracing v0.5.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver v1.5.0 // indirect + github.com/Masterminds/sprig v2.22.0+incompatible // indirect + github.com/aquasecurity/go-dep-parser v0.0.0-20210520015931-0dd56983cc62 // indirect + github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce // indirect + github.com/aquasecurity/go-npm-version v0.0.0-20201110091526-0b796d180798 // indirect + github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 // indirect + github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 // indirect + github.com/caarlos0/env/v6 v6.0.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/go-redis/redis v6.15.9+incompatible // indirect + github.com/go-sql-driver/mysql v1.6.0 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-github/v33 v33.0.0 // indirect + github.com/google/go-querystring v1.0.0 // indirect + github.com/google/uuid v1.2.0 // indirect + github.com/google/wire v0.4.0 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/grokify/html-strip-tags-go v0.0.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/htcat/htcat v1.0.2 // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.9 // indirect + github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.10.0 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.1.1 // indirect + github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect + github.com/jackc/pgtype v1.8.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.2 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/labstack/gommon v0.3.0 // indirect + github.com/magefile/mage v1.11.0 // indirect + github.com/magiconair/properties v1.8.5 // indirect + github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08 // indirect + github.com/mattn/go-colorable v0.1.8 // indirect + github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect + github.com/mitchellh/copystructure v1.1.1 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/mitchellh/reflectwalk v1.0.1 // indirect + github.com/pelletier/go-toml v1.9.4 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/satori/go.uuid v1.2.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.8.1 // indirect + github.com/stretchr/objx v0.3.0 // indirect + github.com/stretchr/testify v1.7.0 // indirect + github.com/subosito/gotenv v1.2.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.1 // indirect + github.com/ymomoi/goval-parser v0.0.0-20170813122243-0a0be1dd9d08 // indirect + go.etcd.io/bbolt v1.3.5 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.17.0 // indirect + golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect + golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.26.0 // indirect + gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect + gopkg.in/ini.v1 v1.63.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + gorm.io/driver/postgres v1.1.0 // indirect + gorm.io/driver/sqlite v1.1.4 // indirect + moul.io/http2curl v1.0.0 // indirect +) diff --git a/go.sum b/go.sum index ae19de44..d25d4c27 100644 --- a/go.sum +++ b/go.sum @@ -927,8 +927,8 @@ github.com/knqyf263/nested v0.0.1/go.mod h1:zwhsIhMkBg90DTOJQvxPkKIypEHPYkgWHs4g github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kotakanbe/go-cve-dictionary v0.7.1 h1:iY+MTF6i9o5XX9pIzNZiqC1gsscj9Zdt1eTGQd3vA8w= -github.com/kotakanbe/go-cve-dictionary v0.7.1/go.mod h1:icV09UzWPPhiolVRPAwDuMfmMxwJKyVsgo4qRkyUMHE= +github.com/kotakanbe/go-cve-dictionary v0.7.2-0.20210907024016-69922490c76a h1:HUwjOw0HR81RHXdgQG0clhs88HCWvbN9bjOUMPL1xE0= +github.com/kotakanbe/go-cve-dictionary v0.7.2-0.20210907024016-69922490c76a/go.mod h1:RRZTNWQL6KVchGwK/444079s/GipyVAzIyjuzgWw5IQ= github.com/kotakanbe/go-pingscanner v0.1.0 h1:VG4/9l0i8WeToXclj7bIGoAZAu7a07Z3qmQiIfU0gT0= github.com/kotakanbe/go-pingscanner v0.1.0/go.mod h1:/761QZzuZFcfN8h/1QuawUA+pKukp3qcNj5mxJCOiAk= github.com/kotakanbe/goval-dictionary v0.3.6-0.20210625044258-9be85404d7dd h1:hnkOzwlknmNU64P5UaQzAZcyNnuSsCz/PIt/P/ZPKYg= @@ -1171,8 +1171,9 @@ github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtP github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= -github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= @@ -1637,8 +1638,8 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210825183410-e898025ed96a h1:bRuuGXV8wwSdGTB+CtJf+FjgO1APK1CoO39T4BN/XBw= -golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210902165921-8d991716f632 h1:900XJE4Rn/iPU+xD5ZznOe4GKKc4AdFK0IO1P6Z3/lQ= +golang.org/x/net v0.0.0-20210902165921-8d991716f632/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1773,9 +1774,8 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e h1:XMgFehsDnnLGtjvjOfqWSUzt0alpTR1RSEuznObga2c= -golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= @@ -2059,8 +2059,9 @@ gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:a gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.63.0 h1:2t0h8NA59dpVQpa5Yh8cIcR6nHAeBIEk0zlLVqfw4N4= +gopkg.in/ini.v1 v1.63.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= diff --git a/integration/int-config.toml b/integration/int-config.toml index 448f6a2e..d603f2d5 100755 --- a/integration/int-config.toml +++ b/integration/int-config.toml @@ -1,34 +1,50 @@ [cveDict] Type = "sqlite3" - SQLite3Path = "/home/ubuntu/vulsctl/docker/cve.sqlite3" + SQLite3Path = "/data/vulsctl/docker/cve.sqlite3" [ovalDict] Type = "sqlite3" - SQLite3Path = "/home/ubuntu/vulsctl/docker/oval.sqlite3" + SQLite3Path = "/data/vulsctl/docker/oval.sqlite3" [gost] Type = "sqlite3" - SQLite3Path = "/home/ubuntu/vulsctl/docker/gost.sqlite3" + SQLite3Path = "/data/vulsctl/docker/gost.sqlite3" [exploit] Type = "sqlite3" - SQLite3Path = "/home/ubuntu/vulsctl/docker/go-exploitdb.sqlite3" + SQLite3Path = "/data/vulsctl/docker/go-exploitdb.sqlite3" [metasploit] type = "sqlite3" - SQLite3Path = "/home/ubuntu/vulsctl/docker/go-msfdb.sqlite3" + SQLite3Path = "/data/vulsctl/docker/go-msfdb.sqlite3" [default] [servers] -[servers.rails] +[servers.nvd_exact] type = "pseudo" cpeNames = [ "cpe:/a:rubyonrails:rails:3.0.1" ] -[servers.cpe_vendor_product_match] +[servers.nvd_rough] type = "pseudo" -cpeNames = ["cpe:/a:hitachi_abb_power_grids:afs660"] +cpeNames = [ "cpe:/a:openssl:openssl:1.1.1" ] + +[servers.nvd_vendor_product] + type = "pseudo" + cpeNames = [ "cpe:/a:djangoproject:django" ] + +[servers.nvd_match_no_jvn] +type = "pseudo" +cpeNames = [ "cpe:/a:apache:tomcat:7.0.27" ] + +[servers.jvn_vendor_product] +type = "pseudo" +cpeNames = [ "cpe:/a:hitachi_abb_power_grids:afs660:1.0.0" ] + +[servers.jvn_vendor_product_nover] +type = "pseudo" +cpeNames = [ "cpe:/o:nec:aterm_wg2600hp2_firmware"] [servers.gemfile] type = "pseudo" diff --git a/integration/int-redis-config.toml b/integration/int-redis-config.toml index db49aa2a..49938a93 100755 --- a/integration/int-redis-config.toml +++ b/integration/int-redis-config.toml @@ -22,13 +22,30 @@ Url = "redis://127.0.0.1/3" [servers] -[servers.rails] +[servers.nvd_exact] type = "pseudo" cpeNames = [ "cpe:/a:rubyonrails:rails:3.0.1" ] +#cpeNames = [ "cpe:/a:rubyonrails:rails:4.0.0" ] -[servers.cpe_vendor_product_match] +[servers.nvd_rough] type = "pseudo" -cpeNames = ["cpe:/a:hitachi_abb_power_grids:afs660"] +cpeNames = [ "cpe:/a:openssl:openssl:1.1.1" ] + +[servers.nvd_vendor_product] + type = "pseudo" + cpeNames = [ "cpe:/a:djangoproject:django" ] + +[servers.nvd_match_no_jvn] +type = "pseudo" +cpeNames = [ "cpe:/a:apache:tomcat:7.0.27" ] + +[servers.jvn_vendor_product] +type = "pseudo" +cpeNames = [ "cpe:/a:hitachi_abb_power_grids:afs660:1.0.0" ] + +[servers.jvn_vendor_product_nover] +type = "pseudo" +cpeNames = [ "cpe:/o:nec:aterm_wg2600hp2_firmware"] [servers.gemfile] type = "pseudo" diff --git a/models/cvecontents.go b/models/cvecontents.go index 998977bc..5d830c04 100644 --- a/models/cvecontents.go +++ b/models/cvecontents.go @@ -1,6 +1,7 @@ package models import ( + "sort" "strings" "time" @@ -44,7 +45,7 @@ func (v CveContents) Except(exceptCtypes ...CveContentType) (values CveContents) } // PrimarySrcURLs returns link of source -func (v CveContents) PrimarySrcURLs(lang, myFamily, cveID string) (values []CveContentStr) { +func (v CveContents) PrimarySrcURLs(lang, myFamily, cveID string, confidences Confidences) (values []CveContentStr) { if cveID == "" { return } @@ -73,7 +74,15 @@ func (v CveContents) PrimarySrcURLs(lang, myFamily, cveID string) (values []CveC } } - if lang == "ja" { + 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) { @@ -220,6 +229,46 @@ func (v CveContents) UniqCweIDs(myFamily string) (values []CveContentStr) { return values } +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 + }) + 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 + } + contents[cveID] = cont + } + v[contType] = contents + } +} + // CveContent has abstraction of various vulnerability information type CveContent struct { Type CveContentType `json:"type"` diff --git a/models/cvecontents_test.go b/models/cvecontents_test.go index 7013c11a..ef55f006 100644 --- a/models/cvecontents_test.go +++ b/models/cvecontents_test.go @@ -30,9 +30,10 @@ func TestExcept(t *testing.T) { func TestSourceLinks(t *testing.T) { type in struct { - lang string - cveID string - cont CveContents + lang string + cveID string + cont CveContents + confidences Confidences } var tests = []struct { in in @@ -128,11 +129,123 @@ func TestSourceLinks(t *testing.T) { }, }, }, + // Confidence: JvnVendorProductMatch + { + in: in{ + lang: "en", + cveID: "CVE-2017-6074", + cont: CveContents{ + Jvn: []CveContent{{ + Type: Jvn, + SourceLink: "https://jvn.jp/vu/JVNVU93610402/", + }}, + }, + confidences: Confidences{ + Confidence{DetectionMethod: JvnVendorProductMatchStr}, + }, + }, + out: []CveContentStr{ + { + Type: Jvn, + Value: "https://jvn.jp/vu/JVNVU93610402/", + }, + }, + }, } for i, tt := range tests { - actual := tt.in.cont.PrimarySrcURLs(tt.in.lang, "redhat", tt.in.cveID) + actual := tt.in.cont.PrimarySrcURLs(tt.in.lang, "redhat", tt.in.cveID, tt.in.confidences) if !reflect.DeepEqual(tt.out, actual) { t.Errorf("\n[%d] expected: %v\n actual: %v\n", i, tt.out, actual) } } } + +func TestCveContents_Sort(t *testing.T) { + tests := []struct { + name string + v CveContents + want CveContents + }{ + { + name: "sorted", + v: map[CveContentType][]CveContent{ + "jvn": { + {Cvss3Score: 3}, + {Cvss3Score: 10}, + }, + }, + want: map[CveContentType][]CveContent{ + "jvn": { + {Cvss3Score: 10}, + {Cvss3Score: 3}, + }, + }, + }, + { + name: "sort JVN by cvss3, cvss2, sourceLink", + v: map[CveContentType][]CveContent{ + "jvn": { + { + Cvss3Score: 3, + Cvss2Score: 3, + SourceLink: "https://jvndb.jvn.jp/ja/contents/2023/JVNDB-2023-001210.html", + }, + { + Cvss3Score: 3, + Cvss2Score: 3, + SourceLink: "https://jvndb.jvn.jp/ja/contents/2021/JVNDB-2021-001210.html", + }, + }, + }, + want: map[CveContentType][]CveContent{ + "jvn": { + { + Cvss3Score: 3, + Cvss2Score: 3, + SourceLink: "https://jvndb.jvn.jp/ja/contents/2021/JVNDB-2021-001210.html", + }, + { + Cvss3Score: 3, + Cvss2Score: 3, + SourceLink: "https://jvndb.jvn.jp/ja/contents/2023/JVNDB-2023-001210.html", + }, + }, + }, + }, + { + name: "sort JVN by cvss3, cvss2", + v: map[CveContentType][]CveContent{ + "jvn": { + { + Cvss3Score: 3, + Cvss2Score: 1, + }, + { + Cvss3Score: 3, + Cvss2Score: 10, + }, + }, + }, + want: map[CveContentType][]CveContent{ + "jvn": { + { + Cvss3Score: 3, + Cvss2Score: 10, + }, + { + Cvss3Score: 3, + Cvss2Score: 1, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.v.Sort() + if !reflect.DeepEqual(tt.v, tt.want) { + t.Errorf("\n[%s] expected: %v\n actual: %v\n", tt.name, tt.want, tt.v) + } + }) + } +} diff --git a/models/scanresults.go b/models/scanresults.go index 9cdcc031..6f93abcd 100644 --- a/models/scanresults.go +++ b/models/scanresults.go @@ -415,25 +415,9 @@ func (r *ScanResult) SortForJSONOutput() { sort.Slice(v.Mitigations, func(i, j int) bool { return v.Mitigations[i].URL < v.Mitigations[j].URL }) - for kk, vv := range v.CveContents { - for kkk, vvv := range vv { - sort.Slice(vvv.References, func(i, j int) bool { - return vvv.References[i].Link < vvv.References[j].Link - }) - sort.Slice(vvv.CweIDs, func(i, j int) bool { - return vvv.CweIDs[i] < vvv.CweIDs[j] - }) - for kkkk, vvvv := range vvv.References { - // sort v.CveContents[].References[].Tags - sort.Slice(vvvv.Tags, func(i, j int) bool { - return vvvv.Tags[i] < vvvv.Tags[j] - }) - vvv.References[kkkk] = vvvv - } - vv[kkk] = vvv - } - v.CveContents[kk] = vv - } + + v.CveContents.Sort() + sort.Slice(v.AlertDict.En, func(i, j int) bool { return v.AlertDict.En[i].Title < v.AlertDict.En[j].Title }) diff --git a/models/scanresults_test.go b/models/scanresults_test.go index 513fe0cc..e7f8afc3 100644 --- a/models/scanresults_test.go +++ b/models/scanresults_test.go @@ -405,6 +405,115 @@ func TestScanResult_Sort(t *testing.T) { }, }, }, + { + name: "sort JVN by cvss v3", + fields: fields{ + ScannedCves: VulnInfos{ + "CVE-2014-3591": VulnInfo{ + CveContents: CveContents{ + "jvn": []CveContent{ + {Cvss3Score: 3}, + {Cvss3Score: 10}, + }, + }, + }, + }, + }, + expected: fields{ + ScannedCves: VulnInfos{ + "CVE-2014-3591": VulnInfo{ + CveContents: CveContents{ + "jvn": []CveContent{ + {Cvss3Score: 10}, + {Cvss3Score: 3}, + }, + }, + }, + }, + }, + }, + { + name: "sort JVN by cvss3, cvss2, sourceLink", + fields: fields{ + ScannedCves: VulnInfos{ + "CVE-2014-3591": VulnInfo{ + CveContents: CveContents{ + "jvn": []CveContent{ + { + Cvss3Score: 3, + Cvss2Score: 3, + SourceLink: "https://jvndb.jvn.jp/ja/contents/2023/JVNDB-2023-001210.html", + }, + { + Cvss3Score: 3, + Cvss2Score: 3, + SourceLink: "https://jvndb.jvn.jp/ja/contents/2021/JVNDB-2021-001210.html", + }, + }, + }, + }, + }, + }, + expected: fields{ + ScannedCves: VulnInfos{ + "CVE-2014-3591": VulnInfo{ + CveContents: CveContents{ + "jvn": []CveContent{ + { + Cvss3Score: 3, + Cvss2Score: 3, + SourceLink: "https://jvndb.jvn.jp/ja/contents/2021/JVNDB-2021-001210.html", + }, + { + Cvss3Score: 3, + Cvss2Score: 3, + SourceLink: "https://jvndb.jvn.jp/ja/contents/2023/JVNDB-2023-001210.html", + }, + }, + }, + }, + }, + }, + }, + { + name: "sort JVN by cvss3, cvss2", + fields: fields{ + ScannedCves: VulnInfos{ + "CVE-2014-3591": VulnInfo{ + CveContents: CveContents{ + "jvn": []CveContent{ + { + Cvss3Score: 3, + Cvss2Score: 1, + }, + { + Cvss3Score: 3, + Cvss2Score: 10, + }, + }, + }, + }, + }, + }, + expected: fields{ + ScannedCves: VulnInfos{ + "CVE-2014-3591": VulnInfo{ + CveContents: CveContents{ + "jvn": []CveContent{ + { + Cvss3Score: 3, + Cvss2Score: 10, + }, + { + Cvss3Score: 3, + Cvss2Score: 1, + }, + }, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/models/vulninfos.go b/models/vulninfos.go index 41952ffc..7441af4a 100644 --- a/models/vulninfos.go +++ b/models/vulninfos.go @@ -37,6 +37,18 @@ func (v VulnInfos) FilterByCvssOver(over float64) VulnInfos { }) } +// FilterByConfidenceOver scored vulnerabilities +func (v VulnInfos) FilterByConfidenceOver(over int) VulnInfos { + return v.Find(func(v VulnInfo) bool { + for _, c := range v.Confidences { + if over <= c.Score { + return true + } + } + return false + }) +} + // FilterIgnoreCves filter function. func (v VulnInfos) FilterIgnoreCves(ignoreCveIDs []string) VulnInfos { return v.Find(func(v VulnInfo) bool { @@ -848,59 +860,56 @@ func (c Confidence) String() string { type DetectionMethod string const ( - // CpeVersionMatchStr is a String representation of CpeNameMatch - CpeVersionMatchStr = "CpeVersionMatch" + // NvdExactVersionMatchStr : + NvdExactVersionMatchStr = "NvdExactVersionMatch" - // CpeVendorProductMatchStr is a String representation of CpeNameMatch - CpeVendorProductMatchStr = "CpeVendorProductMatch" + // NvdRoughVersionMatchStr : + NvdRoughVersionMatchStr = "NvdRoughVersionMatch" - // YumUpdateSecurityMatchStr is a String representation of YumUpdateSecurityMatch - YumUpdateSecurityMatchStr = "YumUpdateSecurityMatch" + // NvdVendorProductMatchStr : + NvdVendorProductMatchStr = "NvdVendorProductMatch" - // PkgAuditMatchStr is a String representation of PkgAuditMatch + // JvnVendorProductMatchStr : + JvnVendorProductMatchStr = "JvnVendorProductMatch" + + // PkgAuditMatchStr : PkgAuditMatchStr = "PkgAuditMatch" - // OvalMatchStr is a String representation of OvalMatch + // OvalMatchStr : OvalMatchStr = "OvalMatch" - // RedHatAPIStr is a String representation of RedHatAPIMatch + // RedHatAPIStr is : RedHatAPIStr = "RedHatAPIMatch" - // DebianSecurityTrackerMatchStr is a String representation of DebianSecurityTrackerMatch + // DebianSecurityTrackerMatchStr : DebianSecurityTrackerMatchStr = "DebianSecurityTrackerMatch" - // UbuntuAPIMatchStr is a String representation of UbuntuAPIMatch + // UbuntuAPIMatchStr : UbuntuAPIMatchStr = "UbuntuAPIMatch" - // TrivyMatchStr is a String representation of Trivy + // TrivyMatchStr : TrivyMatchStr = "TrivyMatch" - // ChangelogExactMatchStr is a String representation of ChangelogExactMatch + // ChangelogExactMatchStr : ChangelogExactMatchStr = "ChangelogExactMatch" - // ChangelogLenientMatchStr is a String representation of ChangelogLenientMatch - ChangelogLenientMatchStr = "ChangelogLenientMatch" + // ChangelogRoughMatch : + ChangelogRoughMatchStr = "ChangelogRoughMatch" - // GitHubMatchStr is a String representation of GitHubMatch + // GitHubMatchStr : GitHubMatchStr = "GitHubMatch" - // WpScanMatchStr is a String representation of WordPress VulnDB scanning + // WpScanMatchStr : WpScanMatchStr = "WpScanMatch" - // FailedToGetChangelog is a String representation of FailedToGetChangelog + // FailedToGetChangelog : FailedToGetChangelog = "FailedToGetChangelog" - // FailedToFindVersionInChangelog is a String representation of FailedToFindVersionInChangelog + // FailedToFindVersionInChangelog : FailedToFindVersionInChangelog = "FailedToFindVersionInChangelog" ) var ( - // CpeVersionMatch is a ranking how confident the CVE-ID was detected correctly - CpeVersionMatch = Confidence{100, CpeVersionMatchStr, 1} - - // YumUpdateSecurityMatch is a ranking how confident the CVE-ID was detected correctly - YumUpdateSecurityMatch = Confidence{100, YumUpdateSecurityMatchStr, 2} - // PkgAuditMatch is a ranking how confident the CVE-ID was detected correctly PkgAuditMatch = Confidence{100, PkgAuditMatchStr, 2} @@ -922,15 +931,24 @@ var ( // ChangelogExactMatch is a ranking how confident the CVE-ID was detected correctly ChangelogExactMatch = Confidence{95, ChangelogExactMatchStr, 3} - // ChangelogLenientMatch is a ranking how confident the CVE-ID was detected correctly - ChangelogLenientMatch = Confidence{50, ChangelogLenientMatchStr, 4} + // ChangelogRoughMatch is a ranking how confident the CVE-ID was detected correctly + ChangelogRoughMatch = Confidence{50, ChangelogRoughMatchStr, 4} // GitHubMatch is a ranking how confident the CVE-ID was detected correctly - GitHubMatch = Confidence{97, GitHubMatchStr, 2} + GitHubMatch = Confidence{100, GitHubMatchStr, 2} // WpScanMatch is a ranking how confident the CVE-ID was detected correctly WpScanMatch = Confidence{100, WpScanMatchStr, 0} - // CpeVendorProductMatch is a ranking how confident the CVE-ID was detected correctly - CpeVendorProductMatch = Confidence{10, CpeVendorProductMatchStr, 9} + // NvdExactVersionMatch is a ranking how confident the CVE-ID was detected correctly + NvdExactVersionMatch = Confidence{100, NvdExactVersionMatchStr, 1} + + // NvdRoughVersionMatch NvdExactVersionMatch is a ranking how confident the CVE-ID was detected correctly + NvdRoughVersionMatch = Confidence{80, NvdRoughVersionMatchStr, 1} + + // NvdVendorProductMatch is a ranking how confident the CVE-ID was detected correctly + NvdVendorProductMatch = Confidence{10, NvdVendorProductMatchStr, 9} + + // JvnVendorProductMatch is a ranking how confident the CVE-ID was detected correctly + JvnVendorProductMatch = Confidence{10, JvnVendorProductMatchStr, 10} ) diff --git a/models/vulninfos_test.go b/models/vulninfos_test.go index 913d1547..09909944 100644 --- a/models/vulninfos_test.go +++ b/models/vulninfos_test.go @@ -1037,20 +1037,20 @@ func TestAppendIfMissing(t *testing.T) { }{ { in: Confidences{ - CpeVersionMatch, + NvdExactVersionMatch, }, - arg: CpeVersionMatch, + arg: NvdExactVersionMatch, out: Confidences{ - CpeVersionMatch, + NvdExactVersionMatch, }, }, { in: Confidences{ - CpeVersionMatch, + NvdExactVersionMatch, }, arg: ChangelogExactMatch, out: Confidences{ - CpeVersionMatch, + NvdExactVersionMatch, ChangelogExactMatch, }, }, @@ -1071,21 +1071,21 @@ func TestSortByConfident(t *testing.T) { { in: Confidences{ OvalMatch, - CpeVersionMatch, + NvdExactVersionMatch, }, out: Confidences{ OvalMatch, - CpeVersionMatch, + NvdExactVersionMatch, }, }, { in: Confidences{ - CpeVersionMatch, + NvdExactVersionMatch, OvalMatch, }, out: Confidences{ OvalMatch, - CpeVersionMatch, + NvdExactVersionMatch, }, }, } @@ -1610,3 +1610,78 @@ func TestVulnInfos_FilterIgnorePkgs(t *testing.T) { }) } } + +func TestVulnInfos_FilterByConfidenceOver(t *testing.T) { + type args struct { + over int + } + tests := []struct { + name string + v VulnInfos + args args + want VulnInfos + }{ + { + name: "over 0", + v: map[string]VulnInfo{ + "CVE-2021-1111": { + CveID: "CVE-2021-1111", + Confidences: Confidences{JvnVendorProductMatch}, + }, + }, + args: args{ + over: 0, + }, + want: map[string]VulnInfo{ + "CVE-2021-1111": { + CveID: "CVE-2021-1111", + Confidences: Confidences{JvnVendorProductMatch}, + }, + }, + }, + { + name: "over 20", + v: map[string]VulnInfo{ + "CVE-2021-1111": { + CveID: "CVE-2021-1111", + Confidences: Confidences{JvnVendorProductMatch}, + }, + }, + args: args{ + over: 20, + }, + want: map[string]VulnInfo{}, + }, + { + name: "over 100", + v: map[string]VulnInfo{ + "CVE-2021-1111": { + CveID: "CVE-2021-1111", + Confidences: Confidences{ + NvdExactVersionMatch, + JvnVendorProductMatch, + }, + }, + }, + args: args{ + over: 20, + }, + want: map[string]VulnInfo{ + "CVE-2021-1111": { + CveID: "CVE-2021-1111", + Confidences: Confidences{ + NvdExactVersionMatch, + JvnVendorProductMatch, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.v.FilterByConfidenceOver(tt.args.over); !reflect.DeepEqual(got, tt.want) { + t.Errorf("VulnInfos.FilterByConfidenceOver() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/reporter/slack.go b/reporter/slack.go index a328622a..3ee68d80 100644 --- a/reporter/slack.go +++ b/reporter/slack.go @@ -282,7 +282,7 @@ func (w SlackWriter) attachmentText(vinfo models.VulnInfo, cweDict map[string]mo } else { if 0 < len(vinfo.DistroAdvisories) { links := []string{} - for _, v := range vinfo.CveContents.PrimarySrcURLs(w.lang, w.osFamily, vinfo.CveID) { + for _, v := range vinfo.CveContents.PrimarySrcURLs(w.lang, w.osFamily, vinfo.CveID, vinfo.Confidences) { links = append(links, fmt.Sprintf("<%s|%s>", v.Value, v.Type)) } diff --git a/reporter/util.go b/reporter/util.go index b60e56f4..871abc55 100644 --- a/reporter/util.go +++ b/reporter/util.go @@ -348,7 +348,7 @@ No CVE-IDs are found in updatable packages. data = append(data, []string{"Mitigation", m.URL}) } - links := vuln.CveContents.PrimarySrcURLs(r.Lang, r.Family, vuln.CveID) + links := vuln.CveContents.PrimarySrcURLs(r.Lang, r.Family, vuln.CveID, vuln.Confidences) for _, link := range links { data = append(data, []string{"Primary Src", link.Value}) } diff --git a/scanner/debian.go b/scanner/debian.go index 35eabc02..79480e1f 100644 --- a/scanner/debian.go +++ b/scanner/debian.go @@ -931,7 +931,7 @@ func (o *debian) getCveIDsFromChangelog( if 1 < len(splittedByColon) { verAfterColon = splittedByColon[1] if cveIDs, pack, err := o.parseChangelog( - changelog, name, verAfterColon, models.ChangelogLenientMatch); err == nil { + changelog, name, verAfterColon, models.ChangelogRoughMatch); err == nil { return cveIDs, pack } } @@ -948,7 +948,7 @@ func (o *debian) getCveIDsFromChangelog( ss := strings.Split(ver, d) if 1 < len(ss) { if cveIDs, pack, err := o.parseChangelog( - changelog, name, ss[0], models.ChangelogLenientMatch); err == nil { + changelog, name, ss[0], models.ChangelogRoughMatch); err == nil { return cveIDs, pack } } @@ -956,7 +956,7 @@ func (o *debian) getCveIDsFromChangelog( ss = strings.Split(verAfterColon, d) if 1 < len(ss) { if cveIDs, pack, err := o.parseChangelog( - changelog, name, ss[0], models.ChangelogLenientMatch); err == nil { + changelog, name, ss[0], models.ChangelogRoughMatch); err == nil { return cveIDs, pack } } @@ -1020,7 +1020,7 @@ func (o *debian) parseChangelog(changelog, name, ver string, confidence models.C pack := o.Packages[name] pack.Changelog = &models.Changelog{ Contents: strings.Join(buf, "\n"), - Method: models.ChangelogLenientMatchStr, + Method: models.ChangelogRoughMatchStr, } cves := []DetectedCveID{} diff --git a/scanner/debian_test.go b/scanner/debian_test.go index b4c34f96..99e44e7d 100644 --- a/scanner/debian_test.go +++ b/scanner/debian_test.go @@ -144,12 +144,7 @@ systemd (228-5) unstable; urgency=medium`, util-linux (2.27.1-1) unstable; urgency=medium util-linux (2.27-3ubuntu1) xenial; urgency=medium`, }, - []DetectedCveID{ - // {"CVE-2015-2325", models.ChangelogLenientMatch}, - // {"CVE-2015-2326", models.ChangelogLenientMatch}, - // {"CVE-2015-3210", models.ChangelogLenientMatch}, - // {"CVE-2016-1000000", models.ChangelogLenientMatch}, - }, + []DetectedCveID{}, models.Changelog{ // Contents: `util-linux (2.27.1-3ubuntu1) xenial; urgency=medium // util-linux (2.27.1-3) unstable; urgency=medium @@ -180,12 +175,7 @@ systemd (228-5) unstable; urgency=medium`, util-linux (2.27.1-1) unstable; urgency=medium util-linux (2.27-3) xenial; urgency=medium`, }, - []DetectedCveID{ - // {"CVE-2015-2325", models.ChangelogLenientMatch}, - // {"CVE-2015-2326", models.ChangelogLenientMatch}, - // {"CVE-2015-3210", models.ChangelogLenientMatch}, - // {"CVE-2016-1000000", models.ChangelogLenientMatch}, - }, + []DetectedCveID{}, models.Changelog{ // Contents: `util-linux (2.27.1-3ubuntu1) xenial; urgency=medium // util-linux (2.27.1-3) unstable; urgency=medium @@ -845,7 +835,7 @@ vlc (3.0.11-0+deb10u1) buster-security; urgency=high -- RealVNC Wed, 13 May 2020 19:51:40 +0100 `, - Method: models.ChangelogLenientMatchStr, + Method: models.ChangelogRoughMatchStr, }}, }, }, diff --git a/subcmds/discover.go b/subcmds/discover.go index 3d2b350a..73503598 100644 --- a/subcmds/discover.go +++ b/subcmds/discover.go @@ -211,6 +211,7 @@ host = "{{$ip}}" #containerType = "docker" #or "lxd" or "lxc" default: docker #containersIncluded = ["${running}"] #containersExcluded = ["container_name_a"] +#confidenceScoreOver = 80 #[servers.{{index $names $i}}.containers.container_name_a] #cpeNames = [ "cpe:/a:rubyonrails:ruby_on_rails:4.2.1" ] diff --git a/subcmds/report.go b/subcmds/report.go index 79969fcc..bdd79736 100644 --- a/subcmds/report.go +++ b/subcmds/report.go @@ -60,6 +60,7 @@ func (*ReportCmd) Usage() string { [-log-dir=/path/to/log] [-refresh-cve] [-cvss-over=7] + [-confidence-over=80] [-diff] [-diff-minus] [-diff-plus] @@ -118,6 +119,9 @@ func (p *ReportCmd) SetFlags(f *flag.FlagSet) { f.Float64Var(&config.Conf.CvssScoreOver, "cvss-over", 0, "-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))") + f.IntVar(&config.Conf.ConfidenceScoreOver, "confidence-over", 80, + "-confidence-over=40 means reporting Confidence Score 40 and over (default: 80)") + f.BoolVar(&config.Conf.DiffMinus, "diff-minus", false, "Minus Difference between previous result and current result") diff --git a/subcmds/server.go b/subcmds/server.go index 6674333d..1e0be649 100644 --- a/subcmds/server.go +++ b/subcmds/server.go @@ -39,6 +39,7 @@ func (*ServerCmd) Usage() string { [-log-to-file] [-log-dir=/path/to/log] [-cvss-over=7] + [-confidence-over=80] [-ignore-unscored-cves] [-ignore-unfixed] [-to-localfile] @@ -71,6 +72,9 @@ func (p *ServerCmd) SetFlags(f *flag.FlagSet) { f.Float64Var(&config.Conf.CvssScoreOver, "cvss-over", 0, "-cvss-over=6.5 means Servering CVSS Score 6.5 and over (default: 0 (means Server all))") + f.IntVar(&config.Conf.ConfidenceScoreOver, "confidence-over", 80, + "-confidence-over=40 means reporting Confidence Score 40 and over (default: 80)") + f.BoolVar(&config.Conf.IgnoreUnscoredCves, "ignore-unscored-cves", false, "Don't Server the unscored CVEs") diff --git a/subcmds/tui.go b/subcmds/tui.go index b552c5b8..38fa654c 100644 --- a/subcmds/tui.go +++ b/subcmds/tui.go @@ -37,6 +37,7 @@ func (*TuiCmd) Usage() string { [-refresh-cve] [-config=/path/to/config.toml] [-cvss-over=7] + [-confidence-over=80] [-diff] [-diff-minus] [-diff-plus] @@ -80,6 +81,9 @@ func (p *TuiCmd) SetFlags(f *flag.FlagSet) { f.Float64Var(&config.Conf.CvssScoreOver, "cvss-over", 0, "-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))") + f.IntVar(&config.Conf.ConfidenceScoreOver, "confidence-over", 80, + "-confidence-over=40 means reporting Confidence Score 40 and over (default: 80)") + f.BoolVar(&config.Conf.Diff, "diff", false, "Plus Difference between previous result and current result") diff --git a/tui/tui.go b/tui/tui.go index 97699a10..24a70104 100644 --- a/tui/tui.go +++ b/tui/tui.go @@ -895,7 +895,7 @@ func detailLines() (string, error) { vinfo := vinfos[currentVinfo] links := []string{} - for _, r := range vinfo.CveContents.PrimarySrcURLs(r.Lang, r.Family, vinfo.CveID) { + for _, r := range vinfo.CveContents.PrimarySrcURLs(r.Lang, r.Family, vinfo.CveID, vinfo.Confidences) { links = append(links, r.Value) }