Skip to content

Commit 770a8c9

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 770a8c9

11 files changed

Lines changed: 263 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: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package vuls2
33
import (
44
"cmp"
55
"fmt"
6+
"maps"
67
"slices"
78
"strings"
89
"time"
@@ -25,6 +26,7 @@ import (
2526
v2 "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/severity/cvss/v2"
2627
v31 "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/severity/cvss/v31"
2728
v40 "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/severity/cvss/v40"
29+
ssvcTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/ssvc"
2830
vulnerabilityTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/vulnerability"
2931
sourceTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/source"
3032
dbTypes "github.com/MaineK00n/vuls2/pkg/db/session/types"
@@ -649,6 +651,8 @@ func cveContentSourceLink(ccType models.CveContentType, v vulnerabilityTypes.Vul
649651
return fmt.Sprintf("https://security.alpinelinux.org/vuln/%s", v.Content.ID)
650652
case models.Nvd:
651653
return fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", v.Content.ID)
654+
case models.Mitre:
655+
return fmt.Sprintf("https://www.cve.org/CVERecord?id=%s", v.Content.ID)
652656
case models.Microsoft:
653657
return fmt.Sprintf("https://msrc.microsoft.com/update-guide/vulnerability/%s", v.Content.ID)
654658
default:
@@ -1168,6 +1172,8 @@ func enrichVulnerabilities(vi *models.VulnInfo, vulns []dbTypes.VulnerabilityDat
11681172
enrichRedHatCVE(vi, rootMap)
11691173
case sourceTypes.NVDFeedCVEv2:
11701174
enrichNVD(vi, rootMap)
1175+
case sourceTypes.MitreCVEV5:
1176+
enrichMitreCVE(vi, rootMap)
11711177
case sourceTypes.Metasploit:
11721178
enrichMetasploit(vi, rootMap)
11731179
case sourceTypes.ExploitExploitDB, sourceTypes.ExploitGitHub, sourceTypes.ExploitInTheWild, sourceTypes.ExploitTrickest, sourceTypes.NucleiRepository:
@@ -1435,6 +1441,151 @@ func enrichNVD(vi *models.VulnInfo, rootMap map[dataTypes.RootID][]vulnerability
14351441
}
14361442
}
14371443

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

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

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ github.com/MaineK00n/go-microsoft-version v0.0.0-20260325021654-1d9206bdeffc h1:
7070
github.com/MaineK00n/go-microsoft-version v0.0.0-20260325021654-1d9206bdeffc/go.mod h1:GNf+Vhnxk8/pW56jsxAeFCBP0VCgVQlLIJ812UnAj9c=
7171
github.com/MaineK00n/go-paloalto-version v0.0.0-20250909032857-57479910413b h1:pDmxa1+HCq7nShTgLURMOpjKc38hYq3lrgNHqur/Nps=
7272
github.com/MaineK00n/go-paloalto-version v0.0.0-20250909032857-57479910413b/go.mod h1:ELOxzfAd4oAe4niMmoZlSiJwzf1DF+DjNdjsUcuqAR8=
73-
github.com/MaineK00n/vuls-data-update v0.0.0-20260623021656-8bd9360ff613 h1:JomAV4cSVXmYTE/IfxxJuYHGD0/MDnVyVedmQi6BXws=
74-
github.com/MaineK00n/vuls-data-update v0.0.0-20260623021656-8bd9360ff613/go.mod h1:xvEPdYhcruMCrwAMEgbqCqZ5t+HLH6Ckn4jaEx59Uic=
75-
github.com/MaineK00n/vuls2 v0.0.1-alpha.0.20260622074355-fab747f7e5ef h1:85cHV3cu2WNU4doaR9KdFwvznAeJl5Or2D0AsMppTd4=
76-
github.com/MaineK00n/vuls2 v0.0.1-alpha.0.20260622074355-fab747f7e5ef/go.mod h1:b0oJvOY0MS7MTCqkvwt6hMvG+eBmXSnLc1eXFG5UMcA=
73+
github.com/MaineK00n/vuls-data-update v0.0.0-20260625095034-a278fec71114 h1:IWTWtlruuks7uVdfrGris9c3tMtlRC0Qpjhx1i97cCc=
74+
github.com/MaineK00n/vuls-data-update v0.0.0-20260625095034-a278fec71114/go.mod h1:sAsL1uqjB4DOJNwfuxY/8AW5h6l05deuxvYnLFedUHM=
75+
github.com/MaineK00n/vuls2 v0.0.1-alpha.0.20260626014420-4f87e83850fe h1:ifTfJ+NcA0VAPvt3X269QkFDB4ka4sSpbJyrzuD508A=
76+
github.com/MaineK00n/vuls2 v0.0.1-alpha.0.20260626014420-4f87e83850fe/go.mod h1:f4rALKUKk6NBqmfbj+W9K3/D+GetG7hV/xDKRPlAmoI=
7777
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
7878
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
7979
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=

0 commit comments

Comments
 (0)