diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e00265303..38f75e8ed 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -162,7 +162,7 @@ jobs: contains(github.event.pull_request.labels.*.name, 'generate-dumps-on-pr') env: NVD_API_KEY: ${{ secrets.NVD_API_KEY }} - ROX_LEGACY_NVD_LOADER: true + ROX_NVD_FEED_LOADER: true runs-on: ubuntu-latest needs: - pre-build-updater diff --git a/e2etests/vuln_test.go b/e2etests/vuln_test.go index 05c6ffec5..97b44f358 100644 --- a/e2etests/vuln_test.go +++ b/e2etests/vuln_test.go @@ -142,6 +142,9 @@ func TestStackroxVulnImages(t *testing.T) { image: "quay.io/rhacs-eng/qa:rails-cve-2016-2098", expectedFeatures: []feature{ {"rails", "4.2.5.1", []expectedVuln{ + {name: "CVE-2016-2098"}, + {name: "CVE-2016-6316"}, + {name: "CVE-2016-6317"}, {name: "CVE-2018-16476", fixedBy: "4.2.11"}, {name: "CVE-2019-5418", fixedBy: "4.2.11.1"}, {name: "CVE-2019-5419", fixedBy: "4.2.11.1"}, @@ -149,16 +152,6 @@ func TestStackroxVulnImages(t *testing.T) { }, }, }, - unexpectedVulns: []feature{ - {"rails", "4.2.5.1", []expectedVuln{ - // These three vulns should exist, but NVD set these to deferred. - // Placing them here until they are no longer deferred. - {name: "CVE-2016-2098"}, - {name: "CVE-2016-6316"}, - {name: "CVE-2016-6317"}, - }, - }, - }, }, { // docker.io/1and1internet/ubuntu-16-customerssh:latest @@ -184,6 +177,10 @@ func TestStackroxVulnImages(t *testing.T) { {name: "CVE-2019-10086", fixedBy: ""}, }, }, + {"commons_fileupload", "1.3.2", []expectedVuln{ + {name: "CVE-2016-1000031", fixedBy: ""}, + }, + }, {"guava", "18.0", []expectedVuln{ {name: "CVE-2018-10237", fixedBy: "24.1.1"}, }, @@ -197,12 +194,6 @@ func TestStackroxVulnImages(t *testing.T) { {name: "CVE-2015-2512"}, }, }, - {"commons_fileupload", "1.3.2", []expectedVuln{ - // This vuln should exist, but NVD set it to deferred. - // Placing it here until they are no longer deferred. - {name: "CVE-2016-1000031", fixedBy: ""}, - }, - }, }, }, { @@ -218,17 +209,17 @@ func TestStackroxVulnImages(t *testing.T) { { // docker.io/library/cassandra:latest image: "quay.io/rhacs-eng/qa:cassandra", + expectedFeatures: []feature{ + {"logback", "1.1.3", []expectedVuln{ + {name: "CVE-2017-5929", fixedBy: ""}, + }, + }, + }, unexpectedVulns: []feature{ {"slingshot", "0.10.3", []expectedVuln{ {name: "CVE-2015-5711"}, }, }, - {"logback", "1.1.3", []expectedVuln{ - // This vuln should exist, but NVD set it to deferred. - // Placing it here until they are no longer deferred. - {name: "CVE-2017-5929", fixedBy: ""}, - }, - }, }, }, { diff --git a/pkg/env/list.go b/pkg/env/list.go index 6a9719a89..bcfc107a8 100644 --- a/pkg/env/list.go +++ b/pkg/env/list.go @@ -41,9 +41,9 @@ var ( // MaxGrpcConcurrentStreams configures the maximum number of HTTP/2 streams to use with gRPC MaxGrpcConcurrentStreams = RegisterIntegerSetting("ROX_GRPC_MAX_CONCURRENT_STREAMS", DefaultMaxGrpcConcurrentStreams) - // LegacyNVDLoader when true will cause the loader to pull NVD data using - // the NVD Legacy Data Feeds, if false will pull from the NVD 2.0 API. - LegacyNVDLoader = RegisterBooleanSetting("ROX_LEGACY_NVD_LOADER", false) + // NVDFeedLoader when true will cause the loader to pull NVD data using + // the NVD 2.0 Data Feeds. If false, the loader will pull from the NVD 2.0 API. + NVDFeedLoader = RegisterBooleanSetting("ROX_NVD_FEED_LOADER", false) // RHLineage when true will cause all parent layers (a.k.a lineage) to be considered when // storing scan results for RHEL image layers. diff --git a/pkg/vulnloader/nvdloader/convert.go b/pkg/vulnloader/nvdloader/convert.go index 05bc52c20..4c7aa3432 100644 --- a/pkg/vulnloader/nvdloader/convert.go +++ b/pkg/vulnloader/nvdloader/convert.go @@ -14,7 +14,7 @@ const ( jsonTimeFormat = "2006-01-02T15:04Z" ) -func toJSON(vulns []*apischema.CVEAPIJSON20DefCVEItem) ([]*jsonschema.NVDCVEFeedJSON10DefCVEItem, error) { +func toJSON10(vulns []*apischema.CVEAPIJSON20DefCVEItem) ([]*jsonschema.NVDCVEFeedJSON10DefCVEItem, error) { if vulns == nil { return nil, nil } @@ -31,6 +31,11 @@ func toJSON(vulns []*apischema.CVEAPIJSON20DefCVEItem) ([]*jsonschema.NVDCVEFeed continue } + // Ignore rejected vulnerabilities. + if strings.EqualFold(vuln.CVE.VulnStatus, "Rejected") { + continue + } + cve := vuln.CVE modifiedTime, err := toTime(cve.LastModified) @@ -152,14 +157,18 @@ func toBaseMetricV2(metrics []*apischema.CVEAPIJSON20CVSSV2) *jsonschema.NVDCVEF } func toBaseMetricV3(metrics30 []*apischema.CVEAPIJSON20CVSSV30, metrics31 []*apischema.CVEAPIJSON20CVSSV31) *jsonschema.NVDCVEFeedJSON10DefImpactBaseMetricV3 { - switch { - case len(metrics31) != 0: - return toBaseMetricV31(metrics31) - case len(metrics30) != 0: - return toBaseMetricV30(metrics30) - default: - return nil + // Prefer CVSS 3.1. + baseMetric := toBaseMetricV31(metrics31) + if baseMetric != nil { + return baseMetric } + + baseMetric = toBaseMetricV30(metrics30) + if baseMetric != nil { + return baseMetric + } + + return nil } func toBaseMetricV31(metrics []*apischema.CVEAPIJSON20CVSSV31) *jsonschema.NVDCVEFeedJSON10DefImpactBaseMetricV3 { diff --git a/pkg/vulnloader/nvdloader/convert_test.go b/pkg/vulnloader/nvdloader/convert_test.go new file mode 100644 index 000000000..fc81b4bbb --- /dev/null +++ b/pkg/vulnloader/nvdloader/convert_test.go @@ -0,0 +1,130 @@ +package nvdloader + +import ( + "os" + "testing" + + jsonschema "github.com/facebookincubator/nvdtools/cvefeed/nvd/schema" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestToJSON10(t *testing.T) { + f, err := os.Open("testdata/nvdcve-2.0-2025.json") + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, f.Close()) + }) + + cveAPI, err := parseReader(f) + require.NoError(t, err) + + cveFeed, err := toJSON10(cveAPI.Vulnerabilities) + assert.NoError(t, err) + + expected := []*jsonschema.NVDCVEFeedJSON10DefCVEItem{ + { + CVE: &jsonschema.CVEJSON40{ + CVEDataMeta: &jsonschema.CVEJSON40CVEDataMeta{ + ID: "CVE-2025-0168", + }, + Description: &jsonschema.CVEJSON40Description{ + DescriptionData: []*jsonschema.CVEJSON40LangString{ + { + Lang: "en", + Value: "A vulnerability classified as critical has been found in code-projects Job Recruitment 1.0. This affects an unknown part of the file /_parse/_feedback_system.php. The manipulation of the argument person leads to sql injection. It is possible to initiate the attack remotely. The exploit has been disclosed to the public and may be used.", + }, + }, + }, + }, + Configurations: &jsonschema.NVDCVEFeedJSON10DefConfigurations{ + Nodes: []*jsonschema.NVDCVEFeedJSON10DefNode{ + { + CPEMatch: []*jsonschema.NVDCVEFeedJSON10DefCPEMatch{ + { + Cpe23Uri: "cpe:2.3:a:anisha:job_recruitment:1.0:*:*:*:*:*:*:*", + Vulnerable: true, + }, + }, + Operator: "OR", + }, + }, + }, + Impact: &jsonschema.NVDCVEFeedJSON10DefImpact{ + BaseMetricV3: &jsonschema.NVDCVEFeedJSON10DefImpactBaseMetricV3{ + CVSSV3: &jsonschema.CVSSV30{ + AttackComplexity: "LOW", + AttackVector: "NETWORK", + AvailabilityImpact: "NONE", + BaseScore: 7.5, + BaseSeverity: "HIGH", + ConfidentialityImpact: "HIGH", + IntegrityImpact: "NONE", + PrivilegesRequired: "NONE", + Scope: "UNCHANGED", + UserInteraction: "NONE", + VectorString: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + Version: "3.1", + }, + ExploitabilityScore: 3.9, + ImpactScore: 3.6, + }, + }, + LastModifiedDate: "2025-02-25T21:26Z", + PublishedDate: "2025-01-01T14:15Z", + }, + { + CVE: &jsonschema.CVEJSON40{ + CVEDataMeta: &jsonschema.CVEJSON40CVEDataMeta{ + ID: "CVE-2025-22144", + }, + Description: &jsonschema.CVEJSON40Description{ + DescriptionData: []*jsonschema.CVEJSON40LangString{ + { + Lang: "en", + Value: "NamelessMC is a free, easy to use & powerful website software for Minecraft servers. A user with admincp.core.emails or admincp.users.edit permissions can validate users and an attacker can reset their password. When the account is successfully approved by email the reset code is NULL, but when the account is manually validated by a user with admincp.core.emails or admincp.users.edit permissions then the reset_code will no longer be NULL but empty. An attacker can request http://localhost/nameless/index.php?route=/forgot_password/&c= and reset the password. As a result an attacker may compromise another users password and take over their account. This issue has been addressed in release version 2.1.3 and all users are advised to upgrade. There are no known workarounds for this vulnerability.", + }, + }, + }, + }, + Configurations: &jsonschema.NVDCVEFeedJSON10DefConfigurations{ + Nodes: []*jsonschema.NVDCVEFeedJSON10DefNode{ + { + CPEMatch: []*jsonschema.NVDCVEFeedJSON10DefCPEMatch{ + { + Cpe23Uri: "cpe:2.3:a:namelessmc:nameless:*:*:*:*:*:*:*:*", + VersionEndExcluding: "2.1.3", + Vulnerable: true, + }, + }, + Operator: "OR", + }, + }, + }, + Impact: &jsonschema.NVDCVEFeedJSON10DefImpact{ + BaseMetricV3: &jsonschema.NVDCVEFeedJSON10DefImpactBaseMetricV3{ + CVSSV3: &jsonschema.CVSSV30{ + AttackComplexity: "LOW", + AttackVector: "NETWORK", + AvailabilityImpact: "HIGH", + BaseScore: 9.8, + BaseSeverity: "CRITICAL", + ConfidentialityImpact: "HIGH", + IntegrityImpact: "HIGH", + PrivilegesRequired: "NONE", + Scope: "UNCHANGED", + UserInteraction: "NONE", + VectorString: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + Version: "3.1", + }, + ExploitabilityScore: 3.9, + ImpactScore: 5.9, + }, + }, + LastModifiedDate: "2025-05-13T15:42Z", + PublishedDate: "2025-01-13T20:15Z", + }, + } + + assert.ElementsMatch(t, expected, cveFeed) +} diff --git a/pkg/vulnloader/nvdloader/loader.go b/pkg/vulnloader/nvdloader/loader.go index 8038a7d52..5a74ce038 100644 --- a/pkg/vulnloader/nvdloader/loader.go +++ b/pkg/vulnloader/nvdloader/loader.go @@ -1,266 +1,15 @@ package nvdloader import ( - "encoding/json" - "fmt" - "net/http" - "os" - "path/filepath" - "time" - - apischema "github.com/facebookincubator/nvdtools/cveapi/nvd/schema" - jsonschema "github.com/facebookincubator/nvdtools/cvefeed/nvd/schema" - "github.com/facebookincubator/nvdtools/wfn" - log "github.com/sirupsen/logrus" - "github.com/stackrox/rox/pkg/httputil/proxy" - "github.com/stackrox/rox/pkg/utils" "github.com/stackrox/scanner/pkg/env" "github.com/stackrox/scanner/pkg/vulndump" "github.com/stackrox/scanner/pkg/vulnloader" ) -const urlFmt = `https://services.nvd.nist.gov/rest/json/cves/2.0?noRejected&startIndex=%d` - -var client = http.Client{ - Timeout: 5 * time.Minute, - Transport: proxy.RoundTripper(), -} - func init() { - if env.LegacyNVDLoader.Enabled() { - vulnloader.RegisterLoader(vulndump.NVDDirName, &legacyLoader{}) + if env.NVDFeedLoader.Enabled() { + vulnloader.RegisterLoader(vulndump.NVDDirName, &feedLoader{}) } else { - vulnloader.RegisterLoader(vulndump.NVDDirName, &loader{}) - } -} - -var _ vulnloader.Loader = (*loader)(nil) - -type loader struct{} - -// DownloadFeedsToPath downloads the NVD feeds to the given path. -// If this function is successful, it will fill the directory with -// one json file for each year of NVD data. -func (l *loader) DownloadFeedsToPath(outputDir string) error { - log.Info("Downloading NVD data using NVD 2.0 API") - - // Fetch NVD enrichment data from curated repos - enrichments, err := Fetch() - if err != nil { - return fmt.Errorf("could not fetch NVD enrichment sources: %w", err) - } - - nvdDir := filepath.Join(outputDir, vulndump.NVDDirName) - if err := os.MkdirAll(nvdDir, 0755); err != nil { - return fmt.Errorf("creating subdir for %s: %w", vulndump.NVDDirName, err) + vulnloader.RegisterLoader(vulndump.NVDDirName, &apiLoader{}) } - - var fileNo, totalVulns int - - // Explicitly set startIdx to parallel how this is all done within the loop below. - startIdx := 0 - apiResp, err := query(fmt.Sprintf(urlFmt, startIdx)) - if err != nil { - return err - } - var i int - // Buffer to store vulns until they are written to a file. - cveItems := make([]*jsonschema.NVDCVEFeedJSON10DefCVEItem, 0, 20_000) - for apiResp.ResultsPerPage != 0 { - vulns, err := toJSON(apiResp.Vulnerabilities) - if err != nil { - return fmt.Errorf("failed to convert API vulns to JSON: %w", err) - } - - if len(vulns) != 0 { - cveItems = append(cveItems, vulns...) - - i++ - // Write to disk every ~20,000 vulnerabilities. - if i == 10 { - i = 0 - - enrichCVEItems(&cveItems, enrichments) - - feed := &jsonschema.NVDCVEFeedJSON10{ - CVEItems: cveItems, - } - if err := writeFile(filepath.Join(nvdDir, fmt.Sprintf("%d.json", fileNo)), feed); err != nil { - return fmt.Errorf("writing to file: %w", err) - } - - fileNo++ - totalVulns += len(cveItems) - log.Infof("Loaded %d NVD vulnerabilities", totalVulns) - // Reduce, reuse, and recycle. - cveItems = cveItems[:0] - } - } - - // Rudimentary rate-limiting. - // NVD limits users without an API key to roughly one call every 6 seconds. - // With an API key, it is roughly one call every 0.6 seconds. - // We'll play it safe and do one call every 3 seconds. - // As of writing there are ~216,000 vulnerabilities, so this whole process should take ~5.4 minutes. - time.Sleep(3 * time.Second) - - startIdx += apiResp.ResultsPerPage - apiResp, err = query(fmt.Sprintf(urlFmt, startIdx)) - if err != nil { - return err - } - } - - // Write the remaining vulnerabilities. - if len(cveItems) != 0 { - enrichCVEItems(&cveItems, enrichments) - - feed := &jsonschema.NVDCVEFeedJSON10{ - CVEItems: cveItems, - } - if err := writeFile(filepath.Join(nvdDir, fmt.Sprintf("%d.json", fileNo)), feed); err != nil { - return fmt.Errorf("writing to file: %w", err) - } - - totalVulns += len(cveItems) - log.Infof("Loaded %d NVD vulnerabilities", totalVulns) - } - - return nil -} - -func query(url string) (*apischema.CVEAPIJSON20, error) { - log.Debugf("Querying %s", url) - req, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - return nil, fmt.Errorf("creating HTTP request: %w", err) - } - req.Header.Set("apiKey", os.Getenv("NVD_API_KEY")) - - apiResp, err := queryWithBackoff(req) - if err != nil { - return nil, err - } - - return apiResp, nil -} - -func queryWithBackoff(req *http.Request) (*apischema.CVEAPIJSON20, error) { - var ( - apiResp *apischema.CVEAPIJSON20 - err error - ) - for i := 1; i <= 5; i++ { - var resp *http.Response - resp, err = tryQuery(req) - if err == nil { - apiResp, err = parseResponse(resp) - if err == nil { - break - } - } - log.Warnf("Failed query attempt %d for %s: %v", i, req.URL.String(), err) - // Wait some multiple of 3 seconds before next attempt. - time.Sleep(time.Duration(3*i) * time.Second) - } - - return apiResp, err -} - -func tryQuery(req *http.Request) (*http.Response, error) { - resp, err := client.Do(req) - if err != nil { - return nil, fmt.Errorf("fetching NVD API results: %w", err) - } - - log.Debugf("Queried %s with status code %d", req.URL.String(), resp.StatusCode) - if resp.StatusCode != 200 { - utils.IgnoreError(resp.Body.Close) - return nil, fmt.Errorf("unexpected status code when querying %s: %d", req.URL.String(), resp.StatusCode) - } - - return resp, nil -} - -func parseResponse(resp *http.Response) (*apischema.CVEAPIJSON20, error) { - defer utils.IgnoreError(resp.Body.Close) - - apiResp := new(apischema.CVEAPIJSON20) - if err := json.NewDecoder(resp.Body).Decode(apiResp); err != nil { - return nil, fmt.Errorf("decoding API response: %w", err) - } - - return apiResp, nil -} - -func enrichCVEItems(cveItems *[]*jsonschema.NVDCVEFeedJSON10DefCVEItem, enrichments map[string]*FileFormatWrapper) { - if cveItems == nil { - return - } - - cves := (*cveItems)[:0] - for _, item := range *cveItems { - if _, ok := manuallyEnrichedVulns[item.CVE.CVEDataMeta.ID]; ok { - log.Warnf("Skipping vuln %s because it is being manually enriched", item.CVE.CVEDataMeta.ID) - continue - } - - for _, node := range item.Configurations.Nodes { - removeInvalidCPEs(node) - } - - if enrichedEntry, ok := enrichments[item.CVE.CVEDataMeta.ID]; ok { - // Add the CPE matches instead of removing for backwards compatibility purposes - item.Configurations.Nodes = append(item.Configurations.Nodes, &jsonschema.NVDCVEFeedJSON10DefNode{ - CPEMatch: enrichedEntry.AffectedPackages, - Operator: "OR", - }) - item.LastModifiedDate = enrichedEntry.LastUpdated - } - cves = append(cves, item) - } - - for _, item := range manuallyEnrichedVulns { - cves = append(cves, item) - } - - *cveItems = cves -} - -func removeInvalidCPEs(item *jsonschema.NVDCVEFeedJSON10DefNode) { - cpeMatches := item.CPEMatch[:0] - for _, cpeMatch := range item.CPEMatch { - if cpeMatch.Cpe23Uri == "" { - cpeMatches = append(cpeMatches, cpeMatch) - continue - } - attr, err := wfn.UnbindFmtString(cpeMatch.Cpe23Uri) - if err != nil { - log.Errorf("error parsing %+v", item) - continue - } - if attr.Product == wfn.Any { - log.Warnf("Filtering out CPE: %+v", attr) - continue - } - cpeMatches = append(cpeMatches, cpeMatch) - } - item.CPEMatch = cpeMatches - for _, child := range item.Children { - removeInvalidCPEs(child) - } -} - -func writeFile(path string, feed *jsonschema.NVDCVEFeedJSON10) error { - outF, err := os.Create(path) - if err != nil { - return fmt.Errorf("failed to create file %s: %w", outF.Name(), err) - } - defer utils.IgnoreError(outF.Close) - - if err := json.NewEncoder(outF).Encode(feed); err != nil { - return fmt.Errorf("could not encode JSON for %s: %w", outF.Name(), err) - } - - return nil } diff --git a/pkg/vulnloader/nvdloader/loader_api.go b/pkg/vulnloader/nvdloader/loader_api.go new file mode 100644 index 000000000..4fc032aff --- /dev/null +++ b/pkg/vulnloader/nvdloader/loader_api.go @@ -0,0 +1,262 @@ +package nvdloader + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "time" + + apischema "github.com/facebookincubator/nvdtools/cveapi/nvd/schema" + jsonschema "github.com/facebookincubator/nvdtools/cvefeed/nvd/schema" + "github.com/facebookincubator/nvdtools/wfn" + log "github.com/sirupsen/logrus" + "github.com/stackrox/rox/pkg/httputil/proxy" + "github.com/stackrox/rox/pkg/utils" + "github.com/stackrox/scanner/pkg/vulndump" + "github.com/stackrox/scanner/pkg/vulnloader" +) + +const urlFmt = `https://services.nvd.nist.gov/rest/json/cves/2.0?noRejected&startIndex=%d` + +var client = http.Client{ + Timeout: 5 * time.Minute, + Transport: proxy.RoundTripper(), +} + +var _ vulnloader.Loader = (*apiLoader)(nil) + +type apiLoader struct{} + +// DownloadFeedsToPath downloads the NVD 2.0 API data to the given path. +// If this function is successful, it will fill the directory with +// one JSON file for each year of NVD data. +func (l *apiLoader) DownloadFeedsToPath(outputDir string) error { + log.Info("Downloading NVD data using NVD 2.0 API") + + // Fetch NVD enrichment data from curated repos + enrichments, err := Fetch() + if err != nil { + return fmt.Errorf("could not fetch NVD enrichment sources: %w", err) + } + + nvdDir := filepath.Join(outputDir, vulndump.NVDDirName) + if err := os.MkdirAll(nvdDir, 0755); err != nil { + return fmt.Errorf("creating subdir for %s: %w", vulndump.NVDDirName, err) + } + + var fileNo, totalVulns int + + // Explicitly set startIdx to parallel how this is all done within the loop below. + startIdx := 0 + apiResp, err := query(fmt.Sprintf(urlFmt, startIdx)) + if err != nil { + return err + } + var i int + // Buffer to store vulns until they are written to a file. + cveItems := make([]*jsonschema.NVDCVEFeedJSON10DefCVEItem, 0, 20_000) + for apiResp.ResultsPerPage != 0 { + vulns, err := toJSON10(apiResp.Vulnerabilities) + if err != nil { + return fmt.Errorf("failed to convert API vulns to JSON: %w", err) + } + + if len(vulns) != 0 { + cveItems = append(cveItems, vulns...) + + i++ + // Write to disk every ~20,000 vulnerabilities. + if i == 10 { + i = 0 + + enrichCVEItems(&cveItems, enrichments) + + feed := &jsonschema.NVDCVEFeedJSON10{ + CVEItems: cveItems, + } + if err := writeFile(filepath.Join(nvdDir, fmt.Sprintf("%d.json", fileNo)), feed); err != nil { + return fmt.Errorf("writing to file: %w", err) + } + + fileNo++ + totalVulns += len(cveItems) + log.Infof("Loaded %d NVD vulnerabilities", totalVulns) + // Reduce, reuse, and recycle. + cveItems = cveItems[:0] + } + } + + // Rudimentary rate-limiting. + // NVD limits users without an API key to roughly one call every 6 seconds. + // With an API key, it is roughly one call every 0.6 seconds. + // We'll play it safe and do one call every 3 seconds. + // As of writing there are ~216,000 vulnerabilities, so this whole process should take ~5.4 minutes. + time.Sleep(3 * time.Second) + + startIdx += apiResp.ResultsPerPage + apiResp, err = query(fmt.Sprintf(urlFmt, startIdx)) + if err != nil { + return err + } + } + + // Write the remaining vulnerabilities. + if len(cveItems) != 0 { + enrichCVEItems(&cveItems, enrichments) + + feed := &jsonschema.NVDCVEFeedJSON10{ + CVEItems: cveItems, + } + if err := writeFile(filepath.Join(nvdDir, fmt.Sprintf("%d.json", fileNo)), feed); err != nil { + return fmt.Errorf("writing to file: %w", err) + } + + totalVulns += len(cveItems) + log.Infof("Loaded %d NVD vulnerabilities", totalVulns) + } + + return nil +} + +func query(url string) (*apischema.CVEAPIJSON20, error) { + log.Debugf("Querying %s", url) + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("creating HTTP request: %w", err) + } + req.Header.Set("apiKey", os.Getenv("NVD_API_KEY")) + + apiResp, err := queryWithBackoff(req) + if err != nil { + return nil, err + } + + return apiResp, nil +} + +func queryWithBackoff(req *http.Request) (*apischema.CVEAPIJSON20, error) { + var ( + apiResp *apischema.CVEAPIJSON20 + err error + ) + for i := 1; i <= 5; i++ { + var resp *http.Response + resp, err = tryQuery(req) + if err == nil { + apiResp, err = parseResponse(resp) + if err == nil { + break + } + } + log.Warnf("Failed query attempt %d for %s: %v", i, req.URL.String(), err) + // Wait some multiple of 3 seconds before next attempt. + time.Sleep(time.Duration(3*i) * time.Second) + } + + return apiResp, err +} + +func tryQuery(req *http.Request) (*http.Response, error) { + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("fetching NVD API results: %w", err) + } + + log.Debugf("Queried %s with status code %d", req.URL.String(), resp.StatusCode) + if resp.StatusCode != 200 { + utils.IgnoreError(resp.Body.Close) + return nil, fmt.Errorf("unexpected status code when querying %s: %d", req.URL.String(), resp.StatusCode) + } + + return resp, nil +} + +func parseResponse(resp *http.Response) (*apischema.CVEAPIJSON20, error) { + defer utils.IgnoreError(resp.Body.Close) + + return parseReader(resp.Body) +} + +func parseReader(r io.Reader) (*apischema.CVEAPIJSON20, error) { + apiJSON := new(apischema.CVEAPIJSON20) + if err := json.NewDecoder(r).Decode(apiJSON); err != nil { + return nil, fmt.Errorf("decoding API response: %w", err) + } + + return apiJSON, nil +} + +func enrichCVEItems(cveItems *[]*jsonschema.NVDCVEFeedJSON10DefCVEItem, enrichments map[string]*FileFormatWrapper) { + if cveItems == nil { + return + } + + cves := (*cveItems)[:0] + for _, item := range *cveItems { + if _, ok := manuallyEnrichedVulns[item.CVE.CVEDataMeta.ID]; ok { + log.Warnf("Skipping vuln %s because it is being manually enriched", item.CVE.CVEDataMeta.ID) + continue + } + + for _, node := range item.Configurations.Nodes { + removeInvalidCPEs(node) + } + + if enrichedEntry, ok := enrichments[item.CVE.CVEDataMeta.ID]; ok { + // Add the CPE matches instead of removing for backwards compatibility purposes + item.Configurations.Nodes = append(item.Configurations.Nodes, &jsonschema.NVDCVEFeedJSON10DefNode{ + CPEMatch: enrichedEntry.AffectedPackages, + Operator: "OR", + }) + item.LastModifiedDate = enrichedEntry.LastUpdated + } + cves = append(cves, item) + } + + for _, item := range manuallyEnrichedVulns { + cves = append(cves, item) + } + + *cveItems = cves +} + +func removeInvalidCPEs(item *jsonschema.NVDCVEFeedJSON10DefNode) { + cpeMatches := item.CPEMatch[:0] + for _, cpeMatch := range item.CPEMatch { + if cpeMatch.Cpe23Uri == "" { + cpeMatches = append(cpeMatches, cpeMatch) + continue + } + attr, err := wfn.UnbindFmtString(cpeMatch.Cpe23Uri) + if err != nil { + log.Errorf("error parsing %+v", item) + continue + } + if attr.Product == wfn.Any { + log.Warnf("Filtering out CPE: %+v", attr) + continue + } + cpeMatches = append(cpeMatches, cpeMatch) + } + item.CPEMatch = cpeMatches + for _, child := range item.Children { + removeInvalidCPEs(child) + } +} + +func writeFile(path string, feed *jsonschema.NVDCVEFeedJSON10) error { + outF, err := os.Create(path) + if err != nil { + return fmt.Errorf("failed to create file %s: %w", outF.Name(), err) + } + defer utils.IgnoreError(outF.Close) + + if err := json.NewEncoder(outF).Encode(feed); err != nil { + return fmt.Errorf("could not encode JSON for %s: %w", outF.Name(), err) + } + + return nil +} diff --git a/pkg/vulnloader/nvdloader/loader_feed.go b/pkg/vulnloader/nvdloader/loader_feed.go new file mode 100644 index 000000000..19398c9a1 --- /dev/null +++ b/pkg/vulnloader/nvdloader/loader_feed.go @@ -0,0 +1,83 @@ +package nvdloader + +import ( + "compress/gzip" + "encoding/json" + "fmt" + "os" + "path/filepath" + "time" + + apischema "github.com/facebookincubator/nvdtools/cveapi/nvd/schema" + jsonschema "github.com/facebookincubator/nvdtools/cvefeed/nvd/schema" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "github.com/stackrox/rox/pkg/utils" + "github.com/stackrox/scanner/pkg/vulndump" + "github.com/stackrox/scanner/pkg/vulnloader" +) + +var _ vulnloader.Loader = (*feedLoader)(nil) + +type feedLoader struct{} + +// DownloadFeedsToPath downloads the NVD JSON 2.0 feeds to the given path. +// If this function is successful, it will fill the directory with +// one JSON file for each year of NVD data. +func (l *feedLoader) DownloadFeedsToPath(outputDir string) error { + log.Info("Downloading NVD data using 2.0 Data Feed") + + // Fetch NVD enrichment data from curated repos + enrichments, err := Fetch() + if err != nil { + return errors.Wrap(err, "could not fetch NVD enrichment sources") + } + + nvdDir := filepath.Join(outputDir, vulndump.NVDDirName) + if err := os.MkdirAll(nvdDir, 0755); err != nil { + return errors.Wrapf(err, "creating subdir for %s", vulndump.NVDDirName) + } + endYear := time.Now().Year() + for year := 2002; year <= endYear; year++ { + if err := l.downloadFeedForYear(enrichments, nvdDir, year); err != nil { + return err + } + } + return nil +} + +func (l *feedLoader) downloadFeedForYear(enrichments map[string]*FileFormatWrapper, outputDir string, year int) error { + url := fmt.Sprintf("https://nvd.nist.gov/feeds/json/cve/2.0/nvdcve-2.0-%d.json.gz", year) + resp, err := client.Get(url) + if err != nil { + return errors.Wrapf(err, "failed to download feed for year %d", year) + } + defer utils.IgnoreError(resp.Body.Close) + + // Un-gzip it. + gr, err := gzip.NewReader(resp.Body) + if err != nil { + return errors.Wrapf(err, "couldn't read resp body for year %d", year) + } + + apiFeed := new(apischema.CVEAPIJSON20) + if err := json.NewDecoder(gr).Decode(apiFeed); err != nil { + return errors.Wrapf(err, "decoding feed response") + } + + cveItems, err := toJSON10(apiFeed.Vulnerabilities) + if err != nil { + return fmt.Errorf("failed to convert feed vulns to JSON: %w", err) + } + + enrichCVEItems(&cveItems, enrichments) + + feed := &jsonschema.NVDCVEFeedJSON10{ + CVEItems: cveItems, + } + if err := writeFile(filepath.Join(outputDir, fmt.Sprintf("%d.json", year)), feed); err != nil { + return errors.Wrapf(err, "writing to file") + } + + return nil +} diff --git a/pkg/vulnloader/nvdloader/loader_legacy.go b/pkg/vulnloader/nvdloader/loader_legacy.go deleted file mode 100644 index 4fd0bde49..000000000 --- a/pkg/vulnloader/nvdloader/loader_legacy.go +++ /dev/null @@ -1,105 +0,0 @@ -package nvdloader - -import ( - "compress/gzip" - "encoding/json" - "fmt" - "os" - "path/filepath" - "time" - - "github.com/facebookincubator/nvdtools/cvefeed/nvd/schema" - "github.com/pkg/errors" - log "github.com/sirupsen/logrus" - "github.com/stackrox/rox/pkg/utils" - "github.com/stackrox/scanner/pkg/vulndump" - "github.com/stackrox/scanner/pkg/vulnloader" -) - -var _ vulnloader.Loader = (*legacyLoader)(nil) - -type legacyLoader struct{} - -// DownloadFeedsToPath downloads the NVD feeds to the given path. -// If this function is successful, it will fill the directory with -// one json file for each year of NVD data. -func (l *legacyLoader) DownloadFeedsToPath(outputDir string) error { - log.Info("Downloading NVD data using Legacy Data Feed") - - // Fetch NVD enrichment data from curated repos - enrichmentMap, err := Fetch() - if err != nil { - return errors.Wrap(err, "could not fetch NVD enrichment sources") - } - - nvdDir := filepath.Join(outputDir, vulndump.NVDDirName) - if err := os.MkdirAll(nvdDir, 0755); err != nil { - return errors.Wrapf(err, "creating subdir for %s", vulndump.NVDDirName) - } - endYear := time.Now().Year() - for year := 2002; year <= endYear; year++ { - if err := l.downloadFeedForYear(enrichmentMap, nvdDir, year); err != nil { - return err - } - } - return nil -} - -func (l *legacyLoader) downloadFeedForYear(enrichmentMap map[string]*FileFormatWrapper, outputDir string, year int) error { - url := l.jsonFeedURLForYear(year) - resp, err := client.Get(url) - if err != nil { - return errors.Wrapf(err, "failed to download feed for year %d", year) - } - defer utils.IgnoreError(resp.Body.Close) - // Un-gzip it. - gr, err := gzip.NewReader(resp.Body) - if err != nil { - return errors.Wrapf(err, "couldn't read resp body for year %d", year) - } - - // Strip out tabs and newlines for size savings - dump, err := LoadJSONFileFromReader(gr) - if err != nil { - return errors.Wrapf(err, "could not decode json for year %d", year) - } - - cveItems := dump.CVEItems[:0] - for _, item := range dump.CVEItems { - if _, ok := manuallyEnrichedVulns[item.CVE.CVEDataMeta.ID]; ok { - log.Warnf("Skipping vuln %s because it is being manually enriched", item.CVE.CVEDataMeta.ID) - continue - } - for _, node := range item.Configurations.Nodes { - removeInvalidCPEs(node) - } - if enrichedEntry, ok := enrichmentMap[item.CVE.CVEDataMeta.ID]; ok { - // Add the CPE matches instead of removing for backwards compatibility purposes - item.Configurations.Nodes = append(item.Configurations.Nodes, &schema.NVDCVEFeedJSON10DefNode{ - CPEMatch: enrichedEntry.AffectedPackages, - Operator: "OR", - }) - item.LastModifiedDate = enrichedEntry.LastUpdated - } - cveItems = append(cveItems, item) - } - for _, item := range manuallyEnrichedVulns { - cveItems = append(cveItems, item) - } - dump.CVEItems = cveItems - - outF, err := os.Create(filepath.Join(outputDir, fmt.Sprintf("%d.json", year))) - if err != nil { - return errors.Wrap(err, "failed to create file") - } - defer utils.IgnoreError(outF.Close) - - if err := json.NewEncoder(outF).Encode(&dump); err != nil { - return errors.Wrapf(err, "could not encode json map for year %d", year) - } - return nil -} - -func (l *legacyLoader) jsonFeedURLForYear(year int) string { - return fmt.Sprintf("https://nvd.nist.gov/feeds/json/cve/1.1/nvdcve-1.1-%d.json.gz", year) -} diff --git a/pkg/vulnloader/nvdloader/testdata/nvdcve-2.0-2025.json b/pkg/vulnloader/nvdloader/testdata/nvdcve-2.0-2025.json new file mode 100644 index 000000000..e4451a88b --- /dev/null +++ b/pkg/vulnloader/nvdloader/testdata/nvdcve-2.0-2025.json @@ -0,0 +1,331 @@ +{ + "resultsPerPage" : 4, + "startIndex" : 0, + "totalResults" : 4, + "format" : "NVD_CVE", + "version" : "2.0", + "timestamp" : "2025-08-06T03:00:00.9285242", + "vulnerabilities" : [ { + "cve" : { + "id" : "CVE-2025-0168", + "sourceIdentifier" : "cna@vuldb.com", + "published" : "2025-01-01T14:15:23.590", + "lastModified" : "2025-02-25T21:26:07.113", + "vulnStatus" : "Analyzed", + "cveTags" : [ ], + "descriptions" : [ { + "lang" : "en", + "value" : "A vulnerability classified as critical has been found in code-projects Job Recruitment 1.0. This affects an unknown part of the file /_parse/_feedback_system.php. The manipulation of the argument person leads to sql injection. It is possible to initiate the attack remotely. The exploit has been disclosed to the public and may be used." + }, { + "lang" : "es", + "value" : "Se ha encontrado una vulnerabilidad clasificada como crítica en code-projects Job Recruitment 1.0. Afecta a una parte desconocida del archivo /_parse/_feedback_system.php. La manipulación del argumento person conduce a la inyección SQL. Es posible iniciar el ataque de forma remota. La vulnerabilidad se ha revelado al público y puede utilizarse." + } ], + "metrics" : { + "cvssMetricV40" : [ { + "source" : "cna@vuldb.com", + "type" : "Secondary", + "cvssData" : { + "version" : "4.0", + "vectorString" : "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X", + "baseScore" : 5.3, + "baseSeverity" : "MEDIUM", + "attackVector" : "NETWORK", + "attackComplexity" : "LOW", + "attackRequirements" : "NONE", + "privilegesRequired" : "LOW", + "userInteraction" : "NONE", + "vulnConfidentialityImpact" : "LOW", + "vulnIntegrityImpact" : "LOW", + "vulnAvailabilityImpact" : "LOW", + "subConfidentialityImpact" : "NONE", + "subIntegrityImpact" : "NONE", + "subAvailabilityImpact" : "NONE", + "exploitMaturity" : "NOT_DEFINED", + "confidentialityRequirement" : "NOT_DEFINED", + "integrityRequirement" : "NOT_DEFINED", + "availabilityRequirement" : "NOT_DEFINED", + "modifiedAttackVector" : "NOT_DEFINED", + "modifiedAttackComplexity" : "NOT_DEFINED", + "modifiedAttackRequirements" : "NOT_DEFINED", + "modifiedPrivilegesRequired" : "NOT_DEFINED", + "modifiedUserInteraction" : "NOT_DEFINED", + "modifiedVulnConfidentialityImpact" : "NOT_DEFINED", + "modifiedVulnIntegrityImpact" : "NOT_DEFINED", + "modifiedVulnAvailabilityImpact" : "NOT_DEFINED", + "modifiedSubConfidentialityImpact" : "NOT_DEFINED", + "modifiedSubIntegrityImpact" : "NOT_DEFINED", + "modifiedSubAvailabilityImpact" : "NOT_DEFINED", + "Safety" : "NOT_DEFINED", + "Automatable" : "NOT_DEFINED", + "Recovery" : "NOT_DEFINED", + "valueDensity" : "NOT_DEFINED", + "vulnerabilityResponseEffort" : "NOT_DEFINED", + "providerUrgency" : "NOT_DEFINED" + } + } ], + "cvssMetricV31" : [ { + "source" : "cna@vuldb.com", + "type" : "Secondary", + "cvssData" : { + "version" : "3.1", + "vectorString" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", + "baseScore" : 6.3, + "baseSeverity" : "MEDIUM", + "attackVector" : "NETWORK", + "attackComplexity" : "LOW", + "privilegesRequired" : "LOW", + "userInteraction" : "NONE", + "scope" : "UNCHANGED", + "confidentialityImpact" : "LOW", + "integrityImpact" : "LOW", + "availabilityImpact" : "LOW" + }, + "exploitabilityScore" : 2.8, + "impactScore" : 3.4 + }, { + "source" : "nvd@nist.gov", + "type" : "Primary", + "cvssData" : { + "version" : "3.1", + "vectorString" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "baseScore" : 7.5, + "baseSeverity" : "HIGH", + "attackVector" : "NETWORK", + "attackComplexity" : "LOW", + "privilegesRequired" : "NONE", + "userInteraction" : "NONE", + "scope" : "UNCHANGED", + "confidentialityImpact" : "HIGH", + "integrityImpact" : "NONE", + "availabilityImpact" : "NONE" + }, + "exploitabilityScore" : 3.9, + "impactScore" : 3.6 + } ], + "cvssMetricV2" : [ { + "source" : "cna@vuldb.com", + "type" : "Secondary", + "cvssData" : { + "version" : "2.0", + "vectorString" : "AV:N/AC:L/Au:S/C:P/I:P/A:P", + "baseScore" : 6.5, + "accessVector" : "NETWORK", + "accessComplexity" : "LOW", + "authentication" : "SINGLE", + "confidentialityImpact" : "PARTIAL", + "integrityImpact" : "PARTIAL", + "availabilityImpact" : "PARTIAL" + }, + "baseSeverity" : "MEDIUM", + "exploitabilityScore" : 8.0, + "impactScore" : 6.4, + "acInsufInfo" : false, + "obtainAllPrivilege" : false, + "obtainUserPrivilege" : false, + "obtainOtherPrivilege" : false, + "userInteractionRequired" : false + } ] + }, + "weaknesses" : [ { + "source" : "cna@vuldb.com", + "type" : "Secondary", + "description" : [ { + "lang" : "en", + "value" : "CWE-74" + }, { + "lang" : "en", + "value" : "CWE-89" + } ] + }, { + "source" : "nvd@nist.gov", + "type" : "Primary", + "description" : [ { + "lang" : "en", + "value" : "CWE-89" + } ] + } ], + "configurations" : [ { + "nodes" : [ { + "operator" : "OR", + "negate" : false, + "cpeMatch" : [ { + "vulnerable" : true, + "criteria" : "cpe:2.3:a:anisha:job_recruitment:1.0:*:*:*:*:*:*:*", + "matchCriteriaId" : "56E6381D-BF5F-4DC1-A525-4DEDA44D5C56" + } ] + } ] + } ], + "references" : [ { + "url" : "https://code-projects.org/", + "source" : "cna@vuldb.com", + "tags" : [ "Product" ] + }, { + "url" : "https://github.com/UnrealdDei/cve/blob/main/sql11.md", + "source" : "cna@vuldb.com", + "tags" : [ "Exploit", "Third Party Advisory" ] + }, { + "url" : "https://vuldb.com/?ctiid.289917", + "source" : "cna@vuldb.com", + "tags" : [ "Permissions Required", "VDB Entry" ] + }, { + "url" : "https://vuldb.com/?id.289917", + "source" : "cna@vuldb.com", + "tags" : [ "Third Party Advisory", "VDB Entry" ] + }, { + "url" : "https://vuldb.com/?submit.473107", + "source" : "cna@vuldb.com", + "tags" : [ "Third Party Advisory", "VDB Entry" ] + } ] + } + }, { + "cve" : { + "id" : "CVE-2025-22144", + "sourceIdentifier" : "security-advisories@github.com", + "published" : "2025-01-13T20:15:29.817", + "lastModified" : "2025-05-13T15:42:53.440", + "vulnStatus" : "Analyzed", + "cveTags" : [ ], + "descriptions" : [ { + "lang" : "en", + "value" : "NamelessMC is a free, easy to use & powerful website software for Minecraft servers. A user with admincp.core.emails or admincp.users.edit permissions can validate users and an attacker can reset their password. When the account is successfully approved by email the reset code is NULL, but when the account is manually validated by a user with admincp.core.emails or admincp.users.edit permissions then the reset_code will no longer be NULL but empty. An attacker can request http://localhost/nameless/index.php?route=/forgot_password/&c= and reset the password. As a result an attacker may compromise another users password and take over their account. This issue has been addressed in release version 2.1.3 and all users are advised to upgrade. There are no known workarounds for this vulnerability." + }, { + "lang" : "es", + "value" : "NamelessMC es un software gratuito, fácil de usar y potente para servidores de Minecraft. Un usuario con permisos admincp.core.emails o admincp.users.edit puede validar usuarios y un atacante puede restablecer sus contraseñas. Cuando la cuenta se aprueba correctamente por correo electrónico, el código de restablecimiento es NULL, pero cuando la cuenta es validada manualmente por un usuario con permisos admincp.core.emails o admincp.users.edit, el código de restablecimiento ya no será NULL, sino que estará vacío. Un atacante puede solicitar http://localhost/nameless/index.php?route=/forgot_password/&c= y restablecer la contraseña. Como resultado, un atacante puede comprometer la contraseña de otro usuario y tomar el control de su cuenta. Este problema se ha solucionado en la versión de lanzamiento 2.1.3 y se recomienda a todos los usuarios que la actualicen. No se conocen Workarounds para esta vulnerabilidad." + } ], + "metrics" : { + "cvssMetricV40" : [ { + "source" : "security-advisories@github.com", + "type" : "Secondary", + "cvssData" : { + "version" : "4.0", + "vectorString" : "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:L/VA:N/SC:H/SI:L/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X", + "baseScore" : 9.0, + "baseSeverity" : "CRITICAL", + "attackVector" : "NETWORK", + "attackComplexity" : "LOW", + "attackRequirements" : "PRESENT", + "privilegesRequired" : "NONE", + "userInteraction" : "NONE", + "vulnConfidentialityImpact" : "HIGH", + "vulnIntegrityImpact" : "LOW", + "vulnAvailabilityImpact" : "NONE", + "subConfidentialityImpact" : "HIGH", + "subIntegrityImpact" : "LOW", + "subAvailabilityImpact" : "NONE", + "exploitMaturity" : "NOT_DEFINED", + "confidentialityRequirement" : "NOT_DEFINED", + "integrityRequirement" : "NOT_DEFINED", + "availabilityRequirement" : "NOT_DEFINED", + "modifiedAttackVector" : "NOT_DEFINED", + "modifiedAttackComplexity" : "NOT_DEFINED", + "modifiedAttackRequirements" : "NOT_DEFINED", + "modifiedPrivilegesRequired" : "NOT_DEFINED", + "modifiedUserInteraction" : "NOT_DEFINED", + "modifiedVulnConfidentialityImpact" : "NOT_DEFINED", + "modifiedVulnIntegrityImpact" : "NOT_DEFINED", + "modifiedVulnAvailabilityImpact" : "NOT_DEFINED", + "modifiedSubConfidentialityImpact" : "NOT_DEFINED", + "modifiedSubIntegrityImpact" : "NOT_DEFINED", + "modifiedSubAvailabilityImpact" : "NOT_DEFINED", + "Safety" : "NOT_DEFINED", + "Automatable" : "NOT_DEFINED", + "Recovery" : "NOT_DEFINED", + "valueDensity" : "NOT_DEFINED", + "vulnerabilityResponseEffort" : "NOT_DEFINED", + "providerUrgency" : "NOT_DEFINED" + } + } ], + "cvssMetricV31" : [ { + "source" : "nvd@nist.gov", + "type" : "Primary", + "cvssData" : { + "version" : "3.1", + "vectorString" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "baseScore" : 9.8, + "baseSeverity" : "CRITICAL", + "attackVector" : "NETWORK", + "attackComplexity" : "LOW", + "privilegesRequired" : "NONE", + "userInteraction" : "NONE", + "scope" : "UNCHANGED", + "confidentialityImpact" : "HIGH", + "integrityImpact" : "HIGH", + "availabilityImpact" : "HIGH" + }, + "exploitabilityScore" : 3.9, + "impactScore" : 5.9 + } ] + }, + "weaknesses" : [ { + "source" : "security-advisories@github.com", + "type" : "Secondary", + "description" : [ { + "lang" : "en", + "value" : "CWE-610" + } ] + }, { + "source" : "nvd@nist.gov", + "type" : "Primary", + "description" : [ { + "lang" : "en", + "value" : "CWE-640" + } ] + } ], + "configurations" : [ { + "nodes" : [ { + "operator" : "OR", + "negate" : false, + "cpeMatch" : [ { + "vulnerable" : true, + "criteria" : "cpe:2.3:a:namelessmc:nameless:*:*:*:*:*:*:*:*", + "versionEndExcluding" : "2.1.3", + "matchCriteriaId" : "4402A4C5-43C3-4436-B207-1374812080DD" + } ] + } ] + } ], + "references" : [ { + "url" : "https://github.com/NamelessMC/Nameless/releases/tag/v2.1.3", + "source" : "security-advisories@github.com", + "tags" : [ "Release Notes" ] + }, { + "url" : "https://github.com/NamelessMC/Nameless/security/advisories/GHSA-p883-7496-x35p", + "source" : "security-advisories@github.com", + "tags" : [ "Exploit", "Vendor Advisory" ] + }, { + "url" : "https://github.com/NamelessMC/Nameless/security/advisories/GHSA-p883-7496-x35p", + "source" : "134c704f-9b21-4f2e-91b3-4a467353bcc0", + "tags" : [ "Exploit", "Vendor Advisory" ] + } ] + } + }, { + "cve" : { + "id" : "CVE-2025-23124", + "sourceIdentifier" : "416baaa9-dc9f-4396-8d5f-8c081fb06d67", + "published" : "2025-01-11T15:15:08.930", + "lastModified" : "2025-01-11T15:15:08.930", + "vulnStatus" : "Rejected", + "cveTags" : [ ], + "descriptions" : [ { + "lang" : "en", + "value" : "Rejected reason: This CVE ID has been rejected or withdrawn by its CVE Numbering Authority." + } ], + "metrics" : { }, + "references" : [ ] + } + }, { + "cve" : { + "id" : "CVE-2025-23125", + "sourceIdentifier" : "416baaa9-dc9f-4396-8d5f-8c081fb06d67", + "published" : "2025-01-11T15:15:09.023", + "lastModified" : "2025-01-11T15:15:09.023", + "vulnStatus" : "Rejected", + "cveTags" : [ ], + "descriptions" : [ { + "lang" : "en", + "value" : "Rejected reason: This CVE ID has been rejected or withdrawn by its CVE Numbering Authority." + } ], + "metrics" : { }, + "references" : [ ] + } + } ] +}