From 008da49b8334594dfb90c320b8dc5785f0778e2e Mon Sep 17 00:00:00 2001 From: kota kanbe Date: Mon, 31 Jul 2017 17:17:35 +0900 Subject: [PATCH] Imlement OVAL scan on Oracle Linux --- models/cvecontents.go | 2 ++ models/vulninfos.go | 4 ++-- oval/debian.go | 2 ++ oval/oval.go | 7 ++++-- oval/redhat.go | 55 ++++++++++++++++++++++++++++++++++--------- report/report.go | 15 ++++++------ report/tui.go | 34 +++++++++++++++++--------- report/util.go | 12 +++++----- scan/redhat.go | 10 ++++++-- 9 files changed, 100 insertions(+), 41 deletions(-) diff --git a/models/cvecontents.go b/models/cvecontents.go index 68822888..170b3553 100644 --- a/models/cvecontents.go +++ b/models/cvecontents.go @@ -199,6 +199,8 @@ func NewCveContentType(name string) CveContentType { return JVN case "redhat", "centos": return RedHat + case "oracle": + return Oracle case "ubuntu": return Ubuntu case "debian": diff --git a/models/vulninfos.go b/models/vulninfos.go index 01107497..edd0d15a 100644 --- a/models/vulninfos.go +++ b/models/vulninfos.go @@ -53,7 +53,7 @@ func (v VulnInfos) FindScoredVulns() VulnInfos { }) } -// ToSortedSlice returns slice of VulnInfos that is sorted by CVE-ID +// ToSortedSlice returns slice of VulnInfos that is sorted by Score, CVE-ID func (v VulnInfos) ToSortedSlice() (sorted []VulnInfo) { for k := range v { sorted = append(sorted, v[k]) @@ -244,7 +244,7 @@ func (v VulnInfo) Cvss3Scores() (values []CveContentCvss) { Type: CVSS3, Score: cont.Cvss3Score, Vector: cont.Cvss3Vector, - Severity: sev, + Severity: strings.ToUpper(sev), }, }) } diff --git a/oval/debian.go b/oval/debian.go index 9bccf21d..2c18ce7a 100644 --- a/oval/debian.go +++ b/oval/debian.go @@ -139,6 +139,7 @@ func (o Debian) FillWithOval(r *models.ScanResult) error { } } + // TODO merge to VulnInfo.VendorLinks for _, vuln := range r.ScannedCves { if cont, ok := vuln.CveContents[models.Debian]; ok { cont.SourceLink = "https://security-tracker.debian.org/tracker/" + cont.CveID @@ -174,6 +175,7 @@ func (o Ubuntu) FillWithOval(r *models.ScanResult) error { } } + // TODO merge to VulnInfo.VendorLinks for _, vuln := range r.ScannedCves { if cont, ok := vuln.CveContents[models.Ubuntu]; ok { cont.SourceLink = "http://people.ubuntu.com/~ubuntu-security/cve/" + cont.CveID diff --git a/oval/oval.go b/oval/oval.go index 3c6f492d..a4f15965 100644 --- a/oval/oval.go +++ b/oval/oval.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "strings" "time" "github.com/cenkalti/backoff" @@ -115,13 +116,15 @@ func (b Base) CheckIfOvalFresh(osFamily, release string) (ok bool, err error) { } } + major := strings.Split(release, ".")[0] since := time.Now() since = since.AddDate(0, 0, -3) if lastModified.Before(since) { - util.Log.Warnf("%s-%s OVAL is old, last modified is %s. It's recommended to update OVAL to improve scanning accuracy. To update OVAL database, see https://github.com/kotakanbe/goval-dictionary#usage", - osFamily, release, lastModified) + util.Log.Warnf("OVAL for %s %s is old, last modified is %s. It's recommended to update OVAL to improve scanning accuracy. How to update OVAL database, see https://github.com/kotakanbe/goval-dictionary#usage", + osFamily, major, lastModified) return false, nil } + util.Log.Infof("OVAL is fresh: %s %s ", osFamily, major) return true, nil } diff --git a/oval/redhat.go b/oval/redhat.go index d0ff57e8..9684bf8a 100644 --- a/oval/redhat.go +++ b/oval/redhat.go @@ -16,7 +16,10 @@ import ( ) // RedHatBase is the base struct for RedHat and CentOS -type RedHatBase struct{ Base } +type RedHatBase struct { + Base + family string +} // FillWithOval returns scan result after updating CVE info by OVAL func (o RedHatBase) FillWithOval(r *models.ScanResult) error { @@ -34,9 +37,17 @@ func (o RedHatBase) FillWithOval(r *models.ScanResult) error { } } + // TODO merge to VulnInfo.VendorLinks for _, vuln := range r.ScannedCves { - if cont, ok := vuln.CveContents[models.RedHat]; ok { - cont.SourceLink = "https://access.redhat.com/security/cve/" + cont.CveID + switch models.NewCveContentType(o.family) { + case models.RedHat: + if cont, ok := vuln.CveContents[models.RedHat]; ok { + cont.SourceLink = "https://access.redhat.com/security/cve/" + cont.CveID + } + case models.Oracle: + if cont, ok := vuln.CveContents[models.Oracle]; ok { + cont.SourceLink = fmt.Sprintf("https://linux.oracle.com/cve/%s.html", cont.CveID) + } } } return nil @@ -71,7 +82,7 @@ func (o RedHatBase) getDefsByPackNameFromOvalDB(osRelease string, var ovaldb db.DB if ovaldb, err = db.NewDB( - ovalconf.RedHat, + o.family, ovalconf.Conf.DBType, ovalconf.Conf.DBPath, ovalconf.Conf.DebugSQL, @@ -82,7 +93,7 @@ func (o RedHatBase) getDefsByPackNameFromOvalDB(osRelease string, for _, pack := range packs { definitions, err := ovaldb.GetByPackName(osRelease, pack.Name) if err != nil { - return nil, fmt.Errorf("Failed to get RedHat OVAL info by package name: %v", err) + return nil, fmt.Errorf("Failed to get %s OVAL info by package name: %v", o.family, err) } for _, def := range definitions { current := ver.NewVersion(fmt.Sprintf("%s-%s", pack.Version, pack.Release)) @@ -99,6 +110,7 @@ func (o RedHatBase) getDefsByPackNameFromOvalDB(osRelease string, } func (o RedHatBase) update(r *models.ScanResult, definition *ovalmodels.Definition) { + ctype := models.NewCveContentType(o.family) for _, cve := range definition.Advisory.Cves { ovalContent := *o.convertToModel(cve.CveID, definition) vinfo, ok := r.ScannedCves[cve.CveID] @@ -112,7 +124,7 @@ func (o RedHatBase) update(r *models.ScanResult, definition *ovalmodels.Definiti } } else { cveContents := vinfo.CveContents - if _, ok := vinfo.CveContents[models.RedHat]; ok { + if _, ok := vinfo.CveContents[ctype]; ok { util.Log.Debugf("%s will be updated by OVAL", cve.CveID) } else { util.Log.Debugf("%s also detected by OVAL", cve.CveID) @@ -122,7 +134,7 @@ func (o RedHatBase) update(r *models.ScanResult, definition *ovalmodels.Definiti if vinfo.Confidence.Score < models.OvalMatch.Score { vinfo.Confidence = models.OvalMatch } - cveContents[models.RedHat] = ovalContent + cveContents[ctype] = ovalContent vinfo.CveContents = cveContents } r.ScannedCves[cve.CveID] = vinfo @@ -147,7 +159,7 @@ func (o RedHatBase) convertToModel(cveID string, def *ovalmodels.Definition) *mo score3, vec3 := o.parseCvss3(cve.Cvss3) return &models.CveContent{ - Type: models.RedHat, + Type: models.NewCveContentType(o.family), CveID: cve.CveID, Title: def.Title, Summary: def.Description, @@ -156,7 +168,6 @@ func (o RedHatBase) convertToModel(cveID string, def *ovalmodels.Definition) *mo Cvss2Vector: vec2, Cvss3Score: score3, Cvss3Vector: vec3, - SourceLink: "https://access.redhat.com/security/cve/" + cve.CveID, References: refs, CweID: cve.Cwe, Published: def.Advisory.Issued, @@ -201,7 +212,11 @@ type RedHat struct { // NewRedhat creates OVAL client for Redhat func NewRedhat() RedHat { - return RedHat{} + return RedHat{ + RedHatBase{ + family: config.RedHat, + }, + } } // CentOS is the interface for CentOS OVAL @@ -211,5 +226,23 @@ type CentOS struct { // NewCentOS creates OVAL client for CentOS func NewCentOS() CentOS { - return CentOS{} + return CentOS{ + RedHatBase{ + family: config.CentOS, + }, + } +} + +// Oracle is the interface for CentOS OVAL +type Oracle struct { + RedHatBase +} + +// NewOracle creates OVAL client for Oracle +func NewOracle() Oracle { + return Oracle{ + RedHatBase{ + family: config.Oracle, + }, + } } diff --git a/report/report.go b/report/report.go index 1206a900..d9ed1f0e 100644 --- a/report/report.go +++ b/report/report.go @@ -19,6 +19,7 @@ package report import ( "fmt" + "strings" c "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" @@ -158,17 +159,17 @@ func fillWithOval(r *models.ScanResult) (err error) { ovalClient = oval.NewCentOS() //use RedHat's OVAL ovalFamily = c.RedHat + case c.Oracle: + ovalClient = oval.NewOracle() + ovalFamily = c.Oracle //TODO - // case c.Oracle: - // ovalClient = oval.New() - // ovalFamily = c.Oracle // case c.Suse: // ovalClient = oval.New() // ovalFamily = c.Oracle - case c.Amazon, c.Oracle, c.Raspbian, c.FreeBSD: + case c.Amazon, c.Raspbian, c.FreeBSD: return nil default: - return fmt.Errorf("Oval %s is not implemented yet", r.Family) + return fmt.Errorf("OVAL for %s is not implemented yet", r.Family) } ok, err := ovalClient.CheckIfOvalFetched(ovalFamily, r.Release) @@ -176,7 +177,8 @@ func fillWithOval(r *models.ScanResult) (err error) { return err } if !ok { - util.Log.Warnf("OVAL entries of %s-%s are not found. It's recommended to use OVAL to improve scanning accuracy. To fetch OVAL, see https://github.com/kotakanbe/goval-dictionary#usage , Then report with --ovaldb-path or --ovaldb-url flag", r.Family, r.Release) + major := strings.Split(r.Release, ".")[0] + util.Log.Warnf("OVAL entries of %s %s are not found. It's recommended to use OVAL to improve scanning accuracy. To fetch OVAL, see https://github.com/kotakanbe/goval-dictionary#usage , Then report with --ovaldb-path or --ovaldb-url flag", ovalFamily, major) return nil } @@ -184,7 +186,6 @@ func fillWithOval(r *models.ScanResult) (err error) { if err != nil { return err } - util.Log.Infof("OVAL is fresh: %s-%s ", r.Family, r.Release) if err := ovalClient.FillWithOval(r); err != nil { return err diff --git a/report/tui.go b/report/tui.go index 1d04de20..1364a6b8 100644 --- a/report/tui.go +++ b/report/tui.go @@ -233,14 +233,14 @@ func movable(v *gocui.View, nextY int) (ok bool, yLimit int) { } return true, yLimit case "detail": - if currentDetailLimitY < nextY { - return false, currentDetailLimitY - } + // if currentDetailLimitY < nextY { + // return false, currentDetailLimitY + // } return true, currentDetailLimitY case "changelog": - if currentChangelogLimitY < nextY { - return false, currentChangelogLimitY - } + // if currentChangelogLimitY < nextY { + // return false, currentChangelogLimitY + // } return true, currentChangelogLimitY default: return true, 0 @@ -733,7 +733,7 @@ func setChangelogLayout(g *gocui.Gui) error { type dataForTmpl struct { CveID string - Cvsses []models.CveContentCvss + Cvsses string Summary string Confidence models.Confidence Cwes []models.CveContentStr @@ -792,9 +792,23 @@ func detailLines() (string, error) { summary := vinfo.Summaries(r.Lang, r.Family)[0] + table := uitable.New() + table.MaxColWidth = maxColWidth + table.Wrap = true + scores := append(vinfo.Cvss3Scores(), vinfo.Cvss2Scores()...) + var cols []interface{} + for _, score := range scores { + cols = []interface{}{ + score.Value.Severity, + score.Value.Format(), + score.Type, + } + table.AddRow(cols...) + } + data := dataForTmpl{ CveID: vinfo.CveID, - Cvsses: append(vinfo.Cvss3Scores(), vinfo.Cvss2Scores()...), + Cvsses: fmt.Sprintf("%s\n", table), Summary: fmt.Sprintf("%s (%s)", summary.Value, summary.Type), Confidence: vinfo.Confidence, Cwes: vinfo.CveContents.CweIDs(r.Family), @@ -817,9 +831,7 @@ const mdTemplate = ` CVSS Scores -------------- -{{range .Cvsses -}} -* {{.Value.Severity}} {{.Value.Format}} ({{.Type}}) -{{end}} +{{.Cvsses }} Summary -------------- diff --git a/report/util.go b/report/util.go index b92d1c5f..f9085377 100644 --- a/report/util.go +++ b/report/util.go @@ -100,9 +100,9 @@ func formatShortPlainText(r models.ScanResult) string { if len(vulns) == 0 { return fmt.Sprintf(` - %s - No CVE-IDs are found in updatable packages. - %s +%s +No CVE-IDs are found in updatable packages. +%s `, header, r.Packages.FormatUpdatablePacksSummary()) } @@ -174,9 +174,9 @@ func formatFullPlainText(r models.ScanResult) string { if len(vulns) == 0 { return fmt.Sprintf(` - %s - No CVE-IDs are found in updatable packages. - %s +%s +No CVE-IDs are found in updatable packages. +%s `, header, r.Packages.FormatUpdatablePacksSummary()) } diff --git a/scan/redhat.go b/scan/redhat.go index f08b96a1..13b5cdbc 100644 --- a/scan/redhat.go +++ b/scan/redhat.go @@ -265,7 +265,13 @@ func (o *redhat) scanPackages() error { func (o *redhat) scanInstalledPackages() (models.Packages, error) { installed := models.Packages{} - cmd := "rpm -qa --queryformat '%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n'" + var cmd string + majorVersion, _ := o.Distro.MajorVersion() + if majorVersion < 6 { + cmd = "rpm -qa --queryformat '%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n'" + } else { + cmd = "rpm -qa --queryformat '%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n'" + } r := o.exec(cmd, noSudo) if r.isSuccess() { // openssl 0 1.0.1e 30.el6.11 x86_64 @@ -295,7 +301,7 @@ func (o *redhat) parseInstalledPackagesLine(line string) (models.Package, error) } ver := "" epoch := fields[1] - if epoch == "0" { + if epoch == "0" || epoch == "(none)" { ver = fields[2] } else { ver = fmt.Sprintf("%s:%s", epoch, fields[2])