Implement -format-full-text
This commit is contained in:
@@ -18,10 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
cvedict "github.com/kotakanbe/go-cve-dictionary/models"
|
||||
)
|
||||
|
||||
@@ -267,6 +269,21 @@ func (r ScanResult) CveSummary(ignoreUnscoreCves bool) string {
|
||||
high+medium+low+unknown, high, medium, low, unknown)
|
||||
}
|
||||
|
||||
// FormatTextReportHeadedr returns header of text report
|
||||
func (r ScanResult) FormatTextReportHeadedr() string {
|
||||
serverInfo := r.ServerInfo()
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < len(serverInfo); i++ {
|
||||
buf.WriteString("=")
|
||||
}
|
||||
return fmt.Sprintf("%s\n%s\n%s\t%s\n",
|
||||
r.ServerInfo(),
|
||||
buf.String(),
|
||||
r.CveSummary(config.Conf.IgnoreUnscoredCves),
|
||||
r.Packages.FormatUpdatablePacksSummary(),
|
||||
)
|
||||
}
|
||||
|
||||
// Confidence is a ranking how confident the CVE-ID was deteted correctly
|
||||
// Score: 0 - 100
|
||||
type Confidence struct {
|
||||
@@ -338,6 +355,17 @@ func (v VulnInfos) Find(f func(VulnInfo) bool) VulnInfos {
|
||||
return filtered
|
||||
}
|
||||
|
||||
// FindScoredVulns return socred vulnerabilities
|
||||
func (v VulnInfos) FindScoredVulns() VulnInfos {
|
||||
return v.Find(func(vv VulnInfo) bool {
|
||||
if 0 < vv.CveContents.MaxCvss2Score().Value.Score ||
|
||||
0 < vv.CveContents.MaxCvss3Score().Value.Score {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
// VulnInfo holds a vulnerability information and unsecure packages
|
||||
type VulnInfo struct {
|
||||
CveID string
|
||||
@@ -487,6 +515,11 @@ type Cvss2 struct {
|
||||
Severity string
|
||||
}
|
||||
|
||||
// Format CVSS Score and Vector
|
||||
func (c Cvss2) Format() string {
|
||||
return fmt.Sprintf("%3.1f/%s", c.Score, c.Vector)
|
||||
}
|
||||
|
||||
func cvss2ScoreToSeverity(score float64) string {
|
||||
if 7.0 <= score {
|
||||
return "HIGH"
|
||||
@@ -555,13 +588,18 @@ type CveContentCvss3 struct {
|
||||
Value Cvss3
|
||||
}
|
||||
|
||||
// Cvss3 has CVSS v3
|
||||
// Cvss3 has CVSS v3 Score, Vector and Severity
|
||||
type Cvss3 struct {
|
||||
Score float64
|
||||
Vector string
|
||||
Severity string
|
||||
}
|
||||
|
||||
// Format CVSS Score and Vector
|
||||
func (c Cvss3) Format() string {
|
||||
return fmt.Sprintf("%3.1f/CVSS:3.0/%s", c.Score, c.Vector)
|
||||
}
|
||||
|
||||
func cvss3ScoreToSeverity(score float64) string {
|
||||
if 9.0 <= score {
|
||||
return "CRITICAL"
|
||||
@@ -627,6 +665,22 @@ func (v CveContents) MaxCvss3Score() CveContentCvss3 {
|
||||
return value
|
||||
}
|
||||
|
||||
// FormatMaxCvssScore returns Max CVSS Score
|
||||
func (v CveContents) FormatMaxCvssScore() string {
|
||||
v2Max := v.MaxCvss2Score()
|
||||
v3Max := v.MaxCvss3Score()
|
||||
if v2Max.Value.Score <= v3Max.Value.Score {
|
||||
return fmt.Sprintf("%3.1f %s (%s)",
|
||||
v3Max.Value.Score,
|
||||
strings.ToUpper(v3Max.Value.Severity),
|
||||
v3Max.Type)
|
||||
}
|
||||
return fmt.Sprintf("%3.1f %s (%s)",
|
||||
v2Max.Value.Score,
|
||||
strings.ToUpper(v2Max.Value.Severity),
|
||||
v2Max.Type)
|
||||
}
|
||||
|
||||
// Titles returns tilte (TUI)
|
||||
func (v CveContents) Titles(lang, myFamily string) (values []CveContentStr) {
|
||||
if lang == "ja" {
|
||||
@@ -694,7 +748,7 @@ func (v CveContents) Summaries(lang, myFamily string) (values []CveContentStr) {
|
||||
}
|
||||
|
||||
// SourceLinks returns link of source
|
||||
func (v CveContents) SourceLinks(lang, myFamily string) (values []CveContentStr) {
|
||||
func (v CveContents) SourceLinks(lang, myFamily, cveID string) (values []CveContentStr) {
|
||||
if lang == "ja" {
|
||||
if cont, found := v[JVN]; found && !cont.Empty() {
|
||||
values = append(values, CveContentStr{JVN, cont.SourceLink})
|
||||
@@ -707,7 +761,23 @@ func (v CveContents) SourceLinks(lang, myFamily string) (values []CveContentStr)
|
||||
values = append(values, CveContentStr{ctype, cont.SourceLink})
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
if len(values) == 0 {
|
||||
return []CveContentStr{{
|
||||
Type: NVD,
|
||||
Value: "https://nvd.nist.gov/vuln/detail/" + cveID,
|
||||
}}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// VendorLink returns link of source
|
||||
func (v CveContents) VendorLink(myFamily string) CveContentStr {
|
||||
ctype := NewCveContentType(myFamily)
|
||||
if cont, ok := v[ctype]; ok {
|
||||
return CveContentStr{ctype, cont.SourceLink}
|
||||
}
|
||||
return CveContentStr{ctype, ""}
|
||||
}
|
||||
|
||||
// Severities returns Severities
|
||||
@@ -770,17 +840,20 @@ func (v CveContents) References(myFamily string) (values []CveContentRefs) {
|
||||
return
|
||||
}
|
||||
|
||||
// CweIDs returns CweIDs
|
||||
// CweIDs returns related CweIDs of the vulnerability
|
||||
func (v CveContents) CweIDs(myFamily string) (values []CveContentStr) {
|
||||
order := CveContentTypes{NewCveContentType(myFamily)}
|
||||
order = append(order, AllCveContetTypes.Except(append(order)...)...)
|
||||
|
||||
for _, ctype := range order {
|
||||
if cont, found := v[ctype]; found && 0 < len(cont.CweID) {
|
||||
values = append(values, CveContentStr{
|
||||
Type: ctype,
|
||||
Value: cont.CweID,
|
||||
})
|
||||
// RedHat's OVAL sometimes contains multiple CWE-IDs separated by spaces
|
||||
for _, cweID := range strings.Fields(cont.CweID) {
|
||||
values = append(values, CveContentStr{
|
||||
Type: ctype,
|
||||
Value: cweID,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
193
report/util.go
193
report/util.go
@@ -27,7 +27,7 @@ import (
|
||||
"github.com/gosuri/uitable"
|
||||
)
|
||||
|
||||
const maxColWidth = 100
|
||||
const maxColWidth = 80
|
||||
|
||||
func formatScanSummary(rs ...models.ScanResult) string {
|
||||
table := uitable.New()
|
||||
@@ -80,38 +80,18 @@ func formatOneLineSummary(rs ...models.ScanResult) string {
|
||||
}
|
||||
|
||||
func formatShortPlainText(r models.ScanResult) string {
|
||||
stable := uitable.New()
|
||||
stable.MaxColWidth = maxColWidth
|
||||
stable.Wrap = true
|
||||
|
||||
vulns := r.ScannedCves
|
||||
if !config.Conf.IgnoreUnscoredCves {
|
||||
vulns = r.ScannedCves.Find(func(v models.VulnInfo) bool {
|
||||
if 0 < v.CveContents.MaxCvss2Score().Value.Score ||
|
||||
0 < v.CveContents.MaxCvss3Score().Value.Score {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < len(r.ServerInfo()); i++ {
|
||||
buf.WriteString("=")
|
||||
}
|
||||
header := fmt.Sprintf("%s\n%s\n%s\t%s\n\n",
|
||||
r.ServerInfo(),
|
||||
buf.String(),
|
||||
r.CveSummary(config.Conf.IgnoreUnscoredCves),
|
||||
r.Packages.FormatUpdatablePacksSummary(),
|
||||
)
|
||||
|
||||
header := r.FormatTextReportHeadedr()
|
||||
if len(r.Errors) != 0 {
|
||||
return fmt.Sprintf(
|
||||
"%s\nError: Scan with --debug to view the details\n%s\n\n",
|
||||
header, r.Errors)
|
||||
}
|
||||
|
||||
vulns := r.ScannedCves
|
||||
if !config.Conf.IgnoreUnscoredCves {
|
||||
vulns = vulns.FindScoredVulns()
|
||||
}
|
||||
|
||||
if len(vulns) == 0 {
|
||||
return fmt.Sprintf(`
|
||||
%s
|
||||
@@ -120,61 +100,27 @@ func formatShortPlainText(r models.ScanResult) string {
|
||||
`, header, r.Packages.FormatUpdatablePacksSummary())
|
||||
}
|
||||
|
||||
stable := uitable.New()
|
||||
stable.MaxColWidth = maxColWidth
|
||||
stable.Wrap = true
|
||||
for _, vuln := range vulns {
|
||||
//TODO
|
||||
// var packsVer string
|
||||
// for _, name := range vuln.PackageNames {
|
||||
// // packages detected by OVAL may not be actually installed
|
||||
// if pack, ok := r.Packages[name]; ok {
|
||||
// packsVer += fmt.Sprintf("%s\n",
|
||||
// pack.FormatVersionFromTo())
|
||||
// }
|
||||
// }
|
||||
// for _, name := range vuln.CpeNames {
|
||||
// packsVer += name + "\n"
|
||||
// }
|
||||
|
||||
summaries := vuln.CveContents.Summaries(config.Conf.Lang, r.Family)
|
||||
links := vuln.CveContents.SourceLinks(config.Conf.Lang, r.Family)
|
||||
if len(links) == 0 {
|
||||
links = []models.CveContentStr{{
|
||||
Type: models.NVD,
|
||||
Value: "https://nvd.nist.gov/vuln/detail/" + vuln.CveID,
|
||||
}}
|
||||
}
|
||||
links := vuln.CveContents.SourceLinks(
|
||||
config.Conf.Lang, r.Family, vuln.CveID)
|
||||
|
||||
cvsses := ""
|
||||
for _, cvss := range vuln.CveContents.Cvss2Scores() {
|
||||
c2 := cvss.Value
|
||||
cvsses += fmt.Sprintf("%3.1f/%s (%s)\n",
|
||||
c2.Score, c2.Vector, cvss.Type)
|
||||
cvsses += fmt.Sprintf("%s (%s)\n", cvss.Value.Format(), cvss.Type)
|
||||
}
|
||||
cvsses += fmt.Sprintf("%s\n", vuln.Cvss2CalcURL())
|
||||
|
||||
cvsses += vuln.Cvss2CalcURL() + "\n"
|
||||
for _, cvss := range vuln.CveContents.Cvss3Scores() {
|
||||
c3 := cvss.Value
|
||||
cvsses += fmt.Sprintf("%3.1f/CVSS:3.0/%s (%s)\n",
|
||||
c3.Score, c3.Vector, cvss.Type)
|
||||
cvsses += fmt.Sprintf("%s (%s)\n", cvss.Value.Format(), cvss.Type)
|
||||
}
|
||||
if 0 < len(vuln.CveContents.Cvss3Scores()) {
|
||||
cvsses += fmt.Sprintf("%s\n", vuln.Cvss3CalcURL())
|
||||
}
|
||||
|
||||
var maxCvss string
|
||||
v2Max := vuln.CveContents.MaxCvss2Score()
|
||||
v3Max := vuln.CveContents.MaxCvss3Score()
|
||||
if v2Max.Value.Score <= v3Max.Value.Score {
|
||||
maxCvss = fmt.Sprintf("%3.1f %s (%s)",
|
||||
v3Max.Value.Score,
|
||||
strings.ToUpper(v3Max.Value.Severity),
|
||||
v3Max.Type)
|
||||
} else {
|
||||
maxCvss = fmt.Sprintf("%3.1f %s (%s)",
|
||||
v2Max.Value.Score,
|
||||
strings.ToUpper(v2Max.Value.Severity),
|
||||
v2Max.Type)
|
||||
cvsses += vuln.Cvss3CalcURL() + "\n"
|
||||
}
|
||||
|
||||
maxCvss := vuln.CveContents.FormatMaxCvssScore()
|
||||
rightCol := fmt.Sprintf(`%s
|
||||
%s
|
||||
---
|
||||
@@ -201,53 +147,76 @@ func formatShortPlainText(r models.ScanResult) string {
|
||||
}
|
||||
|
||||
func formatFullPlainText(r models.ScanResult) string {
|
||||
serverInfo := r.ServerInfo()
|
||||
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < len(serverInfo); i++ {
|
||||
buf.WriteString("=")
|
||||
}
|
||||
header := fmt.Sprintf("%s\n%s\n%s\t%s\n",
|
||||
r.ServerInfo(),
|
||||
buf.String(),
|
||||
r.CveSummary(config.Conf.IgnoreUnscoredCves),
|
||||
r.Packages.FormatUpdatablePacksSummary(),
|
||||
)
|
||||
|
||||
header := r.FormatTextReportHeadedr()
|
||||
if len(r.Errors) != 0 {
|
||||
return fmt.Sprintf(
|
||||
"%s\nError: Scan with --debug to view the details\n%s\n\n",
|
||||
header, r.Errors)
|
||||
}
|
||||
|
||||
//TODO
|
||||
// if len(r.KnownCves) == 0 && len(r.UnknownCves) == 0 {
|
||||
// return fmt.Sprintf(`
|
||||
// %s
|
||||
// No CVE-IDs are found in updatable packages.
|
||||
// %s
|
||||
// `, header, r.Packages.FormatUpdatablePacksSummary())
|
||||
// }
|
||||
vulns := r.ScannedCves
|
||||
if !config.Conf.IgnoreUnscoredCves {
|
||||
vulns = vulns.FindScoredVulns()
|
||||
}
|
||||
|
||||
// scoredReport, unscoredReport := []string{}, []string{}
|
||||
// scoredReport, unscoredReport = formatPlainTextDetails(r, r.Family)
|
||||
if len(vulns) == 0 {
|
||||
return fmt.Sprintf(`
|
||||
%s
|
||||
No CVE-IDs are found in updatable packages.
|
||||
%s
|
||||
`, header, r.Packages.FormatUpdatablePacksSummary())
|
||||
}
|
||||
|
||||
// unscored := ""
|
||||
// if !config.Conf.IgnoreUnscoredCves {
|
||||
// unscored = strings.Join(unscoredReport, "\n\n")
|
||||
// }
|
||||
table := uitable.New()
|
||||
table.MaxColWidth = maxColWidth
|
||||
table.Wrap = true
|
||||
for _, vuln := range vulns {
|
||||
table.AddRow(vuln.CveID)
|
||||
table.AddRow("----------------")
|
||||
table.AddRow("Max Score", vuln.CveContents.FormatMaxCvssScore())
|
||||
for _, cvss := range vuln.CveContents.Cvss2Scores() {
|
||||
table.AddRow(cvss.Type, cvss.Value.Format())
|
||||
}
|
||||
for _, cvss := range vuln.CveContents.Cvss3Scores() {
|
||||
table.AddRow(cvss.Type, cvss.Value.Format())
|
||||
}
|
||||
if 0 < len(vuln.CveContents.Cvss2Scores()) {
|
||||
table.AddRow("CVSSv2 Calc", vuln.Cvss2CalcURL())
|
||||
}
|
||||
if 0 < len(vuln.CveContents.Cvss3Scores()) {
|
||||
table.AddRow("CVSSv3 Calc", vuln.Cvss3CalcURL())
|
||||
}
|
||||
table.AddRow("Summary", vuln.CveContents.Summaries(
|
||||
config.Conf.Lang, r.Family)[0].Value)
|
||||
|
||||
// scored := strings.Join(scoredReport, "\n\n")
|
||||
// detail := fmt.Sprintf(`
|
||||
// %s
|
||||
links := vuln.CveContents.SourceLinks(
|
||||
config.Conf.Lang, r.Family, vuln.CveID)
|
||||
table.AddRow("Source", links[0].Value)
|
||||
|
||||
// %s
|
||||
// `,
|
||||
// scored,
|
||||
// unscored,
|
||||
// )
|
||||
// return fmt.Sprintf("%s\n%s\n%s", header, detail, formatChangelogs(r))
|
||||
return ""
|
||||
vendorLink := vuln.CveContents.VendorLink(r.Family)
|
||||
table.AddRow(fmt.Sprintf("Vendor (%s)", vendorLink.Type), vendorLink.Value)
|
||||
|
||||
for _, v := range vuln.CveContents.CweIDs(r.Family) {
|
||||
table.AddRow(fmt.Sprintf("%s (%s)", v.Value, v.Type), cweURL(v.Value))
|
||||
}
|
||||
|
||||
packsVer := []string{}
|
||||
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())
|
||||
}
|
||||
}
|
||||
for _, name := range vuln.CpeNames {
|
||||
packsVer = append(packsVer, name)
|
||||
}
|
||||
table.AddRow("Package/CPE", strings.Join(packsVer, "\n"))
|
||||
table.AddRow("Confidence", vuln.Confidence)
|
||||
|
||||
table.AddRow("\n")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s\n%s", header, table)
|
||||
}
|
||||
|
||||
//TODO
|
||||
@@ -393,10 +362,10 @@ func formatPlainTextDetails(r models.ScanResult, osFamily string) (scoredReport,
|
||||
// return fmt.Sprintf("%s\n", dtable)
|
||||
// }
|
||||
|
||||
type distroLink struct {
|
||||
title string
|
||||
url string
|
||||
}
|
||||
// type distroLink struct {
|
||||
// title string
|
||||
// url string
|
||||
// }
|
||||
|
||||
// distroLinks add Vendor URL of the CVE to table
|
||||
// func distroLinks(cveInfo models.CveInfo, osFamily string) []distroLink {
|
||||
|
||||
19
util/util.go
19
util/util.go
@@ -23,6 +23,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
// GenWorkers generates goroutine
|
||||
@@ -135,3 +136,21 @@ func Truncate(str string, length int) string {
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// VendorLink returns a URL of the given OS family and CVEID
|
||||
//TODO
|
||||
func VendorLink(family, cveID string) string {
|
||||
cType := models.NewCveContentType(family)
|
||||
switch cType {
|
||||
case models.RedHat:
|
||||
return "https://access.redhat.com/security/cve/" + cveID
|
||||
case models.Debian:
|
||||
return "https://security-tracker.debian.org/tracker/" + cveID
|
||||
case models.Ubuntu:
|
||||
return "http://people.ubuntu.com/~ubuntu-security/cve/" + cveID
|
||||
// case models.FreeBSD:
|
||||
// return "http://people.ubuntu.com/~ubuntu-security/cve/" + cveID
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user