diff --git a/commands/report.go b/commands/report.go index 08b3afae..7d963663 100644 --- a/commands/report.go +++ b/commands/report.go @@ -27,10 +27,10 @@ import ( c "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/cveapi" "github.com/future-architect/vuls/models" + "github.com/future-architect/vuls/oval" "github.com/future-architect/vuls/report" "github.com/future-architect/vuls/util" "github.com/google/subcommands" - "github.com/k0kubun/pp" ) // ReportCmd is subcommand for reporting @@ -417,24 +417,22 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{} } } - filled, err := fillCveInfoFromOvalDB(&r) - if err != nil { + if err := fillCveInfoFromOvalDB(&r); err != nil { util.Log.Errorf("Failed to fill OVAL information: %s", err) return subcommands.ExitFailure } - filled, err = fillCveInfoFromCveDB(*filled) - if err != nil { + if err := fillCveInfoFromCveDB(&r); err != nil { util.Log.Errorf("Failed to fill CVE information: %s", err) return subcommands.ExitFailure } - filled.Lang = c.Conf.Lang - if err := overwriteJSONFile(dir, *filled); err != nil { + r.Lang = c.Conf.Lang + if err := overwriteJSONFile(dir, r); err != nil { util.Log.Errorf("Failed to write JSON: %s", err) return subcommands.ExitFailure } - results = append(results, *filled) + results = append(results, r) } else { util.Log.Debugf("no need to refresh") results = append(results, r) @@ -455,20 +453,30 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{} } results = []models.ScanResult{} for _, r := range diff { - filled, _ := r.FillCveDetail() - results = append(results, *filled) + if err := fillCveDetail(&r); err != nil { + util.Log.Error(err) + return subcommands.ExitFailure + } + results = append(results, r) } } var res models.ScanResults for _, r := range results { //TODO remove - for _, vuln := range r.ScannedCves { - if _, ok := vuln.CveContents.Get(models.CveContentType(r.Family)); !ok { - fmt.Println("not in oval") - pp.Println(vuln) - } - } + // for _, vuln := range r.ScannedCves { + // // if _, ok := vuln.CveContents.Get(models.NewCveContentType(r.Family)); !ok { + // // pp.Printf("not in oval: %s %f\n%v\n", + // // vuln.CveID, vuln.CveContents.CvssV2Score(), vuln.Packages) + // // } else { + // // fmt.Printf(" in oval: %s %f\n", + // // vuln.CveID, vuln.CveContents.CvssV2Score()) + // // } + // // if vuln.CveContents.CvssV2Score() < 0.1 && + // // vuln.CveContents.CvssV3Score() < 0.1 { + // // pp.Println(vuln) + // // } + // } res = append(res, r.FilterByCvssOver()) } @@ -480,3 +488,71 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{} } return subcommands.ExitSuccess } + +// fillCveDetail fetches NVD, JVN from CVE Database, and then set to fields. +//TODO rename to FillCveDictionary +func fillCveDetail(r *models.ScanResult) error { + var cveIDs []string + for _, v := range r.ScannedCves { + cveIDs = append(cveIDs, v.CveID) + } + + ds, err := cveapi.CveClient.FetchCveDetails(cveIDs) + if err != nil { + return err + } + for _, d := range ds { + nvd := *r.ConvertNvdToModel(d.CveID, d.Nvd) + jvn := *r.ConvertJvnToModel(d.CveID, d.Jvn) + for i, sc := range r.ScannedCves { + if sc.CveID == d.CveID { + for _, con := range []models.CveContent{nvd, jvn} { + if !con.Empty() { + r.ScannedCves[i].CveContents.Upsert(con) + } + } + break + } + } + } + //TODO sort + // sort.Sort(r.KnownCves) + // sort.Sort(r.UnknownCves) + // sort.Sort(r.IgnoredCves) + return nil +} + +func fillCveInfoFromCveDB(r *models.ScanResult) error { + var err error + var vs []models.VulnInfo + + sInfo := c.Conf.Servers[r.ServerName] + vs, err = scanVulnByCpeNames(sInfo.CpeNames, r.ScannedCves) + if err != nil { + return err + } + r.ScannedCves = vs + if err := fillCveDetail(r); err != nil { + return err + } + return nil +} + +func fillCveInfoFromOvalDB(r *models.ScanResult) error { + var ovalClient oval.Client + switch r.Family { + case "ubuntu", "debian": + ovalClient = oval.NewDebian() + case "rhel", "centos": + ovalClient = oval.NewRedhat() + case "amazon", "oraclelinux", "Raspbian", "FreeBSD": + //TODO implement OracleLinux + return nil + default: + return fmt.Errorf("Oval %s is not implemented yet", r.Family) + } + if err := ovalClient.FillCveInfoFromOvalDB(r); err != nil { + return err + } + return nil +} diff --git a/commands/tui.go b/commands/tui.go index 530b9c32..cbf39a02 100644 --- a/commands/tui.go +++ b/commands/tui.go @@ -169,20 +169,17 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s } } - filled, err := fillCveInfoFromCveDB(r) - if err != nil { + if err := fillCveInfoFromCveDB(&r); err != nil { log.Errorf("Failed to fill CVE information: %s", err) return subcommands.ExitFailure } - if err := overwriteJSONFile(jsonDir, *filled); err != nil { + if err := overwriteJSONFile(jsonDir, r); err != nil { log.Errorf("Failed to write JSON: %s", err) return subcommands.ExitFailure } - filledResults = append(filledResults, *filled) - } else { - filledResults = append(filledResults, r) } + filledResults = append(filledResults, r) } return report.RunTui(filledResults) } diff --git a/commands/util.go b/commands/util.go index 23f43f0a..feecb61d 100644 --- a/commands/util.go +++ b/commands/util.go @@ -31,7 +31,6 @@ import ( c "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/cveapi" "github.com/future-architect/vuls/models" - "github.com/future-architect/vuls/oval" "github.com/future-architect/vuls/report" "github.com/future-architect/vuls/util" ) @@ -161,39 +160,6 @@ func loadScanResults(jsonDir string) (results models.ScanResults, err error) { return } -func fillCveInfoFromCveDB(r models.ScanResult) (*models.ScanResult, error) { - var err error - var vs []models.VulnInfo - - sInfo := c.Conf.Servers[r.ServerName] - vs, err = scanVulnByCpeNames(sInfo.CpeNames, r.ScannedCves) - if err != nil { - return nil, err - } - r.ScannedCves = vs - return r.FillCveDetail() -} - -func fillCveInfoFromOvalDB(r *models.ScanResult) (*models.ScanResult, error) { - var ovalClient oval.Client - switch r.Family { - case "ubuntu", "debian": - ovalClient = oval.NewDebian() - case "rhel", "centos": - ovalClient = oval.NewRedhat() - case "amazon", "oraclelinux", "Raspbian", "FreeBSD": - //TODO implement OracleLinux - return r, nil - default: - return nil, fmt.Errorf("Oval %s is not implemented yet", r.Family) - } - result, err := ovalClient.FillCveInfoFromOvalDB(r) - if err != nil { - return nil, err - } - return result, nil -} - func loadPrevious(current models.ScanResults) (previous models.ScanResults, err error) { var dirs jsonDirs if dirs, err = lsValidJSONDirs(); err != nil { diff --git a/models/models.go b/models/models.go index 387b63db..ce208b06 100644 --- a/models/models.go +++ b/models/models.go @@ -24,7 +24,6 @@ import ( "time" "github.com/future-architect/vuls/config" - "github.com/future-architect/vuls/cveapi" cvedict "github.com/kotakanbe/go-cve-dictionary/models" ) @@ -68,40 +67,8 @@ type ScanResult struct { Optional [][]interface{} } -// FillCveDetail fetches NVD, JVN from CVE Database, and then set to fields. -//TODO rename to FillCveDictionary -func (r ScanResult) FillCveDetail() (*ScanResult, error) { - var cveIDs []string - for _, v := range r.ScannedCves { - cveIDs = append(cveIDs, v.CveID) - } - - ds, err := cveapi.CveClient.FetchCveDetails(cveIDs) - if err != nil { - return nil, err - } - for _, d := range ds { - nvd := *r.convertNvdToModel(d.CveID, d.Nvd) - jvn := *r.convertJvnToModel(d.CveID, d.Jvn) - for i, sc := range r.ScannedCves { - if sc.CveID == d.CveID { - for _, con := range []CveContent{nvd, jvn} { - if !con.Empty() { - r.ScannedCves[i].CveContents.Upsert(con) - } - } - break - } - } - } - //TODO sort - // sort.Sort(r.KnownCves) - // sort.Sort(r.UnknownCves) - // sort.Sort(r.IgnoredCves) - return &r, nil -} - -func (r ScanResult) convertNvdToModel(cveID string, nvd cvedict.Nvd) *CveContent { +// ConvertNvdToModel convert NVD to CveContent +func (r ScanResult) ConvertNvdToModel(cveID string, nvd cvedict.Nvd) *CveContent { var cpes []Cpe for _, c := range nvd.Cpes { cpes = append(cpes, Cpe{CpeName: c.CpeName}) @@ -155,7 +122,8 @@ func (r ScanResult) convertNvdToModel(cveID string, nvd cvedict.Nvd) *CveContent } } -func (r ScanResult) convertJvnToModel(cveID string, jvn cvedict.Jvn) *CveContent { +// ConvertJvnToModel convert JVN to CveContent +func (r ScanResult) ConvertJvnToModel(cveID string, jvn cvedict.Jvn) *CveContent { var cpes []Cpe for _, c := range jvn.Cpes { cpes = append(cpes, Cpe{CpeName: c.CpeName}) @@ -269,6 +237,9 @@ func (r ScanResult) CveSummary() string { var high, medium, low, unknown int for _, vInfo := range r.ScannedCves { score := vInfo.CveContents.CvssV2Score() + if score < 0.1 { + score = vInfo.CveContents.CvssV3Score() + } switch { case 7.0 <= score: high++ @@ -356,16 +327,15 @@ var ChangelogLenientMatch = Confidence{50, ChangelogLenientMatchStr} // VulnInfos is VulnInfo list, getter/setter, sortable methods. type VulnInfos []VulnInfo -// FindByCveID find by CVEID -// TODO remove -// func (v *VulnInfos) FindByCveID(cveID string) (VulnInfo, bool) { -// for _, p := range s { -// if cveID == p.CveID { -// return p, true -// } -// } -// return VulnInfo{CveID: cveID}, false -// } +// Find elements that matches the function passed in argument +func (v *VulnInfos) Find(f func(VulnInfo) bool) (filtered VulnInfos) { + for _, vv := range *v { + if f(vv) { + filtered = append(filtered, vv) + } + } + return +} // Get VulnInfo by cveID func (v *VulnInfos) Get(cveID string) (VulnInfo, bool) { @@ -592,6 +562,24 @@ func (v *VulnInfo) NilSliceToEmpty() { // CveContentType is a source of CVE information type CveContentType string +// NewCveContentType create CveContentType +func NewCveContentType(name string) CveContentType { + switch name { + case "nvd": + return NVD + case "jvn": + return JVN + case "redhat", "centos": + return RedHat + case "ubuntu": + return Ubuntu + case "debian": + return Debian + default: + return Unknown + } +} + const ( // NVD is NVD NVD CveContentType = "nvd" @@ -610,6 +598,9 @@ const ( // Ubuntu is Ubuntu Ubuntu CveContentType = "ubuntu" + + // Unknown is Unknown + Unknown CveContentType = "unknown" ) // CveContents has slice of CveContent @@ -671,7 +662,15 @@ func (v *CveContents) CvssV2Score() float64 { } else if cont, found := v.Get(RedHat); found { return cont.Cvss2Score } - return -1 + return -1.1 +} + +// CvssV3Score returns CVSS V2 Score +func (v *CveContents) CvssV3Score() float64 { + if cont, found := v.Get(RedHat); found { + return cont.Cvss3Score + } + return -1.1 } // CveContent has abstraction of various vulnerability information diff --git a/oval/debian.go b/oval/debian.go index 76720e91..864b9f73 100644 --- a/oval/debian.go +++ b/oval/debian.go @@ -21,13 +21,14 @@ func NewDebian() Debian { } // FillCveInfoFromOvalDB returns scan result after updating CVE info by OVAL -func (o Debian) FillCveInfoFromOvalDB(r *models.ScanResult) (*models.ScanResult, error) { - util.Log.Debugf("open oval-dictionary db (%s)", config.Conf.OvalDBType) +func (o Debian) FillCveInfoFromOvalDB(r *models.ScanResult) error { ovalconf.Conf.DBType = config.Conf.OvalDBType ovalconf.Conf.DBPath = config.Conf.OvalDBPath + util.Log.Infof("open oval-dictionary db (%s): %s", + config.Conf.OvalDBType, config.Conf.OvalDBPath) if err := db.OpenDB(); err != nil { - return nil, fmt.Errorf("Failed to open OVAL DB. err: %s", err) + return fmt.Errorf("Failed to open OVAL DB. err: %s", err) } var d db.OvalDB @@ -40,27 +41,27 @@ func (o Debian) FillCveInfoFromOvalDB(r *models.ScanResult) (*models.ScanResult, for _, pack := range r.Packages { definitions, err := d.GetByPackName(r.Release, pack.Name) if err != nil { - return nil, fmt.Errorf("Failed to get Debian OVAL info by package name: %v", err) + return fmt.Errorf("Failed to get Debian OVAL info by package name: %v", err) } - for _, definition := range definitions { + for _, def := range definitions { current, _ := ver.NewVersion(pack.Version) - for _, p := range definition.AffectedPacks { + for _, p := range def.AffectedPacks { if pack.Name != p.Name { continue } affected, _ := ver.NewVersion(p.Version) if current.LessThan(affected) { - r = o.fillOvalInfo(r, &definition) + o.fillOvalInfo(r, &def) } } } } - return r, nil + return nil } -func (o Debian) fillOvalInfo(r *models.ScanResult, definition *ovalmodels.Definition) *models.ScanResult { +func (o Debian) fillOvalInfo(r *models.ScanResult, definition *ovalmodels.Definition) { ovalContent := *o.convertToModel(definition) - ovalContent.Type = models.CveContentType(r.Family) + ovalContent.Type = models.NewCveContentType(r.Family) vinfo, ok := r.ScannedCves.Get(definition.Debian.CveID) if !ok { util.Log.Infof("%s is newly detected by OVAL", @@ -72,7 +73,7 @@ func (o Debian) fillOvalInfo(r *models.ScanResult, definition *ovalmodels.Defini CveContents: []models.CveContent{ovalContent}, } } else { - if _, ok := vinfo.CveContents.Get(models.CveContentType(r.Family)); !ok { + if _, ok := vinfo.CveContents.Get(models.NewCveContentType(r.Family)); !ok { util.Log.Infof("%s is also detected by OVAL", definition.Debian.CveID) } else { util.Log.Infof("%s will be updated by OVAL", definition.Debian.CveID) @@ -83,7 +84,6 @@ func (o Debian) fillOvalInfo(r *models.ScanResult, definition *ovalmodels.Defini vinfo.CveContents.Upsert(ovalContent) } r.ScannedCves.Upsert(vinfo) - return r } func (o Debian) convertToModel(def *ovalmodels.Definition) *models.CveContent { diff --git a/oval/oval.go b/oval/oval.go index 0b324da0..410b01e1 100644 --- a/oval/oval.go +++ b/oval/oval.go @@ -7,7 +7,7 @@ import ( // Client is the interface of OVAL client. type Client interface { - FillCveInfoFromOvalDB(r *models.ScanResult) (*models.ScanResult, error) + FillCveInfoFromOvalDB(r *models.ScanResult) error } func getPackageInfoList(r *models.ScanResult, d *ovalmodels.Definition) models.PackageInfoList { diff --git a/oval/redhat.go b/oval/redhat.go index b9c5e709..f59f4d00 100644 --- a/oval/redhat.go +++ b/oval/redhat.go @@ -23,14 +23,14 @@ func NewRedhat() Redhat { } // FillCveInfoFromOvalDB returns scan result after updating CVE info by OVAL -func (o Redhat) FillCveInfoFromOvalDB(r *models.ScanResult) (*models.ScanResult, error) { - util.Log.Debugf("open oval-dictionary db (%s)", config.Conf.OvalDBType) - +func (o Redhat) FillCveInfoFromOvalDB(r *models.ScanResult) error { ovalconf.Conf.DBType = config.Conf.OvalDBType ovalconf.Conf.DBPath = config.Conf.OvalDBPath + util.Log.Infof("open oval-dictionary db (%s): %s", + config.Conf.OvalDBType, config.Conf.OvalDBPath) if err := db.OpenDB(); err != nil { - return nil, fmt.Errorf("Failed to open OVAL DB. err: %s", err) + return fmt.Errorf("Failed to open OVAL DB. err: %s", err) } d := db.NewRedHat() @@ -38,7 +38,7 @@ func (o Redhat) FillCveInfoFromOvalDB(r *models.ScanResult) (*models.ScanResult, for _, pack := range r.Packages { definitions, err := d.GetByPackName(r.Release, pack.Name) if err != nil { - return nil, fmt.Errorf("Failed to get RedHat OVAL info by package name: %v", err) + return fmt.Errorf("Failed to get RedHat OVAL info by package name: %v", err) } for _, definition := range definitions { current, _ := ver.NewVersion(fmt.Sprintf("%s-%s", pack.Version, pack.Release)) @@ -48,15 +48,15 @@ func (o Redhat) FillCveInfoFromOvalDB(r *models.ScanResult) (*models.ScanResult, } affected, _ := ver.NewVersion(p.Version) if current.LessThan(affected) { - r = o.fillOvalInfo(r, &definition) + o.fillOvalInfo(r, &definition) } } } } - return r, nil + return nil } -func (o Redhat) fillOvalInfo(r *models.ScanResult, definition *ovalmodels.Definition) *models.ScanResult { +func (o Redhat) fillOvalInfo(r *models.ScanResult, definition *ovalmodels.Definition) { for _, cve := range definition.Advisory.Cves { ovalContent := *o.convertToModel(cve.CveID, definition) vinfo, ok := r.ScannedCves.Get(cve.CveID) @@ -81,7 +81,6 @@ func (o Redhat) fillOvalInfo(r *models.ScanResult, definition *ovalmodels.Defini } r.ScannedCves.Upsert(vinfo) } - return r } func (o Redhat) convertToModel(cveID string, def *ovalmodels.Definition) *models.CveContent { diff --git a/report/util.go b/report/util.go index e813e676..bfee5d11 100644 --- a/report/util.go +++ b/report/util.go @@ -22,8 +22,10 @@ import ( "fmt" "strings" + "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" "github.com/gosuri/uitable" + "github.com/k0kubun/pp" ) const maxColWidth = 80 @@ -83,11 +85,17 @@ func formatShortPlainText(r models.ScanResult) string { stable.MaxColWidth = maxColWidth stable.Wrap = true - //TODO - // cves := r.KnownCves - // if !config.Conf.IgnoreUnscoredCves { - // cves = append(cves, r.UnknownCves...) - // } + vulns := r.ScannedCves + if !config.Conf.IgnoreUnscoredCves { + //TODO Refactoring + vulns = r.ScannedCves.Find(func(v models.VulnInfo) bool { + if 0 < v.CveContents.CvssV2Score() || 0 < v.CveContents.CvssV3Score() { + return true + } + return false + }) + } + pp.Println(vulns) var buf bytes.Buffer for i := 0; i < len(r.ServerInfo()); i++ {