Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions detector/detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,8 +397,8 @@ func DetectWordPressCves(r *models.ScanResult, wpCnf config.WpScanConf) error {
return nil
}

// FillCvesWithGoCVEDictionary fills CVE detail with VulnCheck, JVN, Fortinet, MITRE, Paloalto, Cisco
// (NVD CveContent/exploits/mitigations and EUVD are filled by the vuls2 enrich path instead; NVD cert alerts still come from here)
// FillCvesWithGoCVEDictionary fills CVE detail with VulnCheck, JVN, Fortinet, Paloalto, Cisco
// (NVD CveContent/exploits/mitigations, EUVD, and MITRE are filled by the vuls2 enrich path instead; JP-CERT alerts still come from here)
func FillCvesWithGoCVEDictionary(r *models.ScanResult, cnf config.GoCveDictConf, logOpts logging.LogOpts) (err error) {
cveIDs := make([]string, 0, len(r.ScannedCves))
for _, v := range r.ScannedCves {
Expand All @@ -424,7 +424,6 @@ func FillCvesWithGoCVEDictionary(r *models.ScanResult, cnf config.GoCveDictConf,
vulnchecks := models.ConvertVulncheckToModel(d.CveID, d.Vulnchecks)
jvns := models.ConvertJvnToModel(d.CveID, d.Jvns)
fortinets := models.ConvertFortinetToModel(d.CveID, d.Fortinets)
mitres := models.ConvertMitreToModel(d.CveID, d.Mitres)
paloaltos := models.ConvertPaloaltoToModel(d.CveID, d.Paloaltos)
ciscos := models.ConvertCiscoToModel(d.CveID, d.Ciscos)

Expand Down Expand Up @@ -453,9 +452,6 @@ func FillCvesWithGoCVEDictionary(r *models.ScanResult, cnf config.GoCveDictConf,
}
}
}
for _, con := range mitres {
vinfo.CveContents[con.Type] = append(vinfo.CveContents[con.Type], con)
}
// Set only JP-CERT; US-CERT is filled by the vuls2 enrich path
// (vuls2.EnrichVulnInfos runs before this) and must be preserved.
vinfo.AlertDict.JPCERT = alerts.JPCERT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,35 @@
"url": "https://www.cve.org/CVERecord?id=CVE-2023-44487"
}
],
"published": "2023-10-10T00:00:00Z"
"ssvc": [
{
"source": "CISA-ADP",
"role": "CISA Coordinator",
"version": "2.0.3",
"options": [
{
"key": "Exploitation",
"value": "active"
},
{
"key": "Automatable",
"value": "yes"
},
{
"key": "Technical Impact",
"value": "total"
}
],
"timestamp": "2023-10-10T00:00:00Z"
}
],
"published": "2023-10-10T00:00:00Z",
"optional": {
"container_types": {
"cve@mitre.org": "CNA",
"CISA-ADP": "ADP"
}
}
}
}
],
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"id": "CVE-2020-0001",
"vulnerabilities": [
{
"content": {
"id": "CVE-2020-0001",
"title": "from nvd-api-cve",
"description": "This data source is not in the enrich DataSources filter, so it should never reach enrichVulnerabilities."
}
}
],
"data_source": {
"id": "nvd-api-cve",
"raws": [
"fixtures/CVE-2020-0001.json"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"id": "nvd-api-cve",
"name": "NVD CVE API"
}
147 changes: 147 additions & 0 deletions detector/vuls2/vendor.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
v2 "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/severity/cvss/v2"
v31 "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/severity/cvss/v31"
v40 "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/severity/cvss/v40"
ssvcTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/ssvc"
vulnerabilityTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/vulnerability"
sourceTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/source"
dbTypes "github.com/MaineK00n/vuls2/pkg/db/session/types"
Expand Down Expand Up @@ -649,6 +650,8 @@ func cveContentSourceLink(ccType models.CveContentType, v vulnerabilityTypes.Vul
return fmt.Sprintf("https://security.alpinelinux.org/vuln/%s", v.Content.ID)
case models.Nvd:
return fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", v.Content.ID)
case models.Mitre:
return fmt.Sprintf("https://www.cve.org/CVERecord?id=%s", v.Content.ID)
case models.Microsoft:
return fmt.Sprintf("https://msrc.microsoft.com/update-guide/vulnerability/%s", v.Content.ID)
default:
Expand Down Expand Up @@ -1168,6 +1171,8 @@ func enrichVulnerabilities(vi *models.VulnInfo, vulns []dbTypes.VulnerabilityDat
enrichRedHatCVE(vi, rootMap)
case sourceTypes.NVDFeedCVEv2:
enrichNVD(vi, rootMap)
case sourceTypes.MitreCVEV5:
enrichMitreCVE(vi, rootMap)
case sourceTypes.Metasploit:
enrichMetasploit(vi, rootMap)
case sourceTypes.ExploitExploitDB, sourceTypes.ExploitGitHub, sourceTypes.ExploitInTheWild, sourceTypes.ExploitTrickest, sourceTypes.NucleiRepository:
Expand Down Expand Up @@ -1435,6 +1440,148 @@ func enrichNVD(vi *models.VulnInfo, rootMap map[dataTypes.RootID][]vulnerability
}
}

// enrichMitreCVE adds MITRE CVE v5 data as CveContent, one entry per CNA/ADP
// source so each source's CVSS, CWE, references, and SSVC decision point are
// reported separately (rendered as mitre(<source>) by CveContents.SSVC and the
// reporter). The source is the CNA/ADP provider shortName (e.g. "CISA-ADP").
func enrichMitreCVE(vi *models.VulnInfo, rootMap map[dataTypes.RootID][]vulnerabilityTypes.Vulnerability) {
if _, ok := vi.CveContents[models.Mitre]; ok {
return
}

// mitreBySource accumulates the per-source MITRE fields so each CNA/ADP
// source becomes its own CveContent.
type mitreBySource struct {
severities []severityTypes.Severity
references models.References
cweIDs []string
ssvc *models.SSVC
}

for _, vulns := range rootMap {
for _, v := range vulns {
// Group every per-source field by its CNA/ADP source in a single pass.
bySource := map[string]*mitreBySource{}
get := func(source string) *mitreBySource {
b, ok := bySource[source]
if !ok {
b = &mitreBySource{}
bySource[source] = b
}
return b
}
for _, s := range v.Content.Severity {
b := get(s.Source)
b.severities = append(b.severities, s)
}
for _, r := range v.Content.References {
b := get(r.Source)
b.references = append(b.references, toReference(r.URL))
}
for _, c := range v.Content.CWE {
b := get(c.Source)
b.cweIDs = append(b.cweIDs, c.CWE...)
}
for _, s := range v.Content.SSVC {
if b := get(s.Source); b.ssvc == nil {
b.ssvc = mitreSSVC(s)
}
}

// container_types maps each source label to its container role
// ("CNA"/"ADP"), set by the extractor from structural position.
containerTypes := mitreContainerTypes(v.Content.Optional)

sourceLink := cveContentSourceLink(models.Mitre, v)
published := func() time.Time {
if v.Content.Published != nil {
return *v.Content.Published
}
return time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC)
}()
lastModified := func() time.Time {
if v.Content.Modified != nil {
return *v.Content.Modified
}
return time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC)
}()

// Order is normalised downstream by CveContents.Sort (see
// ScanResult.SortForJSONOutput), so emit in map order here.
for source, b := range bySource {
cvss2, cvss3, cvss40 := enrichCvss(b.severities)

cc := models.CveContent{
Type: models.Mitre,
CveID: string(v.Content.ID),
Title: v.Content.Title,
Summary: v.Content.Description,
Cvss2Score: cvss2.BaseScore,
Cvss2Vector: cvss2.Vector,
Cvss2Severity: cvss2.NVDBaseSeverity,
Cvss3Score: cvss3.BaseScore,
Cvss3Vector: cvss3.Vector,
Cvss3Severity: cvss3.BaseSeverity,
Cvss40Score: cvss40.Score,
Cvss40Vector: cvss40.Vector,
Cvss40Severity: cvss40.Severity,
SourceLink: sourceLink,
References: b.references,
CweIDs: b.cweIDs,
Published: published,
LastModified: lastModified,
SSVC: b.ssvc,
}
if source != "" {
// Qualify the source with its CNA/ADP role when known
// (e.g. "ADP:CISA-ADP"), so reports distinguish the two.
label := source
if ct := containerTypes[source]; ct != "" {
label = fmt.Sprintf("%s:%s", ct, source)
}
cc.Optional = map[string]string{"source": label}
}
vi.CveContents[models.Mitre] = append(vi.CveContents[models.Mitre], cc)
}
}
}
}

// mitreContainerTypes extracts the source-label -> container role ("CNA"/"ADP")
// mapping that the MITRE CVE v5 extractor stores under Optional["container_types"].
// Returns an empty (non-nil) map when absent or malformed.
func mitreContainerTypes(optional map[string]any) map[string]string {
cts := map[string]string{}
m, ok := optional["container_types"].(map[string]any)
if !ok {
return cts
}
for source, ct := range m {
if s, ok := ct.(string); ok {
cts[source] = s
}
}
return cts
}

// mitreSSVC maps a single SSVC decision point to models.SSVC. The option keys
// follow the SSVC computed schema ("Exploitation", "Automatable",
// "Technical Impact").
func mitreSSVC(s ssvcTypes.SSVC) *models.SSVC {
ssvc := &models.SSVC{}
for _, o := range s.Options {
switch o.Key {
case "Exploitation":
ssvc.Exploitation = o.Value
case "Automatable":
ssvc.Automatable = o.Value
case "Technical Impact":
ssvc.TechnicalImpact = o.Value
}
}
return ssvc
}

// enrichVulnerabilityKEV extracts KEV data from vulnerability content and maps it to models.KEV.
// Handles CISA and VulnCheck KEV sources where KEV data is stored in vulnerability content.
func enrichVulnerabilityKEV(sourceID sourceTypes.SourceID, rootMap map[dataTypes.RootID][]vulnerabilityTypes.Vulnerability) []models.KEV {
Expand Down
1 change: 1 addition & 0 deletions detector/vuls2/vuls2.go
Original file line number Diff line number Diff line change
Expand Up @@ -1818,6 +1818,7 @@ func enrich(sesh *session.Session, vim models.VulnInfos) error {
sourceTypes.ExploitInTheWild,
sourceTypes.ExploitTrickest,
sourceTypes.Metasploit,
sourceTypes.MitreCVEV5,
sourceTypes.NVDFeedCVEv2,
sourceTypes.NucleiRepository,
sourceTypes.RedHatCVE,
Expand Down
69 changes: 67 additions & 2 deletions detector/vuls2/vuls2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11183,7 +11183,7 @@ func Test_enrich(t *testing.T) {
want: models.VulnInfos{},
},
{
name: "datasource not in enrich filter is filtered out",
name: "enrich with mitre-cve-v5 data",
args: args{
vim: models.VulnInfos{
"CVE-2023-44487": models.VulnInfo{
Expand All @@ -11193,7 +11193,57 @@ func Test_enrich(t *testing.T) {
},
want: models.VulnInfos{
"CVE-2023-44487": models.VulnInfo{
CveID: "CVE-2023-44487",
CveID: "CVE-2023-44487",
CveContents: models.CveContents{
models.Mitre: []models.CveContent{
{
Type: models.Mitre,
CveID: "CVE-2023-44487",
Title: "HTTP/2 Rapid Reset Attack",
Summary: "The HTTP/2 protocol allows a denial of service (server resource consumption) because request cancellation can reset many streams quickly, as exploited in the wild in August through October 2023.",
SourceLink: "https://www.cve.org/CVERecord?id=CVE-2023-44487",
Published: time.Date(2023, 10, 10, 0, 0, 0, 0, time.UTC),
LastModified: time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC),
SSVC: &models.SSVC{
Exploitation: "active",
Automatable: "yes",
TechnicalImpact: "total",
},
Optional: map[string]string{"source": "ADP:CISA-ADP"},
},
{
Type: models.Mitre,
CveID: "CVE-2023-44487",
Title: "HTTP/2 Rapid Reset Attack",
Summary: "The HTTP/2 protocol allows a denial of service (server resource consumption) because request cancellation can reset many streams quickly, as exploited in the wild in August through October 2023.",
Cvss3Score: 7.5,
Cvss3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
Cvss3Severity: "HIGH",
SourceLink: "https://www.cve.org/CVERecord?id=CVE-2023-44487",
References: models.References{
{Link: "https://www.cve.org/CVERecord?id=CVE-2023-44487", Source: "CVE", RefID: "CVE-2023-44487"},
},
Published: time.Date(2023, 10, 10, 0, 0, 0, 0, time.UTC),
LastModified: time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC),
Optional: map[string]string{"source": "CNA:cve@mitre.org"},
},
},
},
},
},
},
{
name: "datasource not in enrich filter is filtered out",
args: args{
vim: models.VulnInfos{
"CVE-2020-0001": models.VulnInfo{
CveID: "CVE-2020-0001",
},
},
},
want: models.VulnInfos{
"CVE-2020-0001": models.VulnInfo{
CveID: "CVE-2020-0001",
CveContents: models.CveContents{},
},
},
Expand Down Expand Up @@ -11264,6 +11314,21 @@ func compareVulnInfos(a, b models.VulnInfos) (string, error) {
gocmpopts.SortSlices(func(a, b models.Reference) bool {
return cmp.Compare(a.Link, b.Link) < 0
}),
// enrich emits CveContent in map order (e.g. one per MITRE CNA/ADP
// source); normalised downstream by CveContents.Sort, so compare
// order-insensitively here using the same key order as that sort.
gocmpopts.SortSlices(func(a, b models.CveContent) bool {
return cmp.Or(
cmp.Compare(b.Cvss40Score, a.Cvss40Score),
cmp.Compare(b.Cvss3Score, a.Cvss3Score),
cmp.Compare(b.Cvss2Score, a.Cvss2Score),
cmp.Compare(a.SourceLink, b.SourceLink),
cmp.Compare(a.Cvss40Vector, b.Cvss40Vector),
cmp.Compare(a.Cvss3Vector, b.Cvss3Vector),
cmp.Compare(a.Cvss2Vector, b.Cvss2Vector),
cmp.Compare(fmt.Sprintf("%#v", a.Optional), fmt.Sprintf("%#v", b.Optional)),
) < 0
}),
Comment thread
MaineK00n marked this conversation as resolved.
}...); diff != "" {
sb.WriteString(fmt.Sprintf("%s: %s\n", k, diff))
}
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4
github.com/BurntSushi/toml v1.6.0
github.com/CycloneDX/cyclonedx-go v0.10.0
github.com/MaineK00n/vuls-data-update v0.0.0-20260623021656-8bd9360ff613
github.com/MaineK00n/vuls2 v0.0.1-alpha.0.20260622074355-fab747f7e5ef
github.com/MaineK00n/vuls-data-update v0.0.0-20260625095034-a278fec71114
github.com/MaineK00n/vuls2 v0.0.1-alpha.0.20260626014420-4f87e83850fe
github.com/Ullaakut/nmap/v2 v2.2.2
github.com/aquasecurity/trivy v0.70.0
github.com/aquasecurity/trivy-db v0.0.0-20251222105351-a833f47f8f0d
Expand Down
Loading
Loading