If you are reading this blog post via a 3rd party source it is very likely that many parts of it will not render correctly (usually, the interactive graphs). Please view the post on dogesec.com for the full interactive viewing experience.

tl;dr

The NVD are still struggling to keep up with the backlog of CVEs to be analysed. With 26,876 added since February, it is no surprise.

Key Findings

  • 26,876 CVEs were added to the NVD database between 1st February 2024 and 30th September 2024.
  • 17,375 (64.6%) are yet to be fully analysed.
  • 9,501 (35.4%) have been analysed in some way, including being rejected.
  • 23,109 (86.3%) have a base score greater than 5.0, whilst 3,086 (11.5%) have a CVSS base score greater than 9.
  • The Linux team have by far the largest number of CVEs submitted with no CVSS score (2,108).
  • 23 of the analysed vulnerabilities (7,686) have an EPSS score greater than 9.0.
  • 70 CVEs in this period are known to be exploited – all of which have been analysed by the NVD.

Why I decided to write this post

If you rely on NIST’s National Vulnerability Database analysis on CVEs for your vulnerability prioritisation efforts, you’re likely aware the they began slowing down their analysis of new vulnerabilities in February 2024.

Since then, the NVD has set themselves a target of clearing the backlog of CVEs yet to be analysed by the end of their fiscal year, which was September 30, 2024.

Now we’re into October, I decided to take a look to see how they’re doing whilst demonstrating the power of stix2arango and cve2stix

An important note before I begin

This post is not designed to take a shot a NIST. Like most government departments, they’re chronically underfunded. This post is designed to show we need a more sustainable way to manage the vulnerability ecosystem.

Follow along

For this post I am going to use the STIX Objects created by cve2stix over this period.

If you would like to follow along, you can download the same dataset I will be using with the help of stix2arango.

Once you have installed stix2arango, all you need to run is;

python3 utilities/arango_cve_processor/insert_archive_cve.py \
  --database blog_demo

This command will import all CVEs published by the NVD into the graph database (including those since this post was written), ready for querying in the ArangoDB web interface (usually http://127.0.0.1:8529/).

Analysis

CVEs covered in this period

Warming up, I can define our base search to only include vulnerabilities published after 2024-02-01, but before 2024-09-30.

Here are some searches that will help you get familiar with the Arango query language, if you’re not already.

Total count for vulnerabilities in this period

RETURN LENGTH(
  FOR doc IN nvd_cve_vertex_collection
    FILTER doc.type == "vulnerability"
    FILTER DATE_TIMESTAMP(doc.created) >= DATE_TIMESTAMP("2024-02-01T00:00:00Z")
    FILTER DATE_TIMESTAMP(doc.created) <= DATE_TIMESTAMP("2024-09-30T00:00:00Z")
    RETURN doc
)
[
  26876
]
FOR doc IN nvd_cve_vertex_collection
  FILTER doc.type == "vulnerability"
  FILTER DATE_TIMESTAMP(doc.created) >= DATE_TIMESTAMP("2024-02-01T00:00:00Z")
  FILTER DATE_TIMESTAMP(doc.created) <= DATE_TIMESTAMP("2024-09-30T00:00:00Z")
  LET keys = ATTRIBUTES(doc)
  LET filteredKeys = keys[* FILTER !STARTS_WITH(CURRENT, "_")]
    RETURN [KEEP(doc, filteredKeys)]

Here is an example result:

    {
      "created": "2024-02-02T02:15:17.277Z",
      "created_by_ref": "identity--562918ee-d5da-5579-b6a1-fae50cc6bad3",
      "description": "IBM PowerSC 1.3, 2.0, and 2.1 could allow a remote attacker to hijack the clicking action of the victim. By persuading a victim to visit a malicious Web site, a remote attacker could exploit this vulnerability to hijack the victim's click actions and possibly launch further attacks against the victim.  IBM X-Force ID:  275128.\n\n",
      "extensions": {
        "extension-definition--2c5c13af-ee92-5246-9ba7-0b958f8cd34a": {
          "extension_type": "toplevel-property-extension"
        }
      },
      "external_references": [
        {
          "source_name": "cve",
          "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-50938",
          "external_id": "CVE-2023-50938"
        },
        {
          "source_name": "cwe",
          "url": "https://cwe.mitre.org/data/definitions/CWE-451.html",
          "external_id": "CWE-451"
        },
        {
          "source_name": "[email protected]",
          "description": "VDB Entry,Vendor Advisory",
          "url": "https://exchange.xforce.ibmcloud.com/vulnerabilities/275128"
        },
        {
          "source_name": "[email protected]",
          "description": "Patch,Vendor Advisory",
          "url": "https://www.ibm.com/support/pages/node/7113759"
        },
        {
          "source_name": "vulnStatus",
          "description": "Analyzed"
        },
        {
          "source_name": "sourceIdentifier",
          "description": "[email protected]"
        }
      ],
      "id": "vulnerability--a555db5f-c28c-5709-966a-5a1fd89e27af",
      "modified": "2024-02-02T15:12:44.273Z",
      "name": "CVE-2023-50938",
      "object_marking_refs": [
        "marking-definition--94868c89-83c2-464b-929b-a1a8aa3c8487",
        "marking-definition--562918ee-d5da-5579-b6a1-fae50cc6bad3"
      ],
      "spec_version": "2.1",
      "type": "vulnerability",
      "x_cvss": {
        "v3_1": {
          "base_score": 4.3,
          "base_severity": "MEDIUM",
          "exploitability_score": 2.8,
          "impact_score": 1.4,
          "source": "[email protected]",
          "type": "Primary",
          "vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N"
        }
      }
    }

List CVEs covered in this period

FOR doc IN nvd_cve_vertex_collection
    FILTER doc.type == "vulnerability"
    FILTER DATE_TIMESTAMP(doc.created) >= DATE_TIMESTAMP("2024-02-01T00:00:00Z")
    FILTER DATE_TIMESTAMP(doc.created) <= DATE_TIMESTAMP("2024-09-30T00:00:00Z")
    FOR ext_ref IN doc.external_references
        FILTER ext_ref.source_name == "cve"
        RETURN ext_ref.external_id

Here is a snippet of the results;

[
  "CVE-2024-0704",
  "CVE-2023-50938",
  "CVE-2024-25001",
  "CVE-2023-50934",
  "CVE-2023-50328",
  "CVE-2023-50936",
  "CVE-2024-23034",
  "CVE-2023-50327",

Vulnerabilities by analysis state

I decided to start by looking at the analysis state of all these vulnerabilities;

FOR doc IN nvd_cve_vertex_collection
  FILTER doc.type == "vulnerability"
  FILTER DATE_TIMESTAMP(doc.created) >= DATE_TIMESTAMP("2024-02-01T00:00:00Z")
  FILTER DATE_TIMESTAMP(doc.created) <= DATE_TIMESTAMP("2024-09-30T00:00:00Z")
  // Filter documents with source_name == 'vulnStatus' in the external_references array
  FOR ext_ref IN doc.external_references
    FILTER ext_ref.source_name == "vulnStatus"
    COLLECT description = ext_ref.description INTO group
    // Calculate count for each description
    LET count = LENGTH(group)
    SORT count DESC
    // Collect results into an intermediate array
    RETURN {
      description: description,
      count: count
    }
[
  {
    "description": "Awaiting Analysis",
    "count": 17375
  },
  {
    "description": "Analyzed",
    "count": 7686
  },
  {
    "description": "Modified",
    "count": 1105
  },
  {
    "description": "Rejected",
    "count": 561
  },
  {
    "description": "Undergoing Analysis",
    "count": 149
  }
]

Vulnerabilities by analysis state

Of the 26,876 new vulnerabilities added to the database, 17,375 (64.6%) have yet to be fully analysed. 9,501 (35.4%) have been analysed in some form, including being rejected.

Vulnerabilities by CVSS Score

Even if a vulnerability is in Awaiting Analysis state, it may still have a CVSS score assigned by the CNA who submitted it.

So how many vulnerabilities submitted since February have at least one CVSS assigned? In this search I will ignore those with vulnStatus = Rejected.

RETURN LENGTH(
  FOR doc IN nvd_cve_vertex_collection
      FILTER doc.type == "vulnerability"
      FILTER DATE_TIMESTAMP(doc.created) >= DATE_TIMESTAMP("2024-02-01T00:00:00Z")
      FILTER DATE_TIMESTAMP(doc.created) <= DATE_TIMESTAMP("2024-09-30T00:00:00Z")
      AND doc.x_cvss != "{}"
      AND NOT (
          LENGTH(
              FOR ref IN doc.external_references
                  FILTER ref.source_name == "vulnStatus"
                  AND ref.description == "Rejected"
                  RETURN ref
          ) > 0
      )
      RETURN doc
)
[
  26315
]

Of the 26,876 new vulnerabilities, 26,315 have had CVSS scores assigned by the submitting CNA.

And what CVSS versions are they using to score the vulnerabilities;

LET filtered_docs = (
    FOR doc IN nvd_cve_vertex_collection
        FILTER doc.type == "vulnerability"
        FILTER DATE_TIMESTAMP(doc.created) >= DATE_TIMESTAMP("2024-02-01T00:00:00Z")
        FILTER DATE_TIMESTAMP(doc.created) <= DATE_TIMESTAMP("2024-09-30T00:00:00Z")
        AND NOT (
            LENGTH(
                FOR ref IN doc.external_references
                    FILTER ref.source_name == "vulnStatus"
                    AND ref.description == "Rejected"
                    RETURN ref
            ) > 0
        )
        RETURN doc
)

LET empty_cvss_count = LENGTH(
    FOR doc IN filtered_docs
    FILTER doc.x_cvss == {}
    RETURN doc
)

LET grouped_cvss = (
    FOR doc IN filtered_docs
        // Iterate over the keys of the x_cvss object to extract each cvss version
        FILTER doc.x_cvss != {} // Filter out empty x_cvss
        FOR cvssKey IN ATTRIBUTES(doc.x_cvss)
        COLLECT key = cvssKey INTO group
        RETURN {
            cvss_version: key,
            count: LENGTH(group)
        }
)

// Add a row for the count of empty x_cvss
LET final_result = APPEND(grouped_cvss, [
    { cvss_version: "empty_x_cvss", count: empty_cvss_count }
])

// Sort by the count column in descending order
FOR entry IN final_result
    SORT entry.count DESC
    RETURN entry
[
  {
    "cvss_version": "v3_1",
    "count": 21680
  },
  {
    "cvss_version": "empty_x_cvss",
    "count": 3291
  },
  {
    "cvss_version": "v4_0",
    "count": 1896
  },
  {
    "cvss_version": "v2_0",
    "count": 1860
  },
  {
    "cvss_version": "v3_0",
    "count": 1373
  }
]

Vulnerabilities by CVSS score

Most are using v3.1, which is to be expected given v4.0 is relatively new. It’ll be interesting to see how this changes over the next year.

The fact that almost all CVEs already have a CVSS score, even without an NVD analysis, it is still possible to understand their severity and thus help with prioritisation.

Looking at the vulnerabilities by their CVSS base score over time;

LET filtered_docs = (
    FOR doc IN nvd_cve_vertex_collection
        FILTER doc.type == "vulnerability"
        FILTER DATE_TIMESTAMP(doc.created) >= DATE_TIMESTAMP("2024-02-01T00:00:00Z")
        FILTER DATE_TIMESTAMP(doc.created) <= DATE_TIMESTAMP("2024-09-30T00:00:00Z")
        AND NOT (
            LENGTH(
                FOR ref IN doc.external_references
                    FILTER ref.source_name == "vulnStatus"
                    AND ref.description == "Rejected"
                    RETURN ref
            ) > 0
        )
        RETURN doc
)

LET base_score_groups = (
    FOR doc IN filtered_docs
        // Iterate over each cvss version
        FOR cvss IN VALUES(doc.x_cvss)
        LET base_score = cvss.base_score
        FILTER base_score != null
        // Classify the base_score into different ranges using ternary operator
        LET range = 
            base_score >= 0 AND base_score < 1 ? "0-1" :
            base_score >= 1 AND base_score < 2 ? "1-2" :
            base_score >= 2 AND base_score < 3 ? "2-3" :
            base_score >= 3 AND base_score < 4 ? "3-4" :
            base_score >= 4 AND base_score < 5 ? "4-5" :
            base_score >= 5 AND base_score < 6 ? "5-6" :
            base_score >= 6 AND base_score < 7 ? "6-7" :
            base_score >= 7 AND base_score < 8 ? "7-8" :
            base_score >= 8 AND base_score < 9 ? "8-9" :
            base_score >= 9 AND base_score <= 10 ? "9-10" : "unknown"
        COLLECT score_range = range INTO group
        RETURN {
            score_range: score_range,
            count: LENGTH(group)
        }
)

// Sort by the score range
FOR entry IN base_score_groups
    SORT entry.score_range
    RETURN entry
[
  {
    "score_range": "0-1",
    "count": 1
  },
  {
    "score_range": "1-2",
    "count": 44
  },
  {
    "score_range": "2-3",
    "count": 242
  },
  {
    "score_range": "3-4",
    "count": 770
  },
  {
    "score_range": "4-5",
    "count": 2627
  },
  {
    "score_range": "5-6",
    "count": 5524
  },
  {
    "score_range": "6-7",
    "count": 5314
  },
  {
    "score_range": "7-8",
    "count": 5528
  },
  {
    "score_range": "8-9",
    "count": 3657
  },
  {
    "score_range": "9-10",
    "count": 3086
  }
]

Vulnerabilities by CVSS score

The majority of CVEs (23,109, 86.3%) have a base score greater than 5.0 (just 3,684 have a base score less than 5 – about the same number that have a base score of 9 and above (3,086, 11.5%)).

Vulnerabilities with no CVSS score by CNA

To understand which CNAs have the most outstanding CVEs without a CVSS score;

// First, collect the sourceIdentifier descriptions from the vulnerability documents
LET source_identifiers = (
  FOR doc IN nvd_cve_vertex_collection
    FILTER doc.type == "vulnerability"
    FILTER DATE_TIMESTAMP(doc.created) >= DATE_TIMESTAMP("2024-02-01T00:00:00Z")
    FILTER DATE_TIMESTAMP(doc.created) <= DATE_TIMESTAMP("2024-09-30T00:00:00Z")
    AND doc.x_cvss == {}
    AND NOT (
        LENGTH(
            FOR ref IN doc.external_references
                FILTER ref.source_name == "vulnStatus"
                AND ref.description == "Rejected"
                RETURN ref
        ) > 0
    )
    // Collect the sourceIdentifier description
    FOR ref IN doc.external_references
      FILTER ref.source_name == "sourceIdentifier"
      RETURN {
        value: ref.description
      }
)

// Now group by the description value, count occurrences, and sort by count
FOR identifier IN source_identifiers
  COLLECT value = identifier.value INTO group
  // Sort by count in descending order
  LET count = LENGTH(group)
  SORT count DESC
  RETURN {
    value: value,
    count: count
  }
CNA Count of vulnerabilities with no CVSS
416baaa9-dc9f-4396-8d5f-8c081fb06d67 (Linux) 2108
[email protected] 570
[email protected] 122
[email protected] 65
[email protected] 52
[email protected] 42
[email protected] 39
[email protected] 38
[email protected] 37
[email protected] 28

Two CNAs stand out: Linux (416baaa9-dc9f-4396-8d5f-8c081fb06d67) (2,108), which has overwhelmed the CVE program with a high volume of vulnerabilities this year, and MITRE (570), the Root CNA responsible for managing the CVE program.

Vulnerabilities by EPSS Score

Another popular scoring mechanism for vulnerabilities is EPSS.

EPPS estimates the likelihood of a software vulnerability being exploited.

It is important to note, the EPSS for a CVE changes over time (older vulnerabilities are generally less likely to be exploited). The scores shown here are correct for today.

Using the list of vulnerabilities published in this period, it is possible to also get their EPSS scores as follows;

// Step 1: Collect all vulnerability IDs from the first search
LET vulnerability_ids = (
  FOR doc IN nvd_cve_vertex_collection
    FILTER doc.type == "vulnerability"
    FILTER DATE_TIMESTAMP(doc.created) >= DATE_TIMESTAMP("2024-02-01T00:00:00Z")
    FILTER DATE_TIMESTAMP(doc.created) <= DATE_TIMESTAMP("2024-09-30T00:00:00Z")
    RETURN doc.id
)

// Step 2: Find all note objects that reference these vulnerability IDs
LET epss_data = (
  FOR note IN nvd_cve_vertex_collection
    FILTER note.type == "note"
    // Check if any of the object_refs in the note matches the vulnerability IDs
    FILTER LENGTH(
      FOR ref IN note.object_refs
        FILTER ref IN vulnerability_ids
        RETURN ref
    ) > 0
    // Ensure the note has x_epss data
    FILTER LENGTH(note.x_epss) > 0
    // Collect the epss data
    FOR epss_entry IN note.x_epss
      RETURN TO_NUMBER(epss_entry.epss)
)

// Step 3: Group the epss scores into buckets and count how many fall in each range
FOR epss_value IN epss_data
  LET bucket = 
    epss_value >= 0 AND epss_value < 0.1 ? "0.0-0.1" :
    epss_value >= 0.1 AND epss_value < 0.2 ? "0.1-0.2" :
    epss_value >= 0.2 AND epss_value < 0.3 ? "0.2-0.3" :
    epss_value >= 0.3 AND epss_value < 0.4 ? "0.3-0.4" :
    epss_value >= 0.4 AND epss_value < 0.5 ? "0.4-0.5" :
    epss_value >= 0.5 AND epss_value < 0.6 ? "0.5-0.6" :
    epss_value >= 0.6 AND epss_value < 0.7 ? "0.6-0.7" :
    epss_value >= 0.7 AND epss_value < 0.8 ? "0.7-0.8" :
    epss_value >= 0.8 AND epss_value < 0.9 ? "0.8-0.9" :
    epss_value >= 0.9 AND epss_value <= 1.0 ? "0.9-1.0" : "unknown"

  // Group by bucket and count occurrences
  COLLECT epss_range = bucket WITH COUNT INTO range_count
  SORT epss_range
  RETURN {
    epss_range: epss_range,
    count: range_count
  }
EPPS Range Count of CVEs
0.0-0.1 25947
0.1-0.2 11
0.2-0.3 5
0.3-0.4 5
0.5-0.6 2
0.6-0.7 3
0.7-0.8 3
0.8-0.9 5
0.9-1.0 23

You can see most have a score lower than 0.1 (25,947). All CVEs yet to be analysed (17,375) fall into this bucket. Even so, this highlights the fact most CVEs are not likely to be exploited.

There are however, 23 with a EPSS score above 9.0, which indicated the CVE is almost certain to be exploited.

If your interested what CVEs these are…

// Step 1: Collect all vulnerability IDs from the first search
LET vulnerability_ids = (
  FOR doc IN nvd_cve_vertex_collection
    FILTER doc.type == "vulnerability"
    FILTER DATE_TIMESTAMP(doc.created) >= DATE_TIMESTAMP("2024-02-01T00:00:00Z")
    FILTER DATE_TIMESTAMP(doc.created) <= DATE_TIMESTAMP("2024-09-30T00:00:00Z")
    RETURN doc.id
)

// Step 2: Find all note objects that reference these vulnerability IDs and have EPSS scores greater than 0.9
FOR note IN nvd_cve_vertex_collection
  FILTER note.type == "note"
  // Check if any of the object_refs in the note matches the vulnerability IDs
  FILTER LENGTH(
    FOR ref IN note.object_refs
      FILTER ref IN vulnerability_ids
      RETURN ref
  ) > 0
  // Ensure the note has x_epss data
  FILTER LENGTH(note.x_epss) > 0
  // Return CVE IDs and EPSS scores where EPSS score is greater than 0.9
  FOR epss_entry IN note.x_epss
    FILTER TO_NUMBER(epss_entry.epss) > 0.9
    SORT epss_entry.epss DESC
    RETURN {
      cve: epss_entry.cve,
      epss: epss_entry.epss
    }
CVE EPSS Score
CVE-2024-7593 0.97325
CVE-2024-34102 0.97284
CVE-2024-27198 0.96937
CVE-2024-4879 0.96556
CVE-2024-4040 0.96499
CVE-2024-3400 0.96471
CVE-2024-27348 0.96344
CVE-2024-4577 0.9632
CVE-2024-28995 0.96178
CVE-2024-5217 0.9616
CVE-2024-36401 0.9588
CVE-2024-23692 0.95648
CVE-2024-21683 0.94696
CVE-2024-1709 0.94435
CVE-2024-28987 0.94156
CVE-2024-24919 0.94034
CVE-2024-38856 0.93538
CVE-2024-6893 0.93338
CVE-2024-3273 0.93241
CVE-2024-29972 0.93
CVE-2024-29973 0.93
CVE-2024-4358 0.9275
CVE-2024-6670 0.90985

Vulnerabilities by Known Exploits

CISA KEV report vulnerabilities known to be exploited in the wild.

I wanted to see the analysis state of all CVEs actively being exploited;

// First, collect the list of external_id values and descriptions from sightings
LET cve_sightings = (
  FOR doc IN nvd_cve_vertex_collection
    FILTER doc.type == "sighting"
    FOR ext_ref IN doc.external_references
      FILTER ext_ref.source_name == "cve"
      RETURN {
        cve_id: ext_ref.external_id,
        sighting_description: doc.description
      }
)

// Then, search for vulnerabilities with these external_id values and return both CVE ID, vulnStatus, and sighting description
FOR sighting IN cve_sightings
  FOR doc IN nvd_cve_vertex_collection
    FILTER doc.type == "vulnerability"
    FILTER DATE_TIMESTAMP(doc.created) >= DATE_TIMESTAMP("2024-02-01T00:00:00Z")
    FILTER DATE_TIMESTAMP(doc.created) <= DATE_TIMESTAMP("2024-09-30T00:00:00Z")
    FOR ext_ref IN doc.external_references
      FILTER ext_ref.external_id == sighting.cve_id
      // Extract the vulnStatus and CVE ID, and include sighting description
      FOR ext_ref2 IN doc.external_references
        FILTER ext_ref2.source_name == "vulnStatus"
        SORT doc.created DESC
        RETURN {
          cve_id: ext_ref.external_id,
          vuln_status: ext_ref2.description,
          created: doc.created,
          sighting_description: sighting.sighting_description
        }

[
  {
    "cve_id": "CVE-2024-8963",
    "vuln_status": "Analyzed",
    "created": "2024-09-19T18:15:10.600Z",
    "sighting_description": "CISA KEV: Ivanti Cloud Services Appliance (CSA) Path Traversal Vulnerability\n\n As Ivanti CSA has reached End-of-Life status, users are urged to remove CSA 4.6.x from service or upgrade to the 5.0.x line of supported solutions, as future vulnerabilities on the 4.6.x version of CSA are unlikely to receive security updates.\n\n Action due by: 2024-10-10"
  },
  {
    "cve_id": "CVE-2024-8190",
    "vuln_status": "Analyzed",
    "created": "2024-09-10T21:15:14.697Z",
    "sighting_description": "CISA KEV: Ivanti Cloud Services Appliance OS Command Injection Vulnerability\n\n As Ivanti CSA has reached End-of-Life status, users are urged to remove CSA 4.6.x from service or upgrade to the 5.0.x line of supported solutions, as future vulnerabilities on the 4.6.x version of CSA are unlikely to receive future security updates.\n\n Action due by: 2024-10-04"
  },
  {
    "cve_id": "CVE-2024-43461",
    "vuln_status": "Analyzed",
    "created": "2024-09-10T17:15:33.410Z",
    "sighting_description": "CISA KEV: Microsoft Windows MSHTML Platform Spoofing Vulnerability\n\n Apply mitigations per vendor instructions or discontinue use of the product if mitigations are unavailable.\n\n Action due by: 2024-10-07"
  },

Vulnerabilities by KEV

The good news, all 70 of them have been analysed!


Vulmatch

Straightforward vulnerability management. Know when software you use is vulnerable, how it is being exploited, and how to detect an attack.

Vulmatch. Straightforward vulnerability management.

Discuss this post

Head on over to the DOGESEC community to discuss this post.

DOGESEC community

Posted by:

David Greenwood

David Greenwood, Do Only Good Everyday



Never miss an update


Sign up to receive new articles in your inbox as they published.

Your subscription could not be saved. Please try again.
Your subscription has been successful.