1000 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1000 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package models
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"regexp"
 | 
						|
	"sort"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/future-architect/vuls/logging"
 | 
						|
	exploitmodels "github.com/vulsio/go-exploitdb/models"
 | 
						|
)
 | 
						|
 | 
						|
// VulnInfos has a map of VulnInfo
 | 
						|
// Key: CveID
 | 
						|
type VulnInfos map[string]VulnInfo
 | 
						|
 | 
						|
// Find elements that matches the function passed in argument
 | 
						|
func (v VulnInfos) Find(f func(VulnInfo) bool) VulnInfos {
 | 
						|
	filtered := VulnInfos{}
 | 
						|
	for _, vv := range v {
 | 
						|
		if f(vv) {
 | 
						|
			filtered[vv.CveID] = vv
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return filtered
 | 
						|
}
 | 
						|
 | 
						|
// FilterByCvssOver return scored vulnerabilities
 | 
						|
func (v VulnInfos) FilterByCvssOver(over float64) (_ VulnInfos, nFiltered int) {
 | 
						|
	return v.Find(func(v VulnInfo) bool {
 | 
						|
		if over <= v.MaxCvssScore().Value.Score {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
		nFiltered++
 | 
						|
		return false
 | 
						|
	}), nFiltered
 | 
						|
}
 | 
						|
 | 
						|
// FilterByConfidenceOver scored vulnerabilities
 | 
						|
func (v VulnInfos) FilterByConfidenceOver(over int) (_ VulnInfos, nFiltered int) {
 | 
						|
	return v.Find(func(v VulnInfo) bool {
 | 
						|
		for _, c := range v.Confidences {
 | 
						|
			if over <= c.Score {
 | 
						|
				return true
 | 
						|
			}
 | 
						|
		}
 | 
						|
		nFiltered++
 | 
						|
		return false
 | 
						|
	}), nFiltered
 | 
						|
}
 | 
						|
 | 
						|
// FilterIgnoreCves filter function.
 | 
						|
func (v VulnInfos) FilterIgnoreCves(ignoreCveIDs []string) (_ VulnInfos, nFiltered int) {
 | 
						|
	return v.Find(func(v VulnInfo) bool {
 | 
						|
		for _, c := range ignoreCveIDs {
 | 
						|
			if v.CveID == c {
 | 
						|
				nFiltered++
 | 
						|
				return false
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return true
 | 
						|
	}), nFiltered
 | 
						|
}
 | 
						|
 | 
						|
// FilterUnfixed filter unfixed CVE-IDs
 | 
						|
func (v VulnInfos) FilterUnfixed(ignoreUnfixed bool) (_ VulnInfos, nFiltered int) {
 | 
						|
	if !ignoreUnfixed {
 | 
						|
		return v, 0
 | 
						|
	}
 | 
						|
	return v.Find(func(v VulnInfo) bool {
 | 
						|
		// Report cves detected by CPE because Vuls can't know 'fixed' or 'unfixed'
 | 
						|
		if len(v.CpeURIs) != 0 {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
		NotFixedAll := true
 | 
						|
		for _, p := range v.AffectedPackages {
 | 
						|
			NotFixedAll = NotFixedAll && p.NotFixedYet
 | 
						|
		}
 | 
						|
		if NotFixedAll {
 | 
						|
			nFiltered++
 | 
						|
		}
 | 
						|
		return !NotFixedAll
 | 
						|
	}), nFiltered
 | 
						|
}
 | 
						|
 | 
						|
// FilterIgnorePkgs is filter function.
 | 
						|
func (v VulnInfos) FilterIgnorePkgs(ignorePkgsRegexps []string) (_ VulnInfos, nFiltered int) {
 | 
						|
	regexps := []*regexp.Regexp{}
 | 
						|
	for _, pkgRegexp := range ignorePkgsRegexps {
 | 
						|
		re, err := regexp.Compile(pkgRegexp)
 | 
						|
		if err != nil {
 | 
						|
			logging.Log.Warnf("Failed to parse %s. err: %+v", pkgRegexp, err)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		regexps = append(regexps, re)
 | 
						|
	}
 | 
						|
	if len(regexps) == 0 {
 | 
						|
		return v, 0
 | 
						|
	}
 | 
						|
 | 
						|
	return v.Find(func(v VulnInfo) bool {
 | 
						|
		if len(v.AffectedPackages) == 0 {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
		for _, p := range v.AffectedPackages {
 | 
						|
			match := false
 | 
						|
			for _, re := range regexps {
 | 
						|
				if re.MatchString(p.Name) {
 | 
						|
					match = true
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if !match {
 | 
						|
				return true
 | 
						|
			}
 | 
						|
		}
 | 
						|
		nFiltered++
 | 
						|
		return false
 | 
						|
	}), nFiltered
 | 
						|
}
 | 
						|
 | 
						|
// FindScoredVulns return scored vulnerabilities
 | 
						|
func (v VulnInfos) FindScoredVulns() (_ VulnInfos, nFiltered int) {
 | 
						|
	return v.Find(func(vv VulnInfo) bool {
 | 
						|
		if 0 < vv.MaxCvss2Score().Value.Score ||
 | 
						|
			0 < vv.MaxCvss3Score().Value.Score {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
		nFiltered++
 | 
						|
		return false
 | 
						|
	}), nFiltered
 | 
						|
}
 | 
						|
 | 
						|
// 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])
 | 
						|
	}
 | 
						|
	sort.Slice(sorted, func(i, j int) bool {
 | 
						|
		maxI := sorted[i].MaxCvssScore()
 | 
						|
		maxJ := sorted[j].MaxCvssScore()
 | 
						|
		if maxI.Value.Score != maxJ.Value.Score {
 | 
						|
			return maxJ.Value.Score < maxI.Value.Score
 | 
						|
		}
 | 
						|
		return sorted[i].CveID < sorted[j].CveID
 | 
						|
	})
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// CountGroupBySeverity summarize the number of CVEs group by CVSSv2 Severity
 | 
						|
func (v VulnInfos) CountGroupBySeverity() map[string]int {
 | 
						|
	m := map[string]int{}
 | 
						|
	for _, vInfo := range v {
 | 
						|
		score := vInfo.MaxCvss3Score().Value.Score
 | 
						|
		if score < 0.1 {
 | 
						|
			score = vInfo.MaxCvss2Score().Value.Score
 | 
						|
		}
 | 
						|
		switch {
 | 
						|
		case 9 <= score:
 | 
						|
			m["Critical"]++
 | 
						|
		case 7.0 <= score:
 | 
						|
			m["High"]++
 | 
						|
		case 4.0 <= score:
 | 
						|
			m["Medium"]++
 | 
						|
		case 0 < score:
 | 
						|
			m["Low"]++
 | 
						|
		default:
 | 
						|
			m["Unknown"]++
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return m
 | 
						|
}
 | 
						|
 | 
						|
// FormatCveSummary summarize the number of CVEs group by CVSSv2 Severity
 | 
						|
func (v VulnInfos) FormatCveSummary() string {
 | 
						|
	m := v.CountGroupBySeverity()
 | 
						|
	line := fmt.Sprintf("Total: %d (Critical:%d High:%d Medium:%d Low:%d ?:%d)",
 | 
						|
		m["Critical"]+m["High"]+m["Medium"]+m["Low"]+m["Unknown"],
 | 
						|
		m["Critical"], m["High"], m["Medium"], m["Low"], m["Unknown"])
 | 
						|
 | 
						|
	nPlus, nMinus := v.CountDiff()
 | 
						|
	if 0 < nPlus || 0 < nMinus {
 | 
						|
		line = fmt.Sprintf("%s +%d -%d", line, nPlus, nMinus)
 | 
						|
	}
 | 
						|
	return line
 | 
						|
}
 | 
						|
 | 
						|
// FormatFixedStatus summarize the number of cves are fixed.
 | 
						|
func (v VulnInfos) FormatFixedStatus(packs Packages) string {
 | 
						|
	total, fixed := 0, 0
 | 
						|
	for _, vInfo := range v {
 | 
						|
		if len(vInfo.CpeURIs) != 0 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		total++
 | 
						|
		if vInfo.PatchStatus(packs) == "fixed" {
 | 
						|
			fixed++
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return fmt.Sprintf("%d/%d Fixed", fixed, total)
 | 
						|
}
 | 
						|
 | 
						|
// CountDiff counts the number of added/removed CVE-ID
 | 
						|
func (v VulnInfos) CountDiff() (nPlus int, nMinus int) {
 | 
						|
	for _, vInfo := range v {
 | 
						|
		if vInfo.DiffStatus == DiffPlus {
 | 
						|
			nPlus++
 | 
						|
		} else if vInfo.DiffStatus == DiffMinus {
 | 
						|
			nMinus++
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// PackageFixStatuses is a list of PackageStatus
 | 
						|
type PackageFixStatuses []PackageFixStatus
 | 
						|
 | 
						|
// Names return a slice of package names
 | 
						|
func (ps PackageFixStatuses) Names() (names []string) {
 | 
						|
	for _, p := range ps {
 | 
						|
		names = append(names, p.Name)
 | 
						|
	}
 | 
						|
	return names
 | 
						|
}
 | 
						|
 | 
						|
// Store insert given pkg if missing, update pkg if exists
 | 
						|
func (ps PackageFixStatuses) Store(pkg PackageFixStatus) PackageFixStatuses {
 | 
						|
	for i, p := range ps {
 | 
						|
		if p.Name == pkg.Name {
 | 
						|
			ps[i] = pkg
 | 
						|
			return ps
 | 
						|
		}
 | 
						|
	}
 | 
						|
	ps = append(ps, pkg)
 | 
						|
	return ps
 | 
						|
}
 | 
						|
 | 
						|
// Sort by Name
 | 
						|
func (ps PackageFixStatuses) Sort() {
 | 
						|
	sort.Slice(ps, func(i, j int) bool {
 | 
						|
		return ps[i].Name < ps[j].Name
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// PackageFixStatus has name and other status about the package
 | 
						|
type PackageFixStatus struct {
 | 
						|
	Name        string `json:"name,omitempty"`
 | 
						|
	NotFixedYet bool   `json:"notFixedYet,omitempty"`
 | 
						|
	FixState    string `json:"fixState,omitempty"`
 | 
						|
	FixedIn     string `json:"fixedIn,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
// VulnInfo has a vulnerability information and unsecure packages
 | 
						|
type VulnInfo struct {
 | 
						|
	CveID                string               `json:"cveID,omitempty"`
 | 
						|
	Confidences          Confidences          `json:"confidences,omitempty"`
 | 
						|
	AffectedPackages     PackageFixStatuses   `json:"affectedPackages,omitempty"`
 | 
						|
	DistroAdvisories     DistroAdvisories     `json:"distroAdvisories,omitempty"` // for Amazon, RHEL, Fedora, FreeBSD, Microsoft
 | 
						|
	CveContents          CveContents          `json:"cveContents,omitempty"`
 | 
						|
	Exploits             []Exploit            `json:"exploits,omitempty"`
 | 
						|
	Metasploits          []Metasploit         `json:"metasploits,omitempty"`
 | 
						|
	Mitigations          []Mitigation         `json:"mitigations,omitempty"`
 | 
						|
	Ctis                 []string             `json:"ctis,omitempty"`
 | 
						|
	AlertDict            AlertDict            `json:"alertDict,omitempty"`
 | 
						|
	CpeURIs              []string             `json:"cpeURIs,omitempty"` // CpeURIs related to this CVE defined in config.toml
 | 
						|
	GitHubSecurityAlerts GitHubSecurityAlerts `json:"gitHubSecurityAlerts,omitempty"`
 | 
						|
	WpPackageFixStats    WpPackageFixStats    `json:"wpPackageFixStats,omitempty"`
 | 
						|
	LibraryFixedIns      LibraryFixedIns      `json:"libraryFixedIns,omitempty"`
 | 
						|
	VulnType             string               `json:"vulnType,omitempty"`
 | 
						|
	DiffStatus           DiffStatus           `json:"diffStatus,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
// Alert has CERT alert information
 | 
						|
type Alert struct {
 | 
						|
	URL   string `json:"url,omitempty"`
 | 
						|
	Title string `json:"title,omitempty"`
 | 
						|
	Team  string `json:"team,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
// GitHubSecurityAlerts is a list of GitHubSecurityAlert
 | 
						|
type GitHubSecurityAlerts []GitHubSecurityAlert
 | 
						|
 | 
						|
// Add adds given arg to the slice and return the slice (immutable)
 | 
						|
func (g GitHubSecurityAlerts) Add(alert GitHubSecurityAlert) GitHubSecurityAlerts {
 | 
						|
	for _, a := range g {
 | 
						|
		if a.RepoURLPackageName() == alert.RepoURLPackageName() {
 | 
						|
			return g
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return append(g, alert)
 | 
						|
}
 | 
						|
 | 
						|
// Names return a slice of lib names
 | 
						|
func (g GitHubSecurityAlerts) Names() (names []string) {
 | 
						|
	for _, a := range g {
 | 
						|
		names = append(names, a.RepoURLPackageName())
 | 
						|
	}
 | 
						|
	return names
 | 
						|
}
 | 
						|
 | 
						|
// GitHubSecurityAlert has detected CVE-ID, GSAVulnerablePackage, Status fetched via GitHub API
 | 
						|
type GitHubSecurityAlert struct {
 | 
						|
	Repository    string               `json:"repository"`
 | 
						|
	Package       GSAVulnerablePackage `json:"package,omitempty"`
 | 
						|
	FixedIn       string               `json:"fixedIn"`
 | 
						|
	AffectedRange string               `json:"affectedRange"`
 | 
						|
	Dismissed     bool                 `json:"dismissed"`
 | 
						|
	DismissedAt   time.Time            `json:"dismissedAt"`
 | 
						|
	DismissReason string               `json:"dismissReason"`
 | 
						|
}
 | 
						|
 | 
						|
func (a GitHubSecurityAlert) RepoURLPackageName() string {
 | 
						|
	return fmt.Sprintf("%s %s", a.Repository, a.Package.Name)
 | 
						|
}
 | 
						|
 | 
						|
// RepoURLManifestPath should be same format with DependencyGraphManifest.RepoURLFilename()
 | 
						|
func (a GitHubSecurityAlert) RepoURLManifestPath() string {
 | 
						|
	return fmt.Sprintf("%s/%s", a.Repository, a.Package.ManifestPath)
 | 
						|
}
 | 
						|
 | 
						|
type GSAVulnerablePackage struct {
 | 
						|
	Name             string `json:"name"`
 | 
						|
	Ecosystem        string `json:"ecosystem"`
 | 
						|
	ManifestFilename string `json:"manifestFilename"`
 | 
						|
	ManifestPath     string `json:"manifestPath"`
 | 
						|
	Requirements     string `json:"requirements"`
 | 
						|
}
 | 
						|
 | 
						|
// LibraryFixedIns is a list of Library's FixedIn
 | 
						|
type LibraryFixedIns []LibraryFixedIn
 | 
						|
 | 
						|
// Names return a slice of names
 | 
						|
func (lfs LibraryFixedIns) Names() (names []string) {
 | 
						|
	for _, lf := range lfs {
 | 
						|
		names = append(names, lf.Name)
 | 
						|
	}
 | 
						|
	return names
 | 
						|
}
 | 
						|
 | 
						|
// WpPackageFixStats is a list of WpPackageFixStatus
 | 
						|
type WpPackageFixStats []WpPackageFixStatus
 | 
						|
 | 
						|
// Names return a slice of names
 | 
						|
func (ws WpPackageFixStats) Names() (names []string) {
 | 
						|
	for _, w := range ws {
 | 
						|
		names = append(names, w.Name)
 | 
						|
	}
 | 
						|
	return names
 | 
						|
}
 | 
						|
 | 
						|
// WpPackages has a list of WpPackage
 | 
						|
type WpPackages []WpPackage
 | 
						|
 | 
						|
// Add adds given arg to the slice and return the slice (immutable)
 | 
						|
func (g WpPackages) Add(pkg WpPackage) WpPackages {
 | 
						|
	for _, a := range g {
 | 
						|
		if a.Name == pkg.Name {
 | 
						|
			return g
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return append(g, pkg)
 | 
						|
}
 | 
						|
 | 
						|
// DiffStatus keeps a comparison with the previous detection results for this CVE
 | 
						|
type DiffStatus string
 | 
						|
 | 
						|
const (
 | 
						|
	// DiffPlus is newly detected CVE
 | 
						|
	DiffPlus = DiffStatus("+")
 | 
						|
 | 
						|
	// DiffMinus is resolved CVE
 | 
						|
	DiffMinus = DiffStatus("-")
 | 
						|
)
 | 
						|
 | 
						|
// CveIDDiffFormat format CVE-ID for diff mode
 | 
						|
func (v VulnInfo) CveIDDiffFormat() string {
 | 
						|
	if v.DiffStatus != "" {
 | 
						|
		return fmt.Sprintf("%s %s", v.DiffStatus, v.CveID)
 | 
						|
	}
 | 
						|
	return v.CveID
 | 
						|
}
 | 
						|
 | 
						|
// Titles returns title (TUI)
 | 
						|
func (v VulnInfo) Titles(lang, myFamily string) (values []CveContentStr) {
 | 
						|
	if lang == "ja" {
 | 
						|
		if conts, found := v.CveContents[Jvn]; found {
 | 
						|
			for _, cont := range conts {
 | 
						|
				if cont.Title != "" {
 | 
						|
					values = append(values, CveContentStr{Jvn, cont.Title})
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// RedHat API has one line title.
 | 
						|
	if conts, found := v.CveContents[RedHatAPI]; found {
 | 
						|
		for _, cont := range conts {
 | 
						|
			if cont.Title != "" {
 | 
						|
				values = append(values, CveContentStr{RedHatAPI, cont.Title})
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// GitHub security alerts has a title.
 | 
						|
	if conts, found := v.CveContents[GitHub]; found {
 | 
						|
		for _, cont := range conts {
 | 
						|
			if cont.Title != "" {
 | 
						|
				values = append(values, CveContentStr{GitHub, cont.Title})
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	order := append(CveContentTypes{Trivy, Nvd}, GetCveContentTypes(myFamily)...)
 | 
						|
	order = append(order, AllCveContetTypes.Except(append(order, Jvn)...)...)
 | 
						|
	for _, ctype := range order {
 | 
						|
		if conts, found := v.CveContents[ctype]; found {
 | 
						|
			for _, cont := range conts {
 | 
						|
				if cont.Summary != "" {
 | 
						|
					summary := strings.Replace(cont.Summary, "\n", " ", -1)
 | 
						|
					values = append(values, CveContentStr{
 | 
						|
						Type:  ctype,
 | 
						|
						Value: summary,
 | 
						|
					})
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for _, adv := range v.DistroAdvisories {
 | 
						|
		values = append(values, CveContentStr{
 | 
						|
			Type:  "Vendor",
 | 
						|
			Value: strings.Replace(adv.Description, "\n", " ", -1),
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	if len(values) == 0 {
 | 
						|
		values = []CveContentStr{{
 | 
						|
			Type:  Unknown,
 | 
						|
			Value: "-",
 | 
						|
		}}
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// Summaries returns summaries
 | 
						|
func (v VulnInfo) Summaries(lang, myFamily string) (values []CveContentStr) {
 | 
						|
	if lang == "ja" {
 | 
						|
		if conts, found := v.CveContents[Jvn]; found {
 | 
						|
			for _, cont := range conts {
 | 
						|
				if cont.Summary != "" {
 | 
						|
					summary := cont.Title
 | 
						|
					summary += "\n" + strings.Replace(
 | 
						|
						strings.Replace(cont.Summary, "\n", " ", -1), "\r", " ", -1)
 | 
						|
					values = append(values, CveContentStr{Jvn, summary})
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	order := append(append(CveContentTypes{Trivy}, GetCveContentTypes(myFamily)...), Nvd, GitHub)
 | 
						|
	order = append(order, AllCveContetTypes.Except(append(order, Jvn)...)...)
 | 
						|
	for _, ctype := range order {
 | 
						|
		if conts, found := v.CveContents[ctype]; found {
 | 
						|
			for _, cont := range conts {
 | 
						|
				if cont.Summary != "" {
 | 
						|
					summary := strings.Replace(cont.Summary, "\n", " ", -1)
 | 
						|
					values = append(values, CveContentStr{
 | 
						|
						Type:  ctype,
 | 
						|
						Value: summary,
 | 
						|
					})
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for _, adv := range v.DistroAdvisories {
 | 
						|
		values = append(values, CveContentStr{
 | 
						|
			Type:  "Vendor",
 | 
						|
			Value: adv.Description,
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	if conts, ok := v.CveContents[WpScan]; ok {
 | 
						|
		for _, cont := range conts {
 | 
						|
			if cont.Title != "" {
 | 
						|
				values = append(values, CveContentStr{
 | 
						|
					Type:  WpScan,
 | 
						|
					Value: cont.Title,
 | 
						|
				})
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if len(values) == 0 {
 | 
						|
		return []CveContentStr{{
 | 
						|
			Type:  Unknown,
 | 
						|
			Value: "-",
 | 
						|
		}}
 | 
						|
	}
 | 
						|
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// Cvss2Scores returns CVSS V2 Scores
 | 
						|
func (v VulnInfo) Cvss2Scores() (values []CveContentCvss) {
 | 
						|
	order := []CveContentType{RedHatAPI, RedHat, Nvd, Jvn}
 | 
						|
	for _, ctype := range order {
 | 
						|
		if conts, found := v.CveContents[ctype]; found {
 | 
						|
			for _, cont := range conts {
 | 
						|
				if cont.Cvss2Score == 0 && cont.Cvss2Severity == "" {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				// https://nvd.nist.gov/vuln-metrics/cvss
 | 
						|
				values = append(values, CveContentCvss{
 | 
						|
					Type: ctype,
 | 
						|
					Value: Cvss{
 | 
						|
						Type:     CVSS2,
 | 
						|
						Score:    cont.Cvss2Score,
 | 
						|
						Vector:   cont.Cvss2Vector,
 | 
						|
						Severity: strings.ToUpper(cont.Cvss2Severity),
 | 
						|
					},
 | 
						|
				})
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// Cvss3Scores returns CVSS V3 Score
 | 
						|
func (v VulnInfo) Cvss3Scores() (values []CveContentCvss) {
 | 
						|
	order := []CveContentType{RedHatAPI, RedHat, SUSE, Nvd, Jvn}
 | 
						|
	for _, ctype := range order {
 | 
						|
		if conts, found := v.CveContents[ctype]; found {
 | 
						|
			for _, cont := range conts {
 | 
						|
				if cont.Cvss3Score == 0 && cont.Cvss3Severity == "" {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				// https://nvd.nist.gov/vuln-metrics/cvss
 | 
						|
				values = append(values, CveContentCvss{
 | 
						|
					Type: ctype,
 | 
						|
					Value: Cvss{
 | 
						|
						Type:     CVSS3,
 | 
						|
						Score:    cont.Cvss3Score,
 | 
						|
						Vector:   cont.Cvss3Vector,
 | 
						|
						Severity: strings.ToUpper(cont.Cvss3Severity),
 | 
						|
					},
 | 
						|
				})
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for _, ctype := range []CveContentType{Debian, DebianSecurityTracker, Ubuntu, UbuntuAPI, Amazon, Trivy, GitHub, WpScan} {
 | 
						|
		if conts, found := v.CveContents[ctype]; found {
 | 
						|
			for _, cont := range conts {
 | 
						|
				if cont.Cvss3Severity != "" {
 | 
						|
					values = append(values, CveContentCvss{
 | 
						|
						Type: ctype,
 | 
						|
						Value: Cvss{
 | 
						|
							Type:                 CVSS3,
 | 
						|
							Score:                severityToCvssScoreRoughly(cont.Cvss3Severity),
 | 
						|
							CalculatedBySeverity: true,
 | 
						|
							Severity:             strings.ToUpper(cont.Cvss3Severity),
 | 
						|
						},
 | 
						|
					})
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Memo: Only RedHat, SUSE, Oracle and Amazon has severity data in advisory.
 | 
						|
	for _, adv := range v.DistroAdvisories {
 | 
						|
		if adv.Severity != "" {
 | 
						|
			score := severityToCvssScoreRoughly(adv.Severity)
 | 
						|
			values = append(values, CveContentCvss{
 | 
						|
				Type: "Vendor",
 | 
						|
				Value: Cvss{
 | 
						|
					Type:                 CVSS3,
 | 
						|
					Score:                score,
 | 
						|
					CalculatedBySeverity: true,
 | 
						|
					Severity:             strings.ToUpper(adv.Severity),
 | 
						|
				},
 | 
						|
			})
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// MaxCvssScore returns max CVSS Score
 | 
						|
// If there is no CVSS Score, return Severity as a numerical value.
 | 
						|
func (v VulnInfo) MaxCvssScore() CveContentCvss {
 | 
						|
	v3Max := v.MaxCvss3Score()
 | 
						|
	if v3Max.Type != Unknown {
 | 
						|
		return v3Max
 | 
						|
	}
 | 
						|
	return v.MaxCvss2Score()
 | 
						|
}
 | 
						|
 | 
						|
// MaxCvss3Score returns Max CVSS V3 Score
 | 
						|
func (v VulnInfo) MaxCvss3Score() CveContentCvss {
 | 
						|
	max := CveContentCvss{
 | 
						|
		Type:  Unknown,
 | 
						|
		Value: Cvss{Type: CVSS3},
 | 
						|
	}
 | 
						|
	for _, cvss := range v.Cvss3Scores() {
 | 
						|
		if max.Value.Score < cvss.Value.Score {
 | 
						|
			max = cvss
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return max
 | 
						|
}
 | 
						|
 | 
						|
// MaxCvss2Score returns Max CVSS V2 Score
 | 
						|
func (v VulnInfo) MaxCvss2Score() CveContentCvss {
 | 
						|
	max := CveContentCvss{
 | 
						|
		Type:  Unknown,
 | 
						|
		Value: Cvss{Type: CVSS2},
 | 
						|
	}
 | 
						|
	for _, cvss := range v.Cvss2Scores() {
 | 
						|
		if max.Value.Score < cvss.Value.Score {
 | 
						|
			max = cvss
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return max
 | 
						|
}
 | 
						|
 | 
						|
// AttackVector returns attack vector string
 | 
						|
func (v VulnInfo) AttackVector() string {
 | 
						|
	for _, conts := range v.CveContents {
 | 
						|
		for _, cont := range conts {
 | 
						|
			if strings.HasPrefix(cont.Cvss2Vector, "AV:N") ||
 | 
						|
				strings.Contains(cont.Cvss3Vector, "AV:N") {
 | 
						|
				return "AV:N"
 | 
						|
			} else if strings.HasPrefix(cont.Cvss2Vector, "AV:A") ||
 | 
						|
				strings.Contains(cont.Cvss3Vector, "AV:A") {
 | 
						|
				return "AV:A"
 | 
						|
			} else if strings.HasPrefix(cont.Cvss2Vector, "AV:L") ||
 | 
						|
				strings.Contains(cont.Cvss3Vector, "AV:L") {
 | 
						|
				return "AV:L"
 | 
						|
			} else if strings.Contains(cont.Cvss3Vector, "AV:P") {
 | 
						|
				// no AV:P in CVSS v2
 | 
						|
				return "AV:P"
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if conts, found := v.CveContents[DebianSecurityTracker]; found {
 | 
						|
		for _, cont := range conts {
 | 
						|
			if attackRange, found := cont.Optional["attack range"]; found {
 | 
						|
				return attackRange
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return ""
 | 
						|
}
 | 
						|
 | 
						|
// PatchStatus returns fixed or unfixed string
 | 
						|
func (v VulnInfo) PatchStatus(packs Packages) string {
 | 
						|
	// Vuls don't know patch status of the CPE
 | 
						|
	if len(v.CpeURIs) != 0 {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
	for _, p := range v.AffectedPackages {
 | 
						|
		if p.NotFixedYet {
 | 
						|
			return "unfixed"
 | 
						|
		}
 | 
						|
 | 
						|
		// Fast and offline mode can not get the candidate version.
 | 
						|
		// Vuls can be considered as 'fixed' if not-fixed-yet==true and
 | 
						|
		// the fixed-in-version (information in the oval) is not an empty.
 | 
						|
		if p.FixedIn != "" {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// fast, offline mode doesn't have new version
 | 
						|
		if pack, ok := packs[p.Name]; ok {
 | 
						|
			if pack.NewVersion == "" {
 | 
						|
				return "unknown"
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return "fixed"
 | 
						|
}
 | 
						|
 | 
						|
// CveContentCvss has CVSS information
 | 
						|
type CveContentCvss struct {
 | 
						|
	Type  CveContentType `json:"type"`
 | 
						|
	Value Cvss           `json:"value"`
 | 
						|
}
 | 
						|
 | 
						|
// CvssType Represent the type of CVSS
 | 
						|
type CvssType string
 | 
						|
 | 
						|
const (
 | 
						|
	// CVSS2 means CVSS version2
 | 
						|
	CVSS2 CvssType = "2"
 | 
						|
 | 
						|
	// CVSS3 means CVSS version3
 | 
						|
	CVSS3 CvssType = "3"
 | 
						|
)
 | 
						|
 | 
						|
// Cvss has CVSS Score
 | 
						|
type Cvss struct {
 | 
						|
	Type                 CvssType `json:"type"`
 | 
						|
	Score                float64  `json:"score"`
 | 
						|
	CalculatedBySeverity bool     `json:"calculatedBySeverity"`
 | 
						|
	Vector               string   `json:"vector"`
 | 
						|
	Severity             string   `json:"severity"`
 | 
						|
}
 | 
						|
 | 
						|
// Format CVSS Score and Vector
 | 
						|
func (c Cvss) Format() string {
 | 
						|
	if c.Vector == "" {
 | 
						|
		return fmt.Sprintf("%s %s", c.SeverityToCvssScoreRange(), c.Severity)
 | 
						|
	}
 | 
						|
	return fmt.Sprintf("%3.1f/%s %s", c.Score, c.Vector, c.Severity)
 | 
						|
}
 | 
						|
 | 
						|
// SeverityToCvssScoreRange returns CVSS score range
 | 
						|
func (c Cvss) SeverityToCvssScoreRange() string {
 | 
						|
	return severityToCvssScoreRange(c.Severity)
 | 
						|
}
 | 
						|
 | 
						|
func severityToCvssScoreRange(severity string) string {
 | 
						|
	switch strings.ToUpper(severity) {
 | 
						|
	case "CRITICAL":
 | 
						|
		return "9.0-10.0"
 | 
						|
	case "IMPORTANT", "HIGH":
 | 
						|
		return "7.0-8.9"
 | 
						|
	case "MODERATE", "MEDIUM":
 | 
						|
		return "4.0-6.9"
 | 
						|
	case "LOW", "NEGLIGIBLE":
 | 
						|
		return "0.1-3.9"
 | 
						|
	}
 | 
						|
	return "None"
 | 
						|
}
 | 
						|
 | 
						|
// Amazon Linux Security Advisory
 | 
						|
// Critical, Important, Medium, Low
 | 
						|
// https://alas.aws.amazon.com/
 | 
						|
//
 | 
						|
// RedHat, Oracle OVAL
 | 
						|
// Critical, Important, Moderate, Low
 | 
						|
// https://access.redhat.com/security/updates/classification
 | 
						|
//
 | 
						|
// Ubuntu OVAL
 | 
						|
// Critical, High, Medium, Low
 | 
						|
// https://wiki.ubuntu.com/Bugs/Importance
 | 
						|
// https://people.canonical.com/~ubuntu-security/cve/priority.html
 | 
						|
//
 | 
						|
// Ubuntu CVE Tracker
 | 
						|
// Critical, High, Medium, Low, Negligible
 | 
						|
// https://people.canonical.com/~ubuntu-security/priority.html
 | 
						|
func severityToCvssScoreRoughly(severity string) float64 {
 | 
						|
	switch strings.ToUpper(severity) {
 | 
						|
	case "CRITICAL":
 | 
						|
		return 10.0
 | 
						|
	case "IMPORTANT", "HIGH":
 | 
						|
		return 8.9
 | 
						|
	case "MODERATE", "MEDIUM":
 | 
						|
		return 6.9
 | 
						|
	case "LOW", "NEGLIGIBLE":
 | 
						|
		return 3.9
 | 
						|
	}
 | 
						|
	return 0
 | 
						|
}
 | 
						|
 | 
						|
// FormatMaxCvssScore returns Max CVSS Score
 | 
						|
func (v VulnInfo) FormatMaxCvssScore() string {
 | 
						|
	max := v.MaxCvssScore()
 | 
						|
	return fmt.Sprintf("%3.1f %s (%s)",
 | 
						|
		max.Value.Score,
 | 
						|
		strings.ToUpper(max.Value.Severity),
 | 
						|
		max.Type)
 | 
						|
}
 | 
						|
 | 
						|
// DistroAdvisories is a list of DistroAdvisory
 | 
						|
type DistroAdvisories []DistroAdvisory
 | 
						|
 | 
						|
// AppendIfMissing appends if missing
 | 
						|
func (advs *DistroAdvisories) AppendIfMissing(adv *DistroAdvisory) bool {
 | 
						|
	for _, a := range *advs {
 | 
						|
		if a.AdvisoryID == adv.AdvisoryID {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	}
 | 
						|
	*advs = append(*advs, *adv)
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
// DistroAdvisory has Amazon Linux, RHEL, FreeBSD Security Advisory information.
 | 
						|
type DistroAdvisory struct {
 | 
						|
	AdvisoryID  string    `json:"advisoryID"`
 | 
						|
	Severity    string    `json:"severity"`
 | 
						|
	Issued      time.Time `json:"issued"`
 | 
						|
	Updated     time.Time `json:"updated"`
 | 
						|
	Description string    `json:"description"`
 | 
						|
}
 | 
						|
 | 
						|
// Format the distro advisory information
 | 
						|
func (p DistroAdvisory) Format() string {
 | 
						|
	if p.AdvisoryID == "" {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
 | 
						|
	var delim bytes.Buffer
 | 
						|
	for i := 0; i < len(p.AdvisoryID); i++ {
 | 
						|
		delim.WriteString("-")
 | 
						|
	}
 | 
						|
	buf := []string{p.AdvisoryID, delim.String(), p.Description}
 | 
						|
	return strings.Join(buf, "\n")
 | 
						|
}
 | 
						|
 | 
						|
// Exploit :
 | 
						|
type Exploit struct {
 | 
						|
	ExploitType  exploitmodels.ExploitType `json:"exploitType"`
 | 
						|
	ID           string                    `json:"id"`
 | 
						|
	URL          string                    `json:"url"`
 | 
						|
	Description  string                    `json:"description"`
 | 
						|
	DocumentURL  *string                   `json:"documentURL,omitempty"`
 | 
						|
	ShellCodeURL *string                   `json:"shellCodeURL,omitempty"`
 | 
						|
	BinaryURL    *string                   `json:"binaryURL,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
// Metasploit :
 | 
						|
type Metasploit struct {
 | 
						|
	Name        string   `json:"name"`
 | 
						|
	Title       string   `json:"title"`
 | 
						|
	Description string   `json:"description,omitempty"`
 | 
						|
	URLs        []string `json:",omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
// Mitigation has a link and content
 | 
						|
type Mitigation struct {
 | 
						|
	CveContentType CveContentType `json:"cveContentType,omitempty"`
 | 
						|
	Mitigation     string         `json:"mitigation,omitempty"`
 | 
						|
	URL            string         `json:"url,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
// AlertDict has target cve JPCERT, USCERT and CISA alert data
 | 
						|
type AlertDict struct {
 | 
						|
	CISA   []Alert `json:"cisa"`
 | 
						|
	JPCERT []Alert `json:"jpcert"`
 | 
						|
	USCERT []Alert `json:"uscert"`
 | 
						|
}
 | 
						|
 | 
						|
// IsEmpty checks if the content of AlertDict is empty
 | 
						|
func (a AlertDict) IsEmpty() bool {
 | 
						|
	return len(a.CISA) == 0 && len(a.JPCERT) == 0 && len(a.USCERT) == 0
 | 
						|
}
 | 
						|
 | 
						|
// FormatSource returns which source has this alert
 | 
						|
func (a AlertDict) FormatSource() string {
 | 
						|
	var s []string
 | 
						|
	if len(a.CISA) != 0 {
 | 
						|
		s = append(s, "CISA")
 | 
						|
	}
 | 
						|
	if len(a.USCERT) != 0 || len(a.JPCERT) != 0 {
 | 
						|
		s = append(s, "CERT")
 | 
						|
	}
 | 
						|
	return strings.Join(s, "/")
 | 
						|
}
 | 
						|
 | 
						|
// Confidences is a list of Confidence
 | 
						|
type Confidences []Confidence
 | 
						|
 | 
						|
// AppendIfMissing appends confidence to the list if missing
 | 
						|
func (cs *Confidences) AppendIfMissing(confidence Confidence) {
 | 
						|
	for _, c := range *cs {
 | 
						|
		if c.DetectionMethod == confidence.DetectionMethod {
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
	*cs = append(*cs, confidence)
 | 
						|
}
 | 
						|
 | 
						|
// SortByConfident sorts Confidences
 | 
						|
func (cs Confidences) SortByConfident() Confidences {
 | 
						|
	sort.Slice(cs, func(i, j int) bool {
 | 
						|
		return cs[i].SortOrder < cs[j].SortOrder
 | 
						|
	})
 | 
						|
	return cs
 | 
						|
}
 | 
						|
 | 
						|
// Confidence is a ranking how confident the CVE-ID was detected correctly
 | 
						|
// Score: 0 - 100
 | 
						|
type Confidence struct {
 | 
						|
	Score           int             `json:"score"`
 | 
						|
	DetectionMethod DetectionMethod `json:"detectionMethod"`
 | 
						|
	SortOrder       int             `json:"-"`
 | 
						|
}
 | 
						|
 | 
						|
func (c Confidence) String() string {
 | 
						|
	return fmt.Sprintf("%d / %s", c.Score, c.DetectionMethod)
 | 
						|
}
 | 
						|
 | 
						|
// DetectionMethod indicates
 | 
						|
// - How to detect the CveID
 | 
						|
// - How to get the changelog difference between installed and candidate version
 | 
						|
type DetectionMethod string
 | 
						|
 | 
						|
const (
 | 
						|
	// NvdExactVersionMatchStr :
 | 
						|
	NvdExactVersionMatchStr = "NvdExactVersionMatch"
 | 
						|
 | 
						|
	// NvdRoughVersionMatchStr :
 | 
						|
	NvdRoughVersionMatchStr = "NvdRoughVersionMatch"
 | 
						|
 | 
						|
	// NvdVendorProductMatchStr :
 | 
						|
	NvdVendorProductMatchStr = "NvdVendorProductMatch"
 | 
						|
 | 
						|
	// JvnVendorProductMatchStr :
 | 
						|
	JvnVendorProductMatchStr = "JvnVendorProductMatch"
 | 
						|
 | 
						|
	// PkgAuditMatchStr :
 | 
						|
	PkgAuditMatchStr = "PkgAuditMatch"
 | 
						|
 | 
						|
	// OvalMatchStr :
 | 
						|
	OvalMatchStr = "OvalMatch"
 | 
						|
 | 
						|
	// RedHatAPIStr is :
 | 
						|
	RedHatAPIStr = "RedHatAPIMatch"
 | 
						|
 | 
						|
	// DebianSecurityTrackerMatchStr :
 | 
						|
	DebianSecurityTrackerMatchStr = "DebianSecurityTrackerMatch"
 | 
						|
 | 
						|
	// UbuntuAPIMatchStr :
 | 
						|
	UbuntuAPIMatchStr = "UbuntuAPIMatch"
 | 
						|
 | 
						|
	// WindowsUpdateSearchStr :
 | 
						|
	WindowsUpdateSearchStr = "WindowsUpdateSearch"
 | 
						|
 | 
						|
	// TrivyMatchStr :
 | 
						|
	TrivyMatchStr = "TrivyMatch"
 | 
						|
 | 
						|
	// ChangelogExactMatchStr :
 | 
						|
	ChangelogExactMatchStr = "ChangelogExactMatch"
 | 
						|
 | 
						|
	// ChangelogRoughMatchStr :
 | 
						|
	ChangelogRoughMatchStr = "ChangelogRoughMatch"
 | 
						|
 | 
						|
	// GitHubMatchStr :
 | 
						|
	GitHubMatchStr = "GitHubMatch"
 | 
						|
 | 
						|
	// WpScanMatchStr :
 | 
						|
	WpScanMatchStr = "WpScanMatch"
 | 
						|
 | 
						|
	// FailedToGetChangelog :
 | 
						|
	FailedToGetChangelog = "FailedToGetChangelog"
 | 
						|
 | 
						|
	// FailedToFindVersionInChangelog :
 | 
						|
	FailedToFindVersionInChangelog = "FailedToFindVersionInChangelog"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	// PkgAuditMatch is a ranking how confident the CVE-ID was detected correctly
 | 
						|
	PkgAuditMatch = Confidence{100, PkgAuditMatchStr, 2}
 | 
						|
 | 
						|
	// OvalMatch is a ranking how confident the CVE-ID was detected correctly
 | 
						|
	OvalMatch = Confidence{100, OvalMatchStr, 0}
 | 
						|
 | 
						|
	// RedHatAPIMatch ranking how confident the CVE-ID was detected correctly
 | 
						|
	RedHatAPIMatch = Confidence{100, RedHatAPIStr, 0}
 | 
						|
 | 
						|
	// DebianSecurityTrackerMatch ranking how confident the CVE-ID was detected correctly
 | 
						|
	DebianSecurityTrackerMatch = Confidence{100, DebianSecurityTrackerMatchStr, 0}
 | 
						|
 | 
						|
	// UbuntuAPIMatch ranking how confident the CVE-ID was detected correctly
 | 
						|
	UbuntuAPIMatch = Confidence{100, UbuntuAPIMatchStr, 0}
 | 
						|
 | 
						|
	// WindowsUpdateSearch ranking how confident the CVE-ID was detected correctly
 | 
						|
	WindowsUpdateSearch = Confidence{100, WindowsUpdateSearchStr, 0}
 | 
						|
 | 
						|
	// TrivyMatch ranking how confident the CVE-ID was detected correctly
 | 
						|
	TrivyMatch = Confidence{100, TrivyMatchStr, 0}
 | 
						|
 | 
						|
	// ChangelogExactMatch is a ranking how confident the CVE-ID was detected correctly
 | 
						|
	ChangelogExactMatch = Confidence{95, ChangelogExactMatchStr, 3}
 | 
						|
 | 
						|
	// ChangelogRoughMatch is a ranking how confident the CVE-ID was detected correctly
 | 
						|
	ChangelogRoughMatch = Confidence{50, ChangelogRoughMatchStr, 4}
 | 
						|
 | 
						|
	// GitHubMatch is a ranking how confident the CVE-ID was detected correctly
 | 
						|
	GitHubMatch = Confidence{100, GitHubMatchStr, 2}
 | 
						|
 | 
						|
	// WpScanMatch is a ranking how confident the CVE-ID was detected correctly
 | 
						|
	WpScanMatch = Confidence{100, WpScanMatchStr, 0}
 | 
						|
 | 
						|
	// NvdExactVersionMatch is a ranking how confident the CVE-ID was detected correctly
 | 
						|
	NvdExactVersionMatch = Confidence{100, NvdExactVersionMatchStr, 1}
 | 
						|
 | 
						|
	// NvdRoughVersionMatch NvdExactVersionMatch is a ranking how confident the CVE-ID was detected correctly
 | 
						|
	NvdRoughVersionMatch = Confidence{80, NvdRoughVersionMatchStr, 1}
 | 
						|
 | 
						|
	// NvdVendorProductMatch is a ranking how confident the CVE-ID was detected correctly
 | 
						|
	NvdVendorProductMatch = Confidence{10, NvdVendorProductMatchStr, 9}
 | 
						|
 | 
						|
	// JvnVendorProductMatch is a ranking how confident the CVE-ID was detected correctly
 | 
						|
	JvnVendorProductMatch = Confidence{10, JvnVendorProductMatchStr, 10}
 | 
						|
)
 |