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
28 changes: 9 additions & 19 deletions detector/detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,8 +397,10 @@ func DetectWordPressCves(r *models.ScanResult, wpCnf config.WpScanConf) error {
return nil
}

// 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)
// FillCvesWithGoCVEDictionary fills CVE detail with VulnCheck, JVN, Fortinet, Paloalto
// (NVD CveContent, EUVD, and MITRE are filled by the vuls2 enrich path instead, as are the US-CERT
// alerts; Cisco is detected by vuls2, which emits its DistroAdvisory and a sparse CveContent; only the
// JP-CERT alerts, derived from JVN, 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 @@ -425,7 +427,6 @@ func FillCvesWithGoCVEDictionary(r *models.ScanResult, cnf config.GoCveDictConf,
jvns := models.ConvertJvnToModel(d.CveID, d.Jvns)
fortinets := models.ConvertFortinetToModel(d.CveID, d.Fortinets)
paloaltos := models.ConvertPaloaltoToModel(d.CveID, d.Paloaltos)
ciscos := models.ConvertCiscoToModel(d.CveID, d.Ciscos)

alerts := fillCertAlerts(&d)
for cveID, vinfo := range r.ScannedCves {
Expand All @@ -441,7 +442,7 @@ func FillCvesWithGoCVEDictionary(r *models.ScanResult, cnf config.GoCveDictConf,
for _, con := range vulnchecks {
vinfo.CveContents[con.Type] = append(vinfo.CveContents[con.Type], con)
}
for _, cons := range [][]models.CveContent{jvns, fortinets, paloaltos, ciscos} {
for _, cons := range [][]models.CveContent{jvns, fortinets, paloaltos} {
for _, con := range cons {
if !con.Empty() {
if !slices.ContainsFunc(vinfo.CveContents[con.Type], func(e models.CveContent) bool {
Expand Down Expand Up @@ -539,30 +540,18 @@ func detectCpeURIsCvesWithGoCVEDictionary(r *models.ScanResult, cpes []Cpe, cnf
for _, detail := range details {
// Skip detections carried by no dictionary-remaining DETECTION
// source. The list mirrors go-cve-dictionary's GetByCpeURI
// admission gate minus the vuls2-migrated sources (NVD, so
// far), so NVD-only detections disappear here — vuls2
// admission gate minus the vuls2-migrated sources (NVD and Cisco),
// so NVD-only / Cisco-only detections disappear here — vuls2
// re-detects them from its own data. EUVD / MITRE contents can
// ride along on a detail but are never a detection basis
// (gocve neither matches nor admits on them, and
// getMaxConfidence has no tier for them), so they do not keep
// a detail alive.
if !detail.HasJvn() && !detail.HasCisco() && !detail.HasPaloalto() && !detail.HasFortinet() && !detail.HasVulncheck() {
if !detail.HasJvn() && !detail.HasPaloalto() && !detail.HasFortinet() && !detail.HasVulncheck() {
continue
Comment thread
shino marked this conversation as resolved.
}

advisories := []models.DistroAdvisory{}
if detail.HasCisco() {
for _, cisco := range detail.Ciscos {
advisories = append(advisories, models.DistroAdvisory{
AdvisoryID: cisco.AdvisoryID,
Severity: cisco.SIR,
Issued: cisco.FirstPublished,
Updated: cisco.LastUpdated,
Description: cisco.Summary,
})
}
}

if detail.HasPaloalto() {
for _, paloalto := range detail.Paloaltos {
advisories = append(advisories, models.DistroAdvisory{
Expand Down Expand Up @@ -619,6 +608,7 @@ func detectCpeURIsCvesWithGoCVEDictionary(r *models.ScanResult, cpes []Cpe, cnf
// detection. Deferring the strip keeps the earlier logic free of
// per-source "had*" flags as more sources migrate to vuls2.
detail.Nvds = nil
detail.Ciscos = nil
maxConfidence := getMaxConfidence(detail)

if val, ok := r.ScannedCves[detail.CveID]; ok {
Expand Down
18 changes: 11 additions & 7 deletions detector/vuls2/vendor.go
Original file line number Diff line number Diff line change
Expand Up @@ -632,8 +632,12 @@ func cveContentOptional(e ecosystemTypes.Ecosystem, v vulnerabilityTypes.Vulnera
return m
}

func cveContentSourceLink(ccType models.CveContentType, v vulnerabilityTypes.Vulnerability) string {
func cveContentSourceLink(ccType models.CveContentType, v vulnerabilityTypes.Vulnerability, rootID dataTypes.RootID) string {
switch ccType {
case models.Cisco:
// Cisco content lives in the advisory, whose ID is the root ID, so the
// per-CVE source link points at that advisory page.
return fmt.Sprintf("https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/%s", rootID)
case models.RedHat, models.RedHatAPI:
return fmt.Sprintf("https://access.redhat.com/security/cve/%s", v.Content.ID)
case models.Oracle:
Expand Down Expand Up @@ -1279,7 +1283,7 @@ func enrichRedHatCVE(vi *models.VulnInfo, rootMap map[dataTypes.RootID][]vulnera
if _, ok := vi.CveContents[models.RedHatAPI]; ok {
return
}
for _, vulns := range rootMap {
for rootID, vulns := range rootMap {
for _, v := range vulns {
cvss2, cvss3, cvss40 := enrichCvss(v.Content.Severity)

Expand All @@ -1288,7 +1292,7 @@ func enrichRedHatCVE(vi *models.VulnInfo, rootMap map[dataTypes.RootID][]vulnera
rs = append(rs, toReference(r.URL))
}

sourceLink := cveContentSourceLink(models.RedHatAPI, v)
sourceLink := cveContentSourceLink(models.RedHatAPI, v, rootID)
for _, m := range v.Content.Mitigations {
if m.Description == "" {
continue
Expand Down Expand Up @@ -1349,7 +1353,7 @@ func enrichRedHatCVE(vi *models.VulnInfo, rootMap map[dataTypes.RootID][]vulnera
// populate AlertDict.
func enrichNVD(vi *models.VulnInfo, rootMap map[dataTypes.RootID][]vulnerabilityTypes.Vulnerability) {
_, hasContent := vi.CveContents[models.Nvd]
for _, vulns := range rootMap {
for rootID, vulns := range rootMap {
for _, v := range vulns {
// US-CERT alerts: an NVD reference whose URL contains "us-cert"
// (mirrors go-cve-dictionary's NvdCert derivation). Done regardless
Expand Down Expand Up @@ -1414,7 +1418,7 @@ func enrichNVD(vi *models.VulnInfo, rootMap map[dataTypes.RootID][]vulnerability
Cvss40Score: cvss40.Score,
Cvss40Vector: cvss40.Vector,
Cvss40Severity: cvss40.Severity,
SourceLink: cveContentSourceLink(models.Nvd, v),
SourceLink: cveContentSourceLink(models.Nvd, v, rootID),
References: rs,
CweIDs: func() []string {
var cs []string //nolint:prealloc
Expand Down Expand Up @@ -1458,7 +1462,7 @@ func enrichMitreCVE(vi *models.VulnInfo, rootMap map[dataTypes.RootID][]vulnerab
ssvc *models.SSVC
}

for _, vulns := range rootMap {
for rootID, 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{}
Expand Down Expand Up @@ -1492,7 +1496,7 @@ func enrichMitreCVE(vi *models.VulnInfo, rootMap map[dataTypes.RootID][]vulnerab
// ("CNA"/"ADP"), set by the extractor from structural position.
containerTypes := mitreContainerTypes(v.Content.Optional)

sourceLink := cveContentSourceLink(models.Mitre, v)
sourceLink := cveContentSourceLink(models.Mitre, v, rootID)
published := func() time.Time {
if v.Content.Published != nil {
return *v.Content.Published
Expand Down
2 changes: 1 addition & 1 deletion detector/vuls2/vuls2.go
Original file line number Diff line number Diff line change
Expand Up @@ -1352,7 +1352,7 @@ func walkVulnerabilityDatas(m map[source]sourceData, vds []detectTypes.Vulnerabi
Cvss40Score: cvss40.Score,
Cvss40Vector: cvss40.Vector,
Cvss40Severity: cvss40.Severity,
SourceLink: cveContentSourceLink(cctype, v),
SourceLink: cveContentSourceLink(cctype, v, src.RootID),
References: rs,
CweIDs: func() []string {
var cs []string
Expand Down
166 changes: 166 additions & 0 deletions detector/vuls2/vuls2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
dataTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data"
advisoryTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/advisory"
advisoryContentTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/advisory/content"
cweTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/cwe"
conditionTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/detection/condition"
criteriaTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/detection/condition/criteria"
criterionTypes "github.com/MaineK00n/vuls-data-update/pkg/extract/types/data/detection/condition/criteria/criterion"
Expand Down Expand Up @@ -9148,6 +9149,171 @@ func Test_postConvert(t *testing.T) {
},
},
},
{
// Cisco is advisory-shaped (content in advisories[], M:N with CVEs,
// the vulnerability entry a bare CVE-ID stub), so the detection path
// emits a DistroAdvisory plus a sparse per-CVE CveContent whose source
// link points at the advisory (no CVSS/title/summary in the stub).
name: "cpe cisco detection emits DistroAdvisory and CveContent (source link to advisory)",
args: args{
scanned: scanTypes.ScanResult{
CPE: []string{
"cpe:2.3:a:cisco:firepower_threat_defense:7.4.0.0:*:*:*:*:*:*:*",
},
},
fsToOriginalCPE: map[string][]string{
"cpe:2.3:a:cisco:firepower_threat_defense:7.4.0.0:*:*:*:*:*:*:*": {"cpe:/a:cisco:firepower_threat_defense:7.4.0.0", "cpe:2.3:a:cisco:firepower_threat_defense:7.4.0.0:*:*:*:*:*:*:*"},
},
detected: detectTypes.DetectResult{
Detected: []detectTypes.VulnerabilityData{
{
ID: "cisco-sa-3100_4200_tlsdos-2yNSCd54",
Advisories: []dbTypes.VulnerabilityDataAdvisory{
{
ID: "cisco-sa-3100_4200_tlsdos-2yNSCd54",
Contents: map[sourceTypes.SourceID]map[dataTypes.RootID][]advisoryTypes.Advisory{
sourceTypes.CiscoJSON: {
dataTypes.RootID("cisco-sa-3100_4200_tlsdos-2yNSCd54"): []advisoryTypes.Advisory{
{
Content: advisoryContentTypes.Content{
ID: "cisco-sa-3100_4200_tlsdos-2yNSCd54",
Title: "Cisco Secure Firewall Adaptive Security Appliance and Secure Firewall Threat Defense Software for Firepower 3100 and 4200 Series TLS 1.3 Cipher Denial of Service Vulnerability",
Description: "A vulnerability in the TLS 1.3 implementation for a specific cipher for Cisco Secure Firewall ASA and FTD Software for Firepower 3100 and 4200 Series devices could allow an authenticated, remote attacker to cause a denial of service (DoS) condition.",
Severity: []severityTypes.Severity{
{
Type: severityTypes.SeverityTypeVendor,
Source: "cisco.com",
Vendor: new("High"),
},
},
CWE: []cweTypes.CWE{
{
Source: "cisco.com",
CWE: []string{"CWE-404"},
},
},
References: []referenceTypes.Reference{
{
Source: "cisco.com",
URL: "https://bst.cloudapps.cisco.com/bugsearch/bug/CSCwm91176",
},
{
Source: "cisco.com",
URL: "https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-3100_4200_tlsdos-2yNSCd54",
},
},
Published: new(time.Date(2025, 8, 14, 16, 0, 0, 0, time.UTC)),
Modified: new(time.Date(2025, 9, 3, 13, 37, 50, 0, time.UTC)),
},
Segments: []segmentTypes.Segment{
{
Ecosystem: ecosystemTypes.EcosystemTypeCPE,
},
},
},
},
},
},
},
},
Vulnerabilities: []dbTypes.VulnerabilityDataVulnerability{
{
ID: "CVE-2025-20127",
Contents: map[sourceTypes.SourceID]map[dataTypes.RootID][]vulnerabilityTypes.Vulnerability{
sourceTypes.CiscoJSON: {
dataTypes.RootID("cisco-sa-3100_4200_tlsdos-2yNSCd54"): []vulnerabilityTypes.Vulnerability{
{
Content: vulnerabilityContentTypes.Content{
ID: "CVE-2025-20127",
},
Segments: []segmentTypes.Segment{
{
Ecosystem: ecosystemTypes.EcosystemTypeCPE,
},
},
},
},
},
},
},
},
Detections: []detectTypes.VulnerabilityDataDetection{
{
Ecosystem: ecosystemTypes.EcosystemTypeCPE,
Contents: map[sourceTypes.SourceID][]conditionTypes.FilteredCondition{
sourceTypes.CiscoJSON: {
{
Criteria: criteriaTypes.FilteredCriteria{
Operator: criteriaTypes.CriteriaOperatorTypeOR,
Criterions: []criterionTypes.FilteredCriterion{
{
Criterion: criterionTypes.Criterion{
Type: criterionTypes.CriterionTypeCPE,
CPE: new(ccTypes.Criterion{
Vulnerable: true,
FixStatus: new(vcFixStatusTypes.FixStatus{
Class: vcFixStatusTypes.ClassUnknown,
}),
CPE: ccTypes.CPE("cpe:2.3:a:cisco:firepower_threat_defense:*:*:*:*:*:*:*:*"),
}),
},
Accepts: criterionTypes.AcceptQueries{
CPE: criterionTypes.CPEAccepts{Exact: []int{0}},
},
},
},
},
},
},
},
},
},
},
},
},
},
want: models.VulnInfos{
"CVE-2025-20127": {
CveID: "CVE-2025-20127",
Confidences: models.Confidences{models.CiscoExactVersionMatch},
CpeURIs: []string{"cpe:/a:cisco:firepower_threat_defense:7.4.0.0", "cpe:2.3:a:cisco:firepower_threat_defense:7.4.0.0:*:*:*:*:*:*:*"},
DistroAdvisories: models.DistroAdvisories{
{
AdvisoryID: "cisco-sa-3100_4200_tlsdos-2yNSCd54",
Severity: "High",
Issued: time.Date(2025, 8, 14, 16, 0, 0, 0, time.UTC),
Updated: time.Date(2025, 9, 3, 13, 37, 50, 0, time.UTC),
Description: "A vulnerability in the TLS 1.3 implementation for a specific cipher for Cisco Secure Firewall ASA and FTD Software for Firepower 3100 and 4200 Series devices could allow an authenticated, remote attacker to cause a denial of service (DoS) condition.",
},
},
// Cisco is advisory-shaped: the vulnerability stub carries
// only the CVE-ID, so the CveContent is sparse (no CVSS,
// title, or summary) and its source link points at the
// advisory (the root ID).
CveContents: models.CveContents{
models.Cisco: []models.CveContent{
{
Type: models.Cisco,
CveID: "CVE-2025-20127",
SourceLink: "https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-3100_4200_tlsdos-2yNSCd54",
References: models.References{
{
Link: "https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-3100_4200_tlsdos-2yNSCd54",
Source: "CISCO",
RefID: "cisco-sa-3100_4200_tlsdos-2yNSCd54",
},
},
Published: time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC),
LastModified: time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC),
Optional: map[string]string{
"vuls2-sources": "[{\"root_id\":\"cisco-sa-3100_4200_tlsdos-2yNSCd54\",\"source_id\":\"cisco-json\",\"segment\":{\"ecosystem\":\"cpe\"}}]",
},
},
},
},
},
},
},
{
// A criterion accepted the query only at version-unconfirmed
// quality (the upstream matcher could not confirm the scanned
Expand Down
6 changes: 5 additions & 1 deletion tui/tui.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tui
import (
"bytes"
"cmp"
"errors"
"fmt"
"os"
"slices"
Expand Down Expand Up @@ -56,7 +57,10 @@ func RunTui(results models.ScanResults) subcommands.ExitStatus {
g.SelFgColor = gocui.ColorBlack
g.Cursor = true

if err := g.MainLoop(); err != nil {
// gocui's MainLoop returns ErrQuit on a normal quit and never returns nil, so
// treat ErrQuit as a clean exit (the deferred g.Close runs) and exit only on a
// real error.
if err := g.MainLoop(); !errors.Is(err, gocui.ErrQuit) {
Comment thread
shino marked this conversation as resolved.
g.Close()
logging.Log.Errorf("%+v", err)
os.Exit(1)
Expand Down
Loading