From 591786fde6b451587d370fb40c969b0a744b1963 Mon Sep 17 00:00:00 2001 From: MaineK00n Date: Mon, 13 Sep 2021 10:19:59 +0900 Subject: [PATCH] feat(oval): support new goval-dictionary model (#1280) * feat(oval): support new goval-dictionary model * chore: fix lint err * chore: set len of slice to 0 * fix(oval): avoid contamination of AffectedPackages by writing directly to defPacks * fix(oval): avoid contamination of AffectedPackages by writing directly to defPacks * feat(report): do not add duplicate CveContent * chore: goval-dictionary update * chore: go mod tidy * fix(oval): preload Advisory.Cves for Ubuntu https://github.com/kotakanbe/goval-dictionary/pull/152 Co-authored-by: Kota Kanbe --- detector/detector.go | 20 +++++- detector/github.go | 2 +- go.mod | 16 ++--- go.sum | 20 +++--- gost/debian.go | 2 +- gost/microsoft.go | 2 +- gost/redhat.go | 4 +- gost/ubuntu.go | 2 +- models/cvecontents.go | 15 ++++- models/library.go | 17 ++--- oval/alpine.go | 6 +- oval/debian.go | 141 +++++++++++++++++++++++------------------- oval/debian_test.go | 65 +++++++++++++++++-- oval/redhat.go | 68 +++++++++++--------- oval/redhat_test.go | 61 ++++++++++++++++-- oval/suse.go | 35 ++++++----- 16 files changed, 320 insertions(+), 156 deletions(-) diff --git a/detector/detector.go b/detector/detector.go index 14adcdcd..6c49c8d2 100644 --- a/detector/detector.go +++ b/detector/detector.go @@ -307,9 +307,23 @@ func FillCvesWithNvdJvn(r *models.ScanResult, cnf config.GoCveDictConf, logOpts if vinfo.CveContents == nil { vinfo.CveContents = models.CveContents{} } - for _, con := range append(nvds, jvns...) { + for _, con := range nvds { if !con.Empty() { - vinfo.CveContents[con.Type] = append(vinfo.CveContents[con.Type], con) + vinfo.CveContents[con.Type] = []models.CveContent{con} + } + } + for _, con := range jvns { + if !con.Empty() { + found := false + for _, cveCont := range vinfo.CveContents[con.Type] { + if con.SourceLink == cveCont.SourceLink { + found = true + break + } + } + if !found { + vinfo.CveContents[con.Type] = append(vinfo.CveContents[con.Type], con) + } } } vinfo.AlertDict = alerts @@ -364,7 +378,7 @@ func detectPkgsCvesWithOval(cnf config.GovalDictConf, r *models.ScanResult) erro } if !ok { if r.Family == constant.Debian { - logging.Log.Debug("Skip OVAL and Scan with gost alone.") + logging.Log.Infof("Skip OVAL and Scan with gost alone.") logging.Log.Infof("%s: %d CVEs are detected with OVAL", r.FormatServerName(), 0) return nil } diff --git a/detector/github.go b/detector/github.go index f4f3589a..2b60076c 100644 --- a/detector/github.go +++ b/detector/github.go @@ -126,7 +126,7 @@ func DetectGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string, if val, ok := r.ScannedCves[cveID]; ok { val.GitHubSecurityAlerts = val.GitHubSecurityAlerts.Add(m) - val.CveContents[models.GitHub] = append(val.CveContents[models.GitHub], cveContent) + val.CveContents[models.GitHub] = []models.CveContent{cveContent} r.ScannedCves[cveID] = val } else { v := models.VulnInfo{ diff --git a/go.mod b/go.mod index 80714cba..3b3b0691 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,6 @@ require ( github.com/hashicorp/go-uuid v1.0.2 github.com/hashicorp/go-version v1.3.0 github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c - github.com/jackc/pgx/v4 v4.13.0 // indirect github.com/jesseduffield/gocui v0.3.0 github.com/k0kubun/pp v3.0.1+incompatible github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f @@ -39,9 +38,9 @@ require ( github.com/knqyf263/gost v0.2.0 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/goval-dictionary v0.3.6-0.20210912113205-047e4dbfd356 github.com/kotakanbe/logrus-prefixed-formatter v0.0.0-20180123152602-928f7356cb96 - github.com/mattn/go-isatty v0.0.13 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mitchellh/go-homedir v1.1.0 @@ -49,6 +48,7 @@ require ( github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 // indirect github.com/olekukonko/tablewriter v0.0.5 github.com/parnurzeal/gorequest v0.2.16 + github.com/pelletier/go-toml v1.9.4 // indirect github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 github.com/sirupsen/logrus v1.8.1 github.com/spf13/afero v1.6.0 @@ -62,8 +62,10 @@ require ( golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 + gopkg.in/ini.v1 v1.63.0 // indirect gorm.io/driver/mysql v1.1.2 // indirect - gorm.io/gorm v1.21.14 // indirect + gorm.io/driver/postgres v1.1.1 // indirect + gorm.io/driver/sqlite v1.1.5 // indirect k8s.io/utils v0.0.0-20210111153108-fddb29f9d009 ) @@ -112,6 +114,7 @@ require ( 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/jackc/pgx/v4 v4.13.0 // 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 @@ -123,7 +126,6 @@ require ( 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 @@ -146,10 +148,8 @@ require ( 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 + gorm.io/gorm v1.21.15 // indirect moul.io/http2curl v1.0.0 // indirect ) diff --git a/go.sum b/go.sum index 9132c25a..b154361c 100644 --- a/go.sum +++ b/go.sum @@ -1050,8 +1050,8 @@ github.com/kotakanbe/go-cve-dictionary v0.7.2-0.20210907024016-69922490c76a h1:H 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= -github.com/kotakanbe/goval-dictionary v0.3.6-0.20210625044258-9be85404d7dd/go.mod h1:9BRxYJUgRVDpz4gXkpqReJG3l4bP1oI/eYr6Ok2jzWU= +github.com/kotakanbe/goval-dictionary v0.3.6-0.20210912113205-047e4dbfd356 h1:vF3bZmu2GC7xzYv6CwXhM0r82+AMNiMEuPDI/sKh9Tw= +github.com/kotakanbe/goval-dictionary v0.3.6-0.20210912113205-047e4dbfd356/go.mod h1:0wsKDaOgxLC7yD9zyOkeZjHS5+7SjQSiSYefgkWee0g= github.com/kotakanbe/logrus-prefixed-formatter v0.0.0-20180123152602-928f7356cb96 h1:xNVK0mQJdQjw+QYeaMM4G6fvucWr8rTGGIhlPakx1wU= github.com/kotakanbe/logrus-prefixed-formatter v0.0.0-20180123152602-928f7356cb96/go.mod h1:ljq48H1V+0Vh0u7ucA3LjR4AfkAeCpxrf7LaaCk8Vmo= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -1124,8 +1124,8 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= -github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-jsonpointer v0.0.0-20180225143300-37667080efed/go.mod h1:SDJ4hurDYyQ9/7nc+eCYtXqdufgK4Cq9TJlwPklqEYA= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -1141,6 +1141,7 @@ github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lL github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= @@ -1932,7 +1933,6 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -2281,18 +2281,20 @@ gorm.io/driver/mysql v1.1.1/go.mod h1:KdrTanmfLPPyAOeYGyG+UpDys7/7eeWT1zCq+oekYn gorm.io/driver/mysql v1.1.2 h1:OofcyE2lga734MxwcCW9uB4mWNXMr50uaGRVwQL2B0M= gorm.io/driver/mysql v1.1.2/go.mod h1:4P/X9vSc3WTrhTLZ259cpFd6xKNYiSSdSZngkSBGIMM= gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg= -gorm.io/driver/postgres v1.1.0 h1:afBljg7PtJ5lA6YUWluV2+xovIPhS+YiInuL3kUjrbk= gorm.io/driver/postgres v1.1.0/go.mod h1:hXQIwafeRjJvUm+OMxcFWyswJ/vevcpPLlGocwAwuqw= -gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM= +gorm.io/driver/postgres v1.1.1 h1:tWLmqYCyaoh89fi7DhM6QggujrOnmfo3H98AzgNAAu0= +gorm.io/driver/postgres v1.1.1/go.mod h1:tpe2xN7aCst1NUdYyWQyxPtnHC+Zfp6NEux9PXD1OU0= gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw= +gorm.io/driver/sqlite v1.1.5 h1:JU8G59VyKu1x1RMQgjefQnkZjDe9wHc1kARDZPu5dZs= +gorm.io/driver/sqlite v1.1.5/go.mod h1:NpaYMcVKEh6vLJ47VP6T7Weieu4H1Drs3dGD/K6GrGc= gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= gorm.io/gorm v1.21.9/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= gorm.io/gorm v1.21.10/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= gorm.io/gorm v1.21.11/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= -gorm.io/gorm v1.21.14 h1:NAR9A/3SoyiPVHouW/rlpMUZvuQZ6Z6UYGz+2tosSQo= -gorm.io/gorm v1.21.14/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= +gorm.io/gorm v1.21.15 h1:gAyaDoPw0lCyrSFWhBlahbUA1U4P5RViC1uIqoB+1Rk= +gorm.io/gorm v1.21.15/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= diff --git a/gost/debian.go b/gost/debian.go index aa463ede..e289b0a1 100644 --- a/gost/debian.go +++ b/gost/debian.go @@ -143,7 +143,7 @@ func (deb Debian) detectCVEsWithFixState(r *models.ScanResult, fixStatus string) if v.CveContents == nil { v.CveContents = models.NewCveContents(cve) } else { - v.CveContents[models.DebianSecurityTracker] = append(v.CveContents[models.DebianSecurityTracker], cve) + v.CveContents[models.DebianSecurityTracker] = []models.CveContent{cve} v.Confidences = models.Confidences{models.DebianSecurityTrackerMatch} } } else { diff --git a/gost/microsoft.go b/gost/microsoft.go index 212415b9..e0d04113 100644 --- a/gost/microsoft.go +++ b/gost/microsoft.go @@ -34,7 +34,7 @@ func (ms Microsoft) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err err if v.CveContents == nil { v.CveContents = models.CveContents{} } - v.CveContents[models.Microsoft] = append(v.CveContents[models.Microsoft], *cveCont) + v.CveContents[models.Microsoft] = []models.CveContent{*cveCont} v.Mitigations = append(v.Mitigations, mitigations...) r.ScannedCves[cveID] = v } diff --git a/gost/redhat.go b/gost/redhat.go index e702d18f..3471f653 100644 --- a/gost/redhat.go +++ b/gost/redhat.go @@ -103,7 +103,7 @@ func (red RedHat) setFixedCveToScanResult(cve *gostmodels.RedhatCVE, r *models.S if v.CveContents == nil { v.CveContents = models.NewCveContents(*cveCont) } else { - v.CveContents[models.RedHatAPI] = append(v.CveContents[models.RedHatAPI], *cveCont) + v.CveContents[models.RedHatAPI] = []models.CveContent{*cveCont} } } else { v = models.VulnInfo{ @@ -123,7 +123,7 @@ func (red RedHat) setUnfixedCveToScanResult(cve *gostmodels.RedhatCVE, r *models if v.CveContents == nil { v.CveContents = models.NewCveContents(*cveCont) } else { - v.CveContents[models.RedHatAPI] = append(v.CveContents[models.RedHatAPI], *cveCont) + v.CveContents[models.RedHatAPI] = []models.CveContent{*cveCont} } } else { v = models.VulnInfo{ diff --git a/gost/ubuntu.go b/gost/ubuntu.go index 793df383..0646cc61 100644 --- a/gost/ubuntu.go +++ b/gost/ubuntu.go @@ -116,7 +116,7 @@ func (ubu Ubuntu) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error if v.CveContents == nil { v.CveContents = models.NewCveContents(cve) } else { - v.CveContents[models.UbuntuAPI] = append(v.CveContents[models.UbuntuAPI], cve) + v.CveContents[models.UbuntuAPI] = []models.CveContent{cve} } } else { v = models.VulnInfo{ diff --git a/models/cvecontents.go b/models/cvecontents.go index 43da1c2c..0b10c81a 100644 --- a/models/cvecontents.go +++ b/models/cvecontents.go @@ -15,7 +15,20 @@ type CveContents map[CveContentType][]CveContent func NewCveContents(conts ...CveContent) CveContents { m := CveContents{} for _, cont := range conts { - m[cont.Type] = append(m[cont.Type], cont) + if cont.Type == Jvn { + found := false + for _, cveCont := range m[cont.Type] { + if cont.SourceLink == cveCont.SourceLink { + found = true + break + } + } + if !found { + m[cont.Type] = append(m[cont.Type], cont) + } + } else { + m[cont.Type] = []CveContent{cont} + } } return m } diff --git a/models/library.go b/models/library.go index 7340e3e0..69268034 100644 --- a/models/library.go +++ b/models/library.go @@ -106,15 +106,16 @@ func getCveContents(cveID string, vul trivyDBTypes.Vulnerability) (contents map[ refs = append(refs, Reference{Source: "trivy", Link: refURL}) } - content := CveContent{ - Type: Trivy, - CveID: cveID, - Title: vul.Title, - Summary: vul.Description, - Cvss3Severity: string(vul.Severity), - References: refs, + contents[Trivy] = []CveContent{ + { + Type: Trivy, + CveID: cveID, + Title: vul.Title, + Summary: vul.Description, + Cvss3Severity: string(vul.Severity), + References: refs, + }, } - contents[Trivy] = append(contents[Trivy], content) return contents } diff --git a/oval/alpine.go b/oval/alpine.go index e581a138..4483f489 100644 --- a/oval/alpine.go +++ b/oval/alpine.go @@ -54,8 +54,8 @@ func (o Alpine) FillWithOval(r *models.ScanResult) (nCVEs int, err error) { return len(relatedDefs.entries), nil } -func (o Alpine) update(r *models.ScanResult, defPacks defPacks) { - cveID := defPacks.def.Advisory.Cves[0].CveID +func (o Alpine) update(r *models.ScanResult, defpacks defPacks) { + cveID := defpacks.def.Advisory.Cves[0].CveID vinfo, ok := r.ScannedCves[cveID] if !ok { logging.Log.Debugf("%s is newly detected by OVAL", cveID) @@ -65,7 +65,7 @@ func (o Alpine) update(r *models.ScanResult, defPacks defPacks) { } } - vinfo.AffectedPackages = defPacks.toPackStatuses() + vinfo.AffectedPackages = defpacks.toPackStatuses() vinfo.AffectedPackages.Sort() r.ScannedCves[cveID] = vinfo } diff --git a/oval/debian.go b/oval/debian.go index ab0a7674..ef0ffc10 100644 --- a/oval/debian.go +++ b/oval/debian.go @@ -20,73 +20,75 @@ type DebianBase struct { Base } -func (o DebianBase) update(r *models.ScanResult, defPacks defPacks) { - ovalContent := *o.convertToModel(&defPacks.def) - ovalContent.Type = models.NewCveContentType(o.family) - vinfo, ok := r.ScannedCves[defPacks.def.Debian.CveID] - if !ok { - logging.Log.Debugf("%s is newly detected by OVAL", defPacks.def.Debian.CveID) - vinfo = models.VulnInfo{ - CveID: defPacks.def.Debian.CveID, - Confidences: []models.Confidence{models.OvalMatch}, - CveContents: models.NewCveContents(ovalContent), +func (o DebianBase) update(r *models.ScanResult, defpacks defPacks) { + for _, cve := range defpacks.def.Advisory.Cves { + ovalContent := o.convertToModel(cve.CveID, &defpacks.def) + if ovalContent == nil { + continue } - } else { - cveContents := vinfo.CveContents - ctype := models.NewCveContentType(o.family) - if _, ok := vinfo.CveContents[ctype]; ok { - logging.Log.Debugf("%s OVAL will be overwritten", - defPacks.def.Debian.CveID) + vinfo, ok := r.ScannedCves[cve.CveID] + if !ok { + logging.Log.Debugf("%s is newly detected by OVAL", cve.CveID) + vinfo = models.VulnInfo{ + CveID: cve.CveID, + Confidences: []models.Confidence{models.OvalMatch}, + CveContents: models.NewCveContents(*ovalContent), + } } else { - logging.Log.Debugf("%s is also detected by OVAL", - defPacks.def.Debian.CveID) - cveContents = models.CveContents{} - } - if r.Family != constant.Raspbian { + cveContents := vinfo.CveContents + if _, ok := vinfo.CveContents[ovalContent.Type]; ok { + logging.Log.Debugf("%s OVAL will be overwritten", cve.CveID) + } else { + logging.Log.Debugf("%s is also detected by OVAL", cve.CveID) + cveContents = models.CveContents{} + } vinfo.Confidences.AppendIfMissing(models.OvalMatch) - } else { - if len(vinfo.Confidences) == 0 { - vinfo.Confidences.AppendIfMissing(models.OvalMatch) + cveContents[ovalContent.Type] = []models.CveContent{*ovalContent} + vinfo.CveContents = cveContents + } + + // uniq(vinfo.AffectedPackages[].Name + defPacks.binpkgFixstat(map[string(=package name)]fixStat{})) + collectBinpkgFixstat := defPacks{ + binpkgFixstat: map[string]fixStat{}, + } + for packName, fixStatus := range defpacks.binpkgFixstat { + collectBinpkgFixstat.binpkgFixstat[packName] = fixStatus + } + + for _, pack := range vinfo.AffectedPackages { + collectBinpkgFixstat.binpkgFixstat[pack.Name] = fixStat{ + notFixedYet: pack.NotFixedYet, + fixedIn: pack.FixedIn, + isSrcPack: false, } } - cveContents[ctype] = append(cveContents[ctype], ovalContent) - vinfo.CveContents = cveContents - } - // uniq(vinfo.PackNames + defPacks.binpkgStat) - for _, pack := range vinfo.AffectedPackages { - defPacks.binpkgFixstat[pack.Name] = fixStat{ - notFixedYet: pack.NotFixedYet, - fixedIn: pack.FixedIn, - isSrcPack: false, - } - } - - // Update package status of source packages. - // In the case of Debian based Linux, sometimes source package name is defined as affected package in OVAL. - // To display binary package name showed in apt-get, need to convert source name to binary name. - for binName := range defPacks.binpkgFixstat { - if srcPack, ok := r.SrcPackages.FindByBinName(binName); ok { - for _, p := range defPacks.def.AffectedPacks { - if p.Name == srcPack.Name { - defPacks.binpkgFixstat[binName] = fixStat{ - notFixedYet: p.NotFixedYet, - fixedIn: p.Version, - isSrcPack: true, - srcPackName: srcPack.Name, + // Update package status of source packages. + // In the case of Debian based Linux, sometimes source package name is defined as affected package in OVAL. + // To display binary package name showed in apt-get, need to convert source name to binary name. + for binName := range defpacks.binpkgFixstat { + if srcPack, ok := r.SrcPackages.FindByBinName(binName); ok { + for _, p := range defpacks.def.AffectedPacks { + if p.Name == srcPack.Name { + collectBinpkgFixstat.binpkgFixstat[binName] = fixStat{ + notFixedYet: p.NotFixedYet, + fixedIn: p.Version, + isSrcPack: true, + srcPackName: srcPack.Name, + } } } } } - } - vinfo.AffectedPackages = defPacks.toPackStatuses() - vinfo.AffectedPackages.Sort() - r.ScannedCves[defPacks.def.Debian.CveID] = vinfo + vinfo.AffectedPackages = collectBinpkgFixstat.toPackStatuses() + vinfo.AffectedPackages.Sort() + r.ScannedCves[cve.CveID] = vinfo + } } -func (o DebianBase) convertToModel(def *ovalmodels.Definition) *models.CveContent { - refs := []models.Reference{} +func (o DebianBase) convertToModel(cveID string, def *ovalmodels.Definition) *models.CveContent { + refs := make([]models.Reference, 0, len(def.References)) for _, r := range def.References { refs = append(refs, models.Reference{ Link: r.RefURL, @@ -95,14 +97,23 @@ func (o DebianBase) convertToModel(def *ovalmodels.Definition) *models.CveConten }) } - return &models.CveContent{ - CveID: def.Debian.CveID, - Title: def.Title, - Summary: def.Description, - Cvss2Severity: def.Advisory.Severity, - Cvss3Severity: def.Advisory.Severity, - References: refs, + for _, cve := range def.Advisory.Cves { + if cve.CveID != cveID { + continue + } + + return &models.CveContent{ + Type: models.NewCveContentType(o.family), + CveID: cve.CveID, + Title: def.Title, + Summary: def.Description, + Cvss2Severity: def.Advisory.Severity, + Cvss3Severity: def.Advisory.Severity, + References: refs, + } } + + return nil } // Debian is the interface for Debian OVAL @@ -183,9 +194,9 @@ func (o Debian) FillWithOval(r *models.ScanResult) (nCVEs int, err error) { for _, vuln := range r.ScannedCves { if conts, ok := vuln.CveContents[models.Debian]; ok { - for _, cont := range conts { + for i, cont := range conts { cont.SourceLink = "https://security-tracker.debian.org/tracker/" + cont.CveID - vuln.CveContents[models.Debian] = append(vuln.CveContents[models.Debian], cont) + vuln.CveContents[models.Debian][i] = cont } } } @@ -502,9 +513,9 @@ func (o Ubuntu) fillWithOval(r *models.ScanResult, kernelNamesInOval []string) ( for _, vuln := range r.ScannedCves { if conts, ok := vuln.CveContents[models.Ubuntu]; ok { - for _, cont := range conts { + for i, cont := range conts { cont.SourceLink = "http://people.ubuntu.com/~ubuntu-security/cve/" + cont.CveID - vuln.CveContents[models.Ubuntu] = append(vuln.CveContents[models.Ubuntu], cont) + vuln.CveContents[models.Ubuntu][i] = cont } } } diff --git a/oval/debian_test.go b/oval/debian_test.go index ed47724e..6f52afad 100644 --- a/oval/debian_test.go +++ b/oval/debian_test.go @@ -30,8 +30,8 @@ func TestPackNamesOfUpdateDebian(t *testing.T) { }, defPacks: defPacks{ def: ovalmodels.Definition{ - Debian: ovalmodels.Debian{ - CveID: "CVE-2000-1000", + Advisory: ovalmodels.Advisory{ + Cves: []ovalmodels.Cve{{CveID: "CVE-2000-1000"}}, }, }, binpkgFixstat: map[string]fixStat{ @@ -53,15 +53,68 @@ func TestPackNamesOfUpdateDebian(t *testing.T) { }, }, }, + { + in: models.ScanResult{ + ScannedCves: models.VulnInfos{ + "CVE-2000-1000": models.VulnInfo{ + AffectedPackages: models.PackageFixStatuses{ + {Name: "packA"}, + }, + }, + "CVE-2000-1001": models.VulnInfo{ + AffectedPackages: models.PackageFixStatuses{ + {Name: "packC"}, + }, + }, + }, + }, + defPacks: defPacks{ + def: ovalmodels.Definition{ + Advisory: ovalmodels.Advisory{ + Cves: []ovalmodels.Cve{ + { + CveID: "CVE-2000-1000", + }, + { + CveID: "CVE-2000-1001", + }, + }, + }, + }, + binpkgFixstat: map[string]fixStat{ + "packB": { + notFixedYet: false, + }, + }, + }, + out: models.ScanResult{ + ScannedCves: models.VulnInfos{ + "CVE-2000-1000": models.VulnInfo{ + AffectedPackages: models.PackageFixStatuses{ + {Name: "packA"}, + {Name: "packB", NotFixedYet: false}, + }, + }, + "CVE-2000-1001": models.VulnInfo{ + AffectedPackages: models.PackageFixStatuses{ + {Name: "packB", NotFixedYet: false}, + {Name: "packC"}, + }, + }, + }, + }, + }, } // util.Log = util.NewCustomLogger() for i, tt := range tests { Debian{}.update(&tt.in, tt.defPacks) - e := tt.out.ScannedCves["CVE-2000-1000"].AffectedPackages - a := tt.in.ScannedCves["CVE-2000-1000"].AffectedPackages - if !reflect.DeepEqual(a, e) { - t.Errorf("[%d] expected: %#v\n actual: %#v\n", i, e, a) + for cveid := range tt.out.ScannedCves { + e := tt.out.ScannedCves[cveid].AffectedPackages + a := tt.in.ScannedCves[cveid].AffectedPackages + if !reflect.DeepEqual(a, e) { + t.Errorf("[%d] expected: %v\n actual: %v\n", i, e, a) + } } } } diff --git a/oval/redhat.go b/oval/redhat.go index be71a3b7..f99dd600 100644 --- a/oval/redhat.go +++ b/oval/redhat.go @@ -52,16 +52,16 @@ func (o RedHatBase) FillWithOval(r *models.ScanResult) (nCVEs int, err error) { switch models.NewCveContentType(o.family) { case models.RedHat: if conts, ok := vuln.CveContents[models.RedHat]; ok { - for _, cont := range conts { + for i, cont := range conts { cont.SourceLink = "https://access.redhat.com/security/cve/" + cont.CveID - vuln.CveContents[models.RedHat] = append(vuln.CveContents[models.RedHat], cont) + vuln.CveContents[models.RedHat][i] = cont } } case models.Oracle: if conts, ok := vuln.CveContents[models.Oracle]; ok { - for _, cont := range conts { + for i, cont := range conts { cont.SourceLink = fmt.Sprintf("https://linux.oracle.com/cve/%s.html", cont.CveID) - vuln.CveContents[models.Oracle] = append(vuln.CveContents[models.Oracle], cont) + vuln.CveContents[models.Oracle][i] = cont } } } @@ -102,57 +102,66 @@ var kernelRelatedPackNames = map[string]bool{ "python-perf": true, } -func (o RedHatBase) update(r *models.ScanResult, defPacks defPacks) (nCVEs int) { - ctype := models.NewCveContentType(o.family) - for _, cve := range defPacks.def.Advisory.Cves { - ovalContent := *o.convertToModel(cve.CveID, &defPacks.def) +func (o RedHatBase) update(r *models.ScanResult, defpacks defPacks) (nCVEs int) { + for _, cve := range defpacks.def.Advisory.Cves { + ovalContent := o.convertToModel(cve.CveID, &defpacks.def) + if ovalContent == nil { + continue + } vinfo, ok := r.ScannedCves[cve.CveID] if !ok { - logging.Log.Debugf("%s is newly detected by OVAL: DefID: %s", cve.CveID, defPacks.def.DefinitionID) + logging.Log.Debugf("%s is newly detected by OVAL: DefID: %s", cve.CveID, defpacks.def.DefinitionID) vinfo = models.VulnInfo{ CveID: cve.CveID, Confidences: models.Confidences{models.OvalMatch}, - CveContents: models.NewCveContents(ovalContent), + CveContents: models.NewCveContents(*ovalContent), } nCVEs++ } else { cveContents := vinfo.CveContents - if v, ok := vinfo.CveContents[ctype]; ok { + if v, ok := vinfo.CveContents[ovalContent.Type]; ok { for _, vv := range v { if vv.LastModified.After(ovalContent.LastModified) { - logging.Log.Debugf("%s ignored. DefID: %s ", cve.CveID, defPacks.def.DefinitionID) + logging.Log.Debugf("%s ignored. DefID: %s ", cve.CveID, defpacks.def.DefinitionID) } else { - logging.Log.Debugf("%s OVAL will be overwritten. DefID: %s", cve.CveID, defPacks.def.DefinitionID) + logging.Log.Debugf("%s OVAL will be overwritten. DefID: %s", cve.CveID, defpacks.def.DefinitionID) } } } else { - logging.Log.Debugf("%s also detected by OVAL. DefID: %s", cve.CveID, defPacks.def.DefinitionID) + logging.Log.Debugf("%s also detected by OVAL. DefID: %s", cve.CveID, defpacks.def.DefinitionID) cveContents = models.CveContents{} } vinfo.Confidences.AppendIfMissing(models.OvalMatch) - cveContents[ctype] = append(cveContents[ctype], ovalContent) + cveContents[ovalContent.Type] = []models.CveContent{*ovalContent} vinfo.CveContents = cveContents } vinfo.DistroAdvisories.AppendIfMissing( - o.convertToDistroAdvisory(&defPacks.def)) + o.convertToDistroAdvisory(&defpacks.def)) + + // uniq(vinfo.AffectedPackages[].Name + defPacks.binpkgFixstat(map[string(=package name)]fixStat{})) + collectBinpkgFixstat := defPacks{ + binpkgFixstat: map[string]fixStat{}, + } + for packName, fixStatus := range defpacks.binpkgFixstat { + collectBinpkgFixstat.binpkgFixstat[packName] = fixStatus + } - // uniq(vinfo.PackNames + defPacks.actuallyAffectedPackNames) for _, pack := range vinfo.AffectedPackages { - if stat, ok := defPacks.binpkgFixstat[pack.Name]; !ok { - defPacks.binpkgFixstat[pack.Name] = fixStat{ + if stat, ok := collectBinpkgFixstat.binpkgFixstat[pack.Name]; !ok { + collectBinpkgFixstat.binpkgFixstat[pack.Name] = fixStat{ notFixedYet: pack.NotFixedYet, fixedIn: pack.FixedIn, } } else if stat.notFixedYet { - defPacks.binpkgFixstat[pack.Name] = fixStat{ + collectBinpkgFixstat.binpkgFixstat[pack.Name] = fixStat{ notFixedYet: true, fixedIn: pack.FixedIn, } } } - vinfo.AffectedPackages = defPacks.toPackStatuses() + vinfo.AffectedPackages = collectBinpkgFixstat.toPackStatuses() vinfo.AffectedPackages.Sort() r.ScannedCves[cve.CveID] = vinfo } @@ -178,18 +187,19 @@ func (o RedHatBase) convertToDistroAdvisory(def *ovalmodels.Definition) *models. } func (o RedHatBase) convertToModel(cveID string, def *ovalmodels.Definition) *models.CveContent { + refs := make([]models.Reference, 0, len(def.References)) + for _, r := range def.References { + refs = append(refs, models.Reference{ + Link: r.RefURL, + Source: r.Source, + RefID: r.RefID, + }) + } + for _, cve := range def.Advisory.Cves { if cve.CveID != cveID { continue } - var refs []models.Reference - for _, r := range def.References { - refs = append(refs, models.Reference{ - Link: r.RefURL, - Source: r.Source, - RefID: r.RefID, - }) - } score2, vec2 := o.parseCvss2(cve.Cvss2) score3, vec3 := o.parseCvss3(cve.Cvss3) diff --git a/oval/redhat_test.go b/oval/redhat_test.go index bcd0000b..f4386a15 100644 --- a/oval/redhat_test.go +++ b/oval/redhat_test.go @@ -129,15 +129,68 @@ func TestPackNamesOfUpdate(t *testing.T) { }, }, }, + { + in: models.ScanResult{ + ScannedCves: models.VulnInfos{ + "CVE-2000-1000": models.VulnInfo{ + AffectedPackages: models.PackageFixStatuses{ + {Name: "packA"}, + }, + }, + "CVE-2000-1001": models.VulnInfo{ + AffectedPackages: models.PackageFixStatuses{ + {Name: "packC"}, + }, + }, + }, + }, + defPacks: defPacks{ + def: ovalmodels.Definition{ + Advisory: ovalmodels.Advisory{ + Cves: []ovalmodels.Cve{ + { + CveID: "CVE-2000-1000", + }, + { + CveID: "CVE-2000-1001", + }, + }, + }, + }, + binpkgFixstat: map[string]fixStat{ + "packB": { + notFixedYet: false, + }, + }, + }, + out: models.ScanResult{ + ScannedCves: models.VulnInfos{ + "CVE-2000-1000": models.VulnInfo{ + AffectedPackages: models.PackageFixStatuses{ + {Name: "packA"}, + {Name: "packB", NotFixedYet: false}, + }, + }, + "CVE-2000-1001": models.VulnInfo{ + AffectedPackages: models.PackageFixStatuses{ + {Name: "packB", NotFixedYet: false}, + {Name: "packC"}, + }, + }, + }, + }, + }, } // util.Log = util.Logger{}.NewCustomLogger() for i, tt := range tests { RedHat{}.update(&tt.in, tt.defPacks) - e := tt.out.ScannedCves["CVE-2000-1000"].AffectedPackages - a := tt.in.ScannedCves["CVE-2000-1000"].AffectedPackages - if !reflect.DeepEqual(a, e) { - t.Errorf("[%d] expected: %v\n actual: %v\n", i, e, a) + for cveid := range tt.out.ScannedCves { + e := tt.out.ScannedCves[cveid].AffectedPackages + a := tt.in.ScannedCves[cveid].AffectedPackages + if !reflect.DeepEqual(a, e) { + t.Errorf("[%d] expected: %v\n actual: %v\n", i, e, a) + } } } } diff --git a/oval/suse.go b/oval/suse.go index 92e2efd9..463b3a53 100644 --- a/oval/suse.go +++ b/oval/suse.go @@ -55,23 +55,23 @@ func (o SUSE) FillWithOval(r *models.ScanResult) (nCVEs int, err error) { for _, vuln := range r.ScannedCves { if conts, ok := vuln.CveContents[models.SUSE]; ok { - for _, cont := range conts { + for i, cont := range conts { cont.SourceLink = "https://security-tracker.debian.org/tracker/" + cont.CveID - vuln.CveContents[models.SUSE] = append(vuln.CveContents[models.SUSE], cont) + vuln.CveContents[models.SUSE][i] = cont } } } return len(relatedDefs.entries), nil } -func (o SUSE) update(r *models.ScanResult, defPacks defPacks) { - ovalContent := *o.convertToModel(&defPacks.def) +func (o SUSE) update(r *models.ScanResult, defpacks defPacks) { + ovalContent := *o.convertToModel(&defpacks.def) ovalContent.Type = models.NewCveContentType(o.family) - vinfo, ok := r.ScannedCves[defPacks.def.Title] + vinfo, ok := r.ScannedCves[defpacks.def.Title] if !ok { - logging.Log.Debugf("%s is newly detected by OVAL", defPacks.def.Title) + logging.Log.Debugf("%s is newly detected by OVAL", defpacks.def.Title) vinfo = models.VulnInfo{ - CveID: defPacks.def.Title, + CveID: defpacks.def.Title, Confidences: models.Confidences{models.OvalMatch}, CveContents: models.NewCveContents(ovalContent), } @@ -79,26 +79,33 @@ func (o SUSE) update(r *models.ScanResult, defPacks defPacks) { cveContents := vinfo.CveContents ctype := models.NewCveContentType(o.family) if _, ok := vinfo.CveContents[ctype]; ok { - logging.Log.Debugf("%s OVAL will be overwritten", defPacks.def.Title) + logging.Log.Debugf("%s OVAL will be overwritten", defpacks.def.Title) } else { - logging.Log.Debugf("%s is also detected by OVAL", defPacks.def.Title) + logging.Log.Debugf("%s is also detected by OVAL", defpacks.def.Title) cveContents = models.CveContents{} } vinfo.Confidences.AppendIfMissing(models.OvalMatch) - cveContents[ctype] = append(cveContents[ctype], ovalContent) + cveContents[ctype] = []models.CveContent{ovalContent} vinfo.CveContents = cveContents } - // uniq(vinfo.PackNames + defPacks.actuallyAffectedPackNames) + // uniq(vinfo.AffectedPackages[].Name + defPacks.binpkgFixstat(map[string(=package name)]fixStat{})) + collectBinpkgFixstat := defPacks{ + binpkgFixstat: map[string]fixStat{}, + } + for packName, fixStatus := range defpacks.binpkgFixstat { + collectBinpkgFixstat.binpkgFixstat[packName] = fixStatus + } + for _, pack := range vinfo.AffectedPackages { - defPacks.binpkgFixstat[pack.Name] = fixStat{ + collectBinpkgFixstat.binpkgFixstat[pack.Name] = fixStat{ notFixedYet: pack.NotFixedYet, fixedIn: pack.FixedIn, } } - vinfo.AffectedPackages = defPacks.toPackStatuses() + vinfo.AffectedPackages = collectBinpkgFixstat.toPackStatuses() vinfo.AffectedPackages.Sort() - r.ScannedCves[defPacks.def.Title] = vinfo + r.ScannedCves[defpacks.def.Title] = vinfo } func (o SUSE) convertToModel(def *ovalmodels.Definition) *models.CveContent {