From 73b011eba73d6d2599acd7dd5a4c86e8e2d06b7a Mon Sep 17 00:00:00 2001 From: Kota Kanbe Date: Tue, 23 May 2017 15:48:59 +0900 Subject: [PATCH] Sort results order by CVSS score, CVE-ID --- commands/report.go | 22 +---- models/cvecontents.go | 16 +++- models/cvecontents_test.go | 75 +++++++++++++--- models/vulninfos.go | 19 ++++- models/vulninfos_test.go | 171 +++++++++++++++++++++++++++++++++++++ report/util.go | 6 +- 6 files changed, 270 insertions(+), 39 deletions(-) diff --git a/commands/report.go b/commands/report.go index 442b936c..ee2ea724 100644 --- a/commands/report.go +++ b/commands/report.go @@ -299,8 +299,6 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{} c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves c.Conf.HTTPProxy = p.httpProxy - c.Conf.Pipe = p.pipe - c.Conf.FormatXML = p.formatXML c.Conf.FormatJSON = p.formatJSON c.Conf.FormatOneEMail = p.formatOneEMail @@ -310,6 +308,7 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{} c.Conf.GZIP = p.gzip c.Conf.Diff = p.diff + c.Conf.Pipe = p.pipe var dir string var err error @@ -405,30 +404,11 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{} } util.Log.Infof("Loaded: %s", dir) - //TODO dir if res, err = report.FillCveInfos(res, dir); err != nil { util.Log.Error(err) return subcommands.ExitFailure } - // TODO Filter, Sort - // TODO Add sort function to ScanResults - //remove - // 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) - // // } - // } - // } - for _, w := range reports { if err := w.Write(res...); err != nil { util.Log.Errorf("Failed to report: %s", err) diff --git a/models/cvecontents.go b/models/cvecontents.go index d0322089..21168e1a 100644 --- a/models/cvecontents.go +++ b/models/cvecontents.go @@ -185,7 +185,7 @@ func severityToScoreForUbuntu(severity string) float64 { // Convert Severity to Score for RedHat, Oracle OVAL // https://access.redhat.com/security/updates/classification -// Since I don't know the definition, Use the definition of CVSSv3 +// Use the definition of CVSSv3 because the exact definition of severity and score is not described. func severityToScoreForRedHat(severity string) float64 { switch strings.ToUpper(severity) { case "CRITICAL": @@ -231,7 +231,6 @@ func cvss3ScoreToSeverity(score float64) string { // Cvss3Scores returns CVSS V3 Score func (v CveContents) Cvss3Scores() (values []CveContentCvss3) { - //TODO Severity Ubuntu, Debian... order := []CveContentType{RedHat} for _, ctype := range order { if cont, found := v[ctype]; found && 0 < cont.Cvss3Score { @@ -255,7 +254,6 @@ func (v CveContents) Cvss3Scores() (values []CveContentCvss3) { // MaxCvss3Score returns Max CVSS V3 Score func (v CveContents) MaxCvss3Score() CveContentCvss3 { - //TODO Severity Ubuntu, Debian... order := []CveContentType{RedHat} max := 0.0 value := CveContentCvss3{ @@ -283,6 +281,18 @@ func (v CveContents) MaxCvss3Score() CveContentCvss3 { return value } +// MaxCvssScore returns max CVSS Score +// If there is no CVSS Score, return Severity as a numerical value. +func (v CveContents) MaxCvssScore() float64 { + v3Max := v.MaxCvss3Score() + v2Max := v.MaxCvss2Score() + max := v3Max.Value.Score + if max < v2Max.Value.Score { + max = v2Max.Value.Score + } + return max +} + // FormatMaxCvssScore returns Max CVSS Score func (v CveContents) FormatMaxCvssScore() string { v2Max := v.MaxCvss2Score() diff --git a/models/cvecontents_test.go b/models/cvecontents_test.go index 2428259b..dbafb037 100644 --- a/models/cvecontents_test.go +++ b/models/cvecontents_test.go @@ -21,18 +21,6 @@ import ( "testing" ) -var m = CveContent{ - Type: RedHat, - CveID: "CVE-2017-0001", - Title: "title", - Summary: "summary", - Severity: "High", - Cvss2Score: 8.0, - Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P", - Cvss3Score: 9.0, - Cvss3Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", -} - func TestExcept(t *testing.T) { var tests = []struct { in CveContents @@ -284,6 +272,69 @@ func TestMaxCvss3Scores(t *testing.T) { } } +func TestMaxCvssScores(t *testing.T) { + var tests = []struct { + in CveContents + out float64 + }{ + { + in: CveContents{ + NVD: { + Type: NVD, + Cvss3Score: 7.0, + }, + RedHat: { + Type: RedHat, + Cvss2Score: 8.0, + }, + }, + out: 8.0, + }, + { + in: CveContents{ + RedHat: { + Type: RedHat, + Cvss3Score: 8.0, + }, + }, + out: 8.0, + }, + { + in: CveContents{ + Ubuntu: { + Type: Ubuntu, + Severity: "HIGH", + }, + }, + out: 10.0, + }, + { + in: CveContents{ + Ubuntu: { + Type: Ubuntu, + Severity: "MEDIUM", + }, + NVD: { + Type: NVD, + Cvss2Score: 7.0, + }, + }, + out: 7.0, + }, + // Empty + { + in: CveContents{}, + out: 0, + }, + } + for i, tt := range tests { + actual := tt.in.MaxCvssScore() + if !reflect.DeepEqual(tt.out, actual) { + t.Errorf("\n[%d] expected: %v\n actual: %v\n", i, tt.out, actual) + } + } +} + func TestFormatMaxCvssScore(t *testing.T) { var tests = []struct { in CveContents diff --git a/models/vulninfos.go b/models/vulninfos.go index a1a03540..25a4757c 100644 --- a/models/vulninfos.go +++ b/models/vulninfos.go @@ -19,6 +19,7 @@ package models import ( "fmt" + "sort" "time" ) @@ -36,7 +37,7 @@ func (v VulnInfos) Find(f func(VulnInfo) bool) VulnInfos { return filtered } -// FindScoredVulns return socred vulnerabilities +// FindScoredVulns return scored vulnerabilities func (v VulnInfos) FindScoredVulns() VulnInfos { return v.Find(func(vv VulnInfo) bool { if 0 < vv.CveContents.MaxCvss2Score().Value.Score || @@ -47,6 +48,22 @@ func (v VulnInfos) FindScoredVulns() VulnInfos { }) } +// ToSortedSlice returns slice of VulnInfos that is sorted by CVE-ID +func (v VulnInfos) ToSortedSlice() (sorted []VulnInfo) { + for k := range v { + sorted = append(sorted, v[k]) + } + sort.Slice(sorted, func(i, j int) bool { + maxI := sorted[i].CveContents.MaxCvssScore() + maxJ := sorted[j].CveContents.MaxCvssScore() + if maxI != maxJ { + return maxJ < maxI + } + return sorted[i].CveID < sorted[j].CveID + }) + return +} + // VulnInfo holds a vulnerability information and unsecure packages type VulnInfo struct { CveID string diff --git a/models/vulninfos_test.go b/models/vulninfos_test.go index fced7012..1e796a3a 100644 --- a/models/vulninfos_test.go +++ b/models/vulninfos_test.go @@ -15,3 +15,174 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ package models + +import ( + "reflect" + "testing" +) + +func TestToSortedSlice(t *testing.T) { + var tests = []struct { + in VulnInfos + out []VulnInfo + }{ + { + in: VulnInfos{ + "CVE-2017-0002": { + CveID: "CVE-2017-0002", + CveContents: CveContents{ + NVD: { + Type: NVD, + Cvss3Score: 6.0, + }, + RedHat: { + Type: RedHat, + Cvss2Score: 7.0, + }, + }, + }, + "CVE-2017-0001": { + CveID: "CVE-2017-0001", + CveContents: CveContents{ + NVD: { + Type: NVD, + Cvss3Score: 7.0, + }, + RedHat: { + Type: RedHat, + Cvss2Score: 8.0, + }, + }, + }, + }, + out: []VulnInfo{ + { + CveID: "CVE-2017-0001", + CveContents: CveContents{ + NVD: { + Type: NVD, + Cvss3Score: 7.0, + }, + RedHat: { + Type: RedHat, + Cvss2Score: 8.0, + }, + }, + }, + { + CveID: "CVE-2017-0002", + CveContents: CveContents{ + NVD: { + Type: NVD, + Cvss3Score: 6.0, + }, + RedHat: { + Type: RedHat, + Cvss2Score: 7.0, + }, + }, + }, + }, + }, + // When max scores are the same, sort by CVE-ID + { + in: VulnInfos{ + "CVE-2017-0002": { + CveID: "CVE-2017-0002", + CveContents: CveContents{ + NVD: { + Type: NVD, + Cvss3Score: 6.0, + }, + RedHat: { + Type: RedHat, + Cvss2Score: 7.0, + }, + }, + }, + "CVE-2017-0001": { + CveID: "CVE-2017-0001", + CveContents: CveContents{ + RedHat: { + Type: RedHat, + Cvss2Score: 7.0, + }, + }, + }, + }, + out: []VulnInfo{ + { + CveID: "CVE-2017-0001", + CveContents: CveContents{ + RedHat: { + Type: RedHat, + Cvss2Score: 7.0, + }, + }, + }, + { + CveID: "CVE-2017-0002", + CveContents: CveContents{ + NVD: { + Type: NVD, + Cvss3Score: 6.0, + }, + RedHat: { + Type: RedHat, + Cvss2Score: 7.0, + }, + }, + }, + }, + }, + // When there are no cvss scores, sort by severity + { + in: VulnInfos{ + "CVE-2017-0002": { + CveID: "CVE-2017-0002", + CveContents: CveContents{ + Ubuntu: { + Type: Ubuntu, + Severity: "High", + }, + }, + }, + "CVE-2017-0001": { + CveID: "CVE-2017-0001", + CveContents: CveContents{ + Ubuntu: { + Type: Ubuntu, + Severity: "Low", + }, + }, + }, + }, + out: []VulnInfo{ + { + CveID: "CVE-2017-0002", + CveContents: CveContents{ + Ubuntu: { + Type: Ubuntu, + Severity: "High", + }, + }, + }, + { + CveID: "CVE-2017-0001", + CveContents: CveContents{ + Ubuntu: { + Type: Ubuntu, + Severity: "Low", + }, + }, + }, + }, + }, + } + for _, tt := range tests { + actual := tt.in.ToSortedSlice() + if !reflect.DeepEqual(tt.out, actual) { + t.Errorf("\nexpected: %v\n actual: %v\n", tt.out, actual) + } + } +} diff --git a/report/util.go b/report/util.go index 9e4eb230..de848095 100644 --- a/report/util.go +++ b/report/util.go @@ -111,7 +111,7 @@ func formatShortPlainText(r models.ScanResult) string { stable := uitable.New() stable.MaxColWidth = maxColWidth stable.Wrap = true - for _, vuln := range vulns { + for _, vuln := range vulns.ToSortedSlice() { summaries := vuln.CveContents.Summaries(config.Conf.Lang, r.Family) links := vuln.CveContents.SourceLinks( config.Conf.Lang, r.Family, vuln.CveID) @@ -178,7 +178,7 @@ func formatFullPlainText(r models.ScanResult) string { table := uitable.New() table.MaxColWidth = maxColWidth table.Wrap = true - for _, vuln := range vulns { + for _, vuln := range vulns.ToSortedSlice() { table.AddRow(vuln.CveID) table.AddRow("----------------") table.AddRow("Max Score", vuln.CveContents.FormatMaxCvssScore()) @@ -209,12 +209,14 @@ func formatFullPlainText(r models.ScanResult) string { } packsVer := []string{} + sort.Strings(vuln.PackageNames) for _, name := range vuln.PackageNames { // packages detected by OVAL may not be actually installed if pack, ok := r.Packages[name]; ok { packsVer = append(packsVer, pack.FormatVersionFromTo()) } } + sort.Strings(vuln.CpeNames) for _, name := range vuln.CpeNames { packsVer = append(packsVer, name) }