Skip to content

Commit 7f9fc92

Browse files
MaineK00nclaude
andcommitted
feat(detector): enrich MITRE CVE v5 via vuls2
Add MITRE CVE v5 (mitre-cve-v5) as a vuls2 enrich data source and remove the MITRE path from FillCvesWithGoCVEDictionary so MITRE CveContent is now sourced from the vuls2 DB. This mirrors the NVD migration in #2575. MITRE content is emitted one entry per CNA/ADP source (rendered as mitre(<source>)) so each source's CVSS, CWE, references, and SSVC decision point are reported separately. - bump vuls-data-update to the revision adding the SSVC content field (#862). - vuls2.go: add sourceTypes.MitreCVEV5 to the enrich DataSources filter. - vendor.go: add enrichMitreCVE (source-split, with SSVC via mitreSSVC) and a Mitre case in cveContentSourceLink. - detector.go / models/utils.go: drop ConvertMitreToModel and its usage. - testdata: add a positive mitre-cve-v5 enrich test (CVE-2023-44487, with a CISA-ADP SSVC); move the "datasource not in filter" case to nvd-api-cve. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 635222e commit 7f9fc92

11 files changed

Lines changed: 268 additions & 152 deletions

File tree

detector/detector.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -397,8 +397,8 @@ func DetectWordPressCves(r *models.ScanResult, wpCnf config.WpScanConf) error {
397397
return nil
398398
}
399399

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

@@ -453,9 +452,6 @@ func FillCvesWithGoCVEDictionary(r *models.ScanResult, cnf config.GoCveDictConf,
453452
}
454453
}
455454
}
456-
for _, con := range mitres {
457-
vinfo.CveContents[con.Type] = append(vinfo.CveContents[con.Type], con)
458-
}
459455
// Set only JP-CERT; US-CERT is filled by the vuls2 enrich path
460456
// (vuls2.EnrichVulnInfos runs before this) and must be preserved.
461457
vinfo.AlertDict.JPCERT = alerts.JPCERT

detector/vuls2/testdata/fixtures/enrich/mitre-cve-v5/data/CVE-2023-44487.json

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,35 @@
2323
"url": "https://www.cve.org/CVERecord?id=CVE-2023-44487"
2424
}
2525
],
26-
"published": "2023-10-10T00:00:00Z"
26+
"ssvc": [
27+
{
28+
"source": "CISA-ADP",
29+
"role": "CISA Coordinator",
30+
"version": "2.0.3",
31+
"options": [
32+
{
33+
"key": "Exploitation",
34+
"value": "active"
35+
},
36+
{
37+
"key": "Automatable",
38+
"value": "yes"
39+
},
40+
{
41+
"key": "Technical Impact",
42+
"value": "total"
43+
}
44+
],
45+
"timestamp": "2023-10-10T00:00:00Z"
46+
}
47+
],
48+
"published": "2023-10-10T00:00:00Z",
49+
"optional": {
50+
"container_types": {
51+
"cve@mitre.org": "CNA",
52+
"CISA-ADP": "ADP"
53+
}
54+
}
2755
}
2856
}
2957
],

detector/vuls2/testdata/fixtures/enrich/mitre-cve-v5/data/CVE-2024-1102.json

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"id": "CVE-2020-0001",
3+
"vulnerabilities": [
4+
{
5+
"content": {
6+
"id": "CVE-2020-0001",
7+
"title": "from nvd-api-cve",
8+
"description": "This data source is not in the enrich DataSources filter, so it should never reach enrichVulnerabilities."
9+
}
10+
}
11+
],
12+
"data_source": {
13+
"id": "nvd-api-cve",
14+
"raws": [
15+
"fixtures/CVE-2020-0001.json"
16+
]
17+
}
18+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"id": "nvd-api-cve",
3+
"name": "NVD CVE API"
4+
}

detector/vuls2/vendor.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
v2 "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/severity/cvss/v2"
2626
v31 "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/severity/cvss/v31"
2727
v40 "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/severity/cvss/v40"
28+
ssvcTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/ssvc"
2829
vulnerabilityTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/vulnerability"
2930
sourceTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/source"
3031
dbTypes "github.com/MaineK00n/vuls2/pkg/db/session/types"
@@ -649,6 +650,8 @@ func cveContentSourceLink(ccType models.CveContentType, v vulnerabilityTypes.Vul
649650
return fmt.Sprintf("https://security.alpinelinux.org/vuln/%s", v.Content.ID)
650651
case models.Nvd:
651652
return fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", v.Content.ID)
653+
case models.Mitre:
654+
return fmt.Sprintf("https://www.cve.org/CVERecord?id=%s", v.Content.ID)
652655
case models.Microsoft:
653656
return fmt.Sprintf("https://msrc.microsoft.com/update-guide/vulnerability/%s", v.Content.ID)
654657
default:
@@ -1168,6 +1171,8 @@ func enrichVulnerabilities(vi *models.VulnInfo, vulns []dbTypes.VulnerabilityDat
11681171
enrichRedHatCVE(vi, rootMap)
11691172
case sourceTypes.NVDFeedCVEv2:
11701173
enrichNVD(vi, rootMap)
1174+
case sourceTypes.MitreCVEV5:
1175+
enrichMitreCVE(vi, rootMap)
11711176
case sourceTypes.Metasploit:
11721177
enrichMetasploit(vi, rootMap)
11731178
case sourceTypes.ExploitExploitDB, sourceTypes.ExploitGitHub, sourceTypes.ExploitInTheWild, sourceTypes.ExploitTrickest, sourceTypes.NucleiRepository:
@@ -1435,6 +1440,148 @@ func enrichNVD(vi *models.VulnInfo, rootMap map[dataTypes.RootID][]vulnerability
14351440
}
14361441
}
14371442

1443+
// enrichMitreCVE adds MITRE CVE v5 data as CveContent, one entry per CNA/ADP
1444+
// source so each source's CVSS, CWE, references, and SSVC decision point are
1445+
// reported separately (rendered as mitre(<source>) by CveContents.SSVC and the
1446+
// reporter). The source is the CNA/ADP provider shortName (e.g. "CISA-ADP").
1447+
func enrichMitreCVE(vi *models.VulnInfo, rootMap map[dataTypes.RootID][]vulnerabilityTypes.Vulnerability) {
1448+
if _, ok := vi.CveContents[models.Mitre]; ok {
1449+
return
1450+
}
1451+
1452+
// mitreBySource accumulates the per-source MITRE fields so each CNA/ADP
1453+
// source becomes its own CveContent.
1454+
type mitreBySource struct {
1455+
severities []severityTypes.Severity
1456+
references models.References
1457+
cweIDs []string
1458+
ssvc *models.SSVC
1459+
}
1460+
1461+
for _, vulns := range rootMap {
1462+
for _, v := range vulns {
1463+
// Group every per-source field by its CNA/ADP source in a single pass.
1464+
bySource := map[string]*mitreBySource{}
1465+
get := func(source string) *mitreBySource {
1466+
b, ok := bySource[source]
1467+
if !ok {
1468+
b = &mitreBySource{}
1469+
bySource[source] = b
1470+
}
1471+
return b
1472+
}
1473+
for _, s := range v.Content.Severity {
1474+
b := get(s.Source)
1475+
b.severities = append(b.severities, s)
1476+
}
1477+
for _, r := range v.Content.References {
1478+
b := get(r.Source)
1479+
b.references = append(b.references, toReference(r.URL))
1480+
}
1481+
for _, c := range v.Content.CWE {
1482+
b := get(c.Source)
1483+
b.cweIDs = append(b.cweIDs, c.CWE...)
1484+
}
1485+
for _, s := range v.Content.SSVC {
1486+
if b := get(s.Source); b.ssvc == nil {
1487+
b.ssvc = mitreSSVC(s)
1488+
}
1489+
}
1490+
1491+
// container_types maps each source label to its container role
1492+
// ("CNA"/"ADP"), set by the extractor from structural position.
1493+
containerTypes := mitreContainerTypes(v.Content.Optional)
1494+
1495+
sourceLink := cveContentSourceLink(models.Mitre, v)
1496+
published := func() time.Time {
1497+
if v.Content.Published != nil {
1498+
return *v.Content.Published
1499+
}
1500+
return time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC)
1501+
}()
1502+
lastModified := func() time.Time {
1503+
if v.Content.Modified != nil {
1504+
return *v.Content.Modified
1505+
}
1506+
return time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC)
1507+
}()
1508+
1509+
// Order is normalised downstream by CveContents.Sort (see
1510+
// ScanResult.SortForJSONOutput), so emit in map order here.
1511+
for source, b := range bySource {
1512+
cvss2, cvss3, cvss40 := enrichCvss(b.severities)
1513+
1514+
cc := models.CveContent{
1515+
Type: models.Mitre,
1516+
CveID: string(v.Content.ID),
1517+
Title: v.Content.Title,
1518+
Summary: v.Content.Description,
1519+
Cvss2Score: cvss2.BaseScore,
1520+
Cvss2Vector: cvss2.Vector,
1521+
Cvss2Severity: cvss2.NVDBaseSeverity,
1522+
Cvss3Score: cvss3.BaseScore,
1523+
Cvss3Vector: cvss3.Vector,
1524+
Cvss3Severity: cvss3.BaseSeverity,
1525+
Cvss40Score: cvss40.Score,
1526+
Cvss40Vector: cvss40.Vector,
1527+
Cvss40Severity: cvss40.Severity,
1528+
SourceLink: sourceLink,
1529+
References: b.references,
1530+
CweIDs: b.cweIDs,
1531+
Published: published,
1532+
LastModified: lastModified,
1533+
SSVC: b.ssvc,
1534+
}
1535+
if source != "" {
1536+
// Qualify the source with its CNA/ADP role when known
1537+
// (e.g. "ADP:CISA-ADP"), so reports distinguish the two.
1538+
label := source
1539+
if ct := containerTypes[source]; ct != "" {
1540+
label = fmt.Sprintf("%s:%s", ct, source)
1541+
}
1542+
cc.Optional = map[string]string{"source": label}
1543+
}
1544+
vi.CveContents[models.Mitre] = append(vi.CveContents[models.Mitre], cc)
1545+
}
1546+
}
1547+
}
1548+
}
1549+
1550+
// mitreContainerTypes extracts the source-label -> container role ("CNA"/"ADP")
1551+
// mapping that the MITRE CVE v5 extractor stores under Optional["container_types"].
1552+
// Returns an empty (non-nil) map when absent or malformed.
1553+
func mitreContainerTypes(optional map[string]any) map[string]string {
1554+
cts := map[string]string{}
1555+
m, ok := optional["container_types"].(map[string]any)
1556+
if !ok {
1557+
return cts
1558+
}
1559+
for source, ct := range m {
1560+
if s, ok := ct.(string); ok {
1561+
cts[source] = s
1562+
}
1563+
}
1564+
return cts
1565+
}
1566+
1567+
// mitreSSVC maps a single SSVC decision point to models.SSVC. The option keys
1568+
// follow the SSVC computed schema ("Exploitation", "Automatable",
1569+
// "Technical Impact").
1570+
func mitreSSVC(s ssvcTypes.SSVC) *models.SSVC {
1571+
ssvc := &models.SSVC{}
1572+
for _, o := range s.Options {
1573+
switch o.Key {
1574+
case "Exploitation":
1575+
ssvc.Exploitation = o.Value
1576+
case "Automatable":
1577+
ssvc.Automatable = o.Value
1578+
case "Technical Impact":
1579+
ssvc.TechnicalImpact = o.Value
1580+
}
1581+
}
1582+
return ssvc
1583+
}
1584+
14381585
// enrichVulnerabilityKEV extracts KEV data from vulnerability content and maps it to models.KEV.
14391586
// Handles CISA and VulnCheck KEV sources where KEV data is stored in vulnerability content.
14401587
func enrichVulnerabilityKEV(sourceID sourceTypes.SourceID, rootMap map[dataTypes.RootID][]vulnerabilityTypes.Vulnerability) []models.KEV {

detector/vuls2/vuls2.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1818,6 +1818,7 @@ func enrich(sesh *session.Session, vim models.VulnInfos) error {
18181818
sourceTypes.ExploitInTheWild,
18191819
sourceTypes.ExploitTrickest,
18201820
sourceTypes.Metasploit,
1821+
sourceTypes.MitreCVEV5,
18211822
sourceTypes.NVDFeedCVEv2,
18221823
sourceTypes.NucleiRepository,
18231824
sourceTypes.RedHatCVE,

detector/vuls2/vuls2_test.go

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11183,7 +11183,7 @@ func Test_enrich(t *testing.T) {
1118311183
want: models.VulnInfos{},
1118411184
},
1118511185
{
11186-
name: "datasource not in enrich filter is filtered out",
11186+
name: "enrich with mitre-cve-v5 data",
1118711187
args: args{
1118811188
vim: models.VulnInfos{
1118911189
"CVE-2023-44487": models.VulnInfo{
@@ -11193,7 +11193,57 @@ func Test_enrich(t *testing.T) {
1119311193
},
1119411194
want: models.VulnInfos{
1119511195
"CVE-2023-44487": models.VulnInfo{
11196-
CveID: "CVE-2023-44487",
11196+
CveID: "CVE-2023-44487",
11197+
CveContents: models.CveContents{
11198+
models.Mitre: []models.CveContent{
11199+
{
11200+
Type: models.Mitre,
11201+
CveID: "CVE-2023-44487",
11202+
Title: "HTTP/2 Rapid Reset Attack",
11203+
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.",
11204+
SourceLink: "https://www.cve.org/CVERecord?id=CVE-2023-44487",
11205+
Published: time.Date(2023, 10, 10, 0, 0, 0, 0, time.UTC),
11206+
LastModified: time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC),
11207+
SSVC: &models.SSVC{
11208+
Exploitation: "active",
11209+
Automatable: "yes",
11210+
TechnicalImpact: "total",
11211+
},
11212+
Optional: map[string]string{"source": "ADP:CISA-ADP"},
11213+
},
11214+
{
11215+
Type: models.Mitre,
11216+
CveID: "CVE-2023-44487",
11217+
Title: "HTTP/2 Rapid Reset Attack",
11218+
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.",
11219+
Cvss3Score: 7.5,
11220+
Cvss3Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
11221+
Cvss3Severity: "HIGH",
11222+
SourceLink: "https://www.cve.org/CVERecord?id=CVE-2023-44487",
11223+
References: models.References{
11224+
{Link: "https://www.cve.org/CVERecord?id=CVE-2023-44487", Source: "CVE", RefID: "CVE-2023-44487"},
11225+
},
11226+
Published: time.Date(2023, 10, 10, 0, 0, 0, 0, time.UTC),
11227+
LastModified: time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC),
11228+
Optional: map[string]string{"source": "CNA:cve@mitre.org"},
11229+
},
11230+
},
11231+
},
11232+
},
11233+
},
11234+
},
11235+
{
11236+
name: "datasource not in enrich filter is filtered out",
11237+
args: args{
11238+
vim: models.VulnInfos{
11239+
"CVE-2020-0001": models.VulnInfo{
11240+
CveID: "CVE-2020-0001",
11241+
},
11242+
},
11243+
},
11244+
want: models.VulnInfos{
11245+
"CVE-2020-0001": models.VulnInfo{
11246+
CveID: "CVE-2020-0001",
1119711247
CveContents: models.CveContents{},
1119811248
},
1119911249
},
@@ -11264,6 +11314,15 @@ func compareVulnInfos(a, b models.VulnInfos) (string, error) {
1126411314
gocmpopts.SortSlices(func(a, b models.Reference) bool {
1126511315
return cmp.Compare(a.Link, b.Link) < 0
1126611316
}),
11317+
// enrich emits CveContent in map order (e.g. one per MITRE CNA/ADP
11318+
// source); normalised downstream by CveContents.Sort, so compare
11319+
// order-insensitively here.
11320+
gocmpopts.SortSlices(func(a, b models.CveContent) bool {
11321+
return cmp.Or(
11322+
cmp.Compare(a.SourceLink, b.SourceLink),
11323+
cmp.Compare(a.Optional["source"], b.Optional["source"]),
11324+
) < 0
11325+
}),
1126711326
}...); diff != "" {
1126811327
sb.WriteString(fmt.Sprintf("%s: %s\n", k, diff))
1126911328
}

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ require (
66
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4
77
github.com/BurntSushi/toml v1.6.0
88
github.com/CycloneDX/cyclonedx-go v0.10.0
9-
github.com/MaineK00n/vuls-data-update v0.0.0-20260623021656-8bd9360ff613
10-
github.com/MaineK00n/vuls2 v0.0.1-alpha.0.20260622074355-fab747f7e5ef
9+
github.com/MaineK00n/vuls-data-update v0.0.0-20260625095034-a278fec71114
10+
github.com/MaineK00n/vuls2 v0.0.1-alpha.0.20260626014420-4f87e83850fe
1111
github.com/Ullaakut/nmap/v2 v2.2.2
1212
github.com/aquasecurity/trivy v0.70.0
1313
github.com/aquasecurity/trivy-db v0.0.0-20251222105351-a833f47f8f0d

0 commit comments

Comments
 (0)