Files
vuls/models/vulninfos.go
MaineK00n d8173cdd42 feat(cve/mitre): support go-cve-dictionary:mitre (#1978)
* feat(cve/mitre): support go-cve-dictionary:mitre

* chore: adopt reviewer comment

* refactor(models): refactor CveContents method
2024-06-29 16:35:06 +09:00

1092 lines
30 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 || 0 < vv.MaxCvss40Score().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.MaxCvss40Score().Value.Score
if score < 0.1 {
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 asc, FixedIn desc
func (ps PackageFixStatuses) Sort() {
sort.Slice(ps, func(i, j int) bool {
if ps[i].Name != ps[j].Name {
return ps[i].Name < ps[j].Name
}
return ps[j].FixedIn < ps[i].FixedIn
})
}
// 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"`
WindowsKBFixedIns []string `json:"windowsKBFixedIns,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"`
}
// RepoURLPackageName returns a string connecting the repository and package name
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)
}
// GSAVulnerablePackage has vulnerable package information
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(GetCveContentTypes(string(Trivy)), append(CveContentTypes{Fortinet, Nvd, Mitre}, 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(GetCveContentTypes(string(Trivy)), GetCveContentTypes(myFamily)...), Fortinet, Nvd, Mitre, 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 := append([]CveContentType{RedHatAPI, RedHat, Nvd, Mitre, Jvn}, GetCveContentTypes(string(Trivy))...)
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 := append([]CveContentType{RedHatAPI, RedHat, SUSE, Microsoft, Fortinet, Nvd, Mitre, Jvn}, GetCveContentTypes(string(Trivy))...)
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 append([]CveContentType{Debian, DebianSecurityTracker, Ubuntu, UbuntuAPI, Amazon, GitHub, WpScan}, GetCveContentTypes(string(Trivy))...) {
if conts, found := v.CveContents[ctype]; found {
for _, cont := range conts {
if cont.Cvss3Severity != "" {
switch ctype {
case DebianSecurityTracker: // Multiple Severities(sorted) may be listed, and the largest one is used.
ss := strings.Split(cont.Cvss3Severity, "|")
values = append(values, CveContentCvss{
Type: ctype,
Value: Cvss{
Type: CVSS3,
Score: severityToCvssScoreRoughly(ss[len(ss)-1]),
CalculatedBySeverity: true,
Severity: strings.ToUpper(cont.Cvss3Severity),
},
})
default:
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
}
// Cvss40Scores returns CVSS V4 Score
func (v VulnInfo) Cvss40Scores() (values []CveContentCvss) {
for _, ctype := range []CveContentType{Mitre} {
if conts, found := v.CveContents[ctype]; found {
for _, cont := range conts {
if cont.Cvss40Score == 0 && cont.Cvss40Severity == "" {
continue
}
// https://nvd.nist.gov/vuln-metrics/cvss
values = append(values, CveContentCvss{
Type: ctype,
Value: Cvss{
Type: CVSS40,
Score: cont.Cvss40Score,
Vector: cont.Cvss40Vector,
Severity: strings.ToUpper(cont.Cvss40Severity),
},
})
}
}
}
return
}
// MaxCvssScore returns max CVSS Score
// If there is no CVSS Score, return Severity as a numerical value.
func (v VulnInfo) MaxCvssScore() CveContentCvss {
v40Max := v.MaxCvss40Score()
if v40Max.Type != Unknown {
return v40Max
}
v3Max := v.MaxCvss3Score()
if v3Max.Type != Unknown {
return v3Max
}
return v.MaxCvss2Score()
}
// MaxCvss40Score returns Max CVSS V4.0 Score
func (v VulnInfo) MaxCvss40Score() CveContentCvss {
max := CveContentCvss{
Type: Unknown,
Value: Cvss{Type: CVSS40},
}
for _, cvss := range v.Cvss40Scores() {
if max.Value.Score < cvss.Value.Score {
max = cvss
}
}
return max
}
// 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 {
switch {
case strings.HasPrefix(cont.Cvss2Vector, "AV:N") || strings.Contains(cont.Cvss3Vector, "AV:N") || strings.Contains(cont.Cvss40Vector, "AV:N"):
return "AV:N"
case strings.HasPrefix(cont.Cvss2Vector, "AV:A") || strings.Contains(cont.Cvss3Vector, "AV:A") || strings.Contains(cont.Cvss40Vector, "AV:A"):
return "AV:A"
case strings.HasPrefix(cont.Cvss2Vector, "AV:L") || strings.Contains(cont.Cvss3Vector, "AV:L") || strings.Contains(cont.Cvss40Vector, "AV:L"):
return "AV:L"
case strings.Contains(cont.Cvss3Vector, "AV:P") || strings.Contains(cont.Cvss40Vector, "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"
}
}
}
for _, c := range v.Confidences {
if c == WindowsUpdateSearch && len(v.WindowsKBFixedIns) == 0 {
return "unfixed"
}
}
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"
// CVSS40 means CVSS version4.0
CVSS40 CvssType = "4.0"
)
// 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"`
PaperURL *string `json:"paperURL,omitempty"`
GHDBURL *string `json:"ghdbURL,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"
// FortinetExactVersionMatchStr :
FortinetExactVersionMatchStr = "FortinetExactVersionMatch"
// FortinetRoughVersionMatchStr :
FortinetRoughVersionMatchStr = "FortinetRoughVersionMatch"
// FortinetVendorProductMatchStr :
FortinetVendorProductMatchStr = "FortinetVendorProductMatch"
// 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}
// FortinetExactVersionMatch is a ranking how confident the CVE-ID was detected correctly
FortinetExactVersionMatch = Confidence{100, FortinetExactVersionMatchStr, 1}
// FortinetRoughVersionMatch FortinetExactVersionMatch is a ranking how confident the CVE-ID was detected correctly
FortinetRoughVersionMatch = Confidence{80, FortinetRoughVersionMatchStr, 1}
// FortinetVendorProductMatch is a ranking how confident the CVE-ID was detected correctly
FortinetVendorProductMatch = Confidence{10, FortinetVendorProductMatchStr, 9}
)