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. Please view the post on signalscorps.com for the full interactive viewing experience.
In this post I will show you how to use STIX 2.1 SDOs, SCOs, and SROs to standardised the way vulnerabilities are represented.
Recapping what I have covered in the previous two posts, cve2stix considers;
- NVD CPE records
- NVD CVE records (including CPE configurations)
One of the main design elements of the cve2stix design was the ability to integrate data with other products. STIX 2.1 was what I settled on, becoming an increasingly popular standard in the world of cyber threat intelligence.
I will not cover STIX 2.1 concepts in the post. If you are new to STIX, check out my tutorial on the subject from last year.
CVEs as STIX 2.1 Objects
Vulnerability SDOs
STIX 2.1 contains a Vulnerability SDO, here is the specification for it. In short, it is designed for modelling vulnerabilities, so I will use it for just that.
Using the response from the CVE API (see the schema) I can map the data to the STIX 2.1 Vulnerability SDO;
{
"type": "vulnerability",
"spec_version": "2.1",
"id": "vulnerability--<GENERATED BY STIX2 LIBRARY>",
"created_by_ref": "identity--<CVE2STIX IDENTITY ID>",
"created": "<vulnerabilities.cve.published>",
"modified": "<vulnerabilities.cve.lastModified>",
"name": "CVE: <vulnerabilities.cve.id>",
"description": "<vulnerabilities.cve.descriptions.description_data.value> (if multiple, where lan = en, else first result)",
"external_references": [
{
"source_name": "cve",
"external_id": "<vulnerabilities.cve.id>",
"url": "https://nvd.nist.gov/vuln/detail/<vulnerabilities.cve.id>"
},
{
"source_name": "cwe",
"external_id": "<vulnerabilities.cve.weaknesses.description.value[n]>",
"url": "https://cwe.mitre.org/data/definitions/<vulnerabilities.cve.weaknesses.description.value[n]>.html"
},
{
"source_name": "<vulnerabilities.cve.references.source.[n]>",
"url": "<vulnerabilities.cve.references.url.[n]>",
"description": "[<TAG 1>, <TAG N>]"
},
{
"source_name": "cve2stix",
"external_id": "<vulnerability.id>",
"url": "https://<HOST>/cve/<CVE ID>"
}
],
"revoked": "TRUE IF vulnStatus IS REVOKED",
"object_marking_refs": [
"marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9"
],
"labels": [
"cvssv3ExploitabilityScore: <vulnerabilities.cve.metrics.cvssMetricV31.exploitabilityScore>",
"cvssv3ImpactScore: <vulnerabilities.cve.metrics.cvssMetricV31.impactScore>"
]
}
The CVE metadata and references in the response are mapped directly to the default properties of the STIX 2.1 Vulnerability Object.
Sometime CVEs are revoked for a variety of reasons. When a CVE is revoked, the vulnStatus
becomes Revoked
. In which case a revoked
property is included in the Vulnerability SDO with its value set to true
.
The CVE Object (which defines what CPE is actually vulnerable can be thought of like a description of the CVE, cve2stix also uses the STIX 2.1 Indicator SDO to provide a logical pattern to describe the products (and configorations) the CVE affects.
Indicator SDOs
STIX 2.1 Indicator Objects contain STIX Patterns that can be used to describe the CPE configuration logic defined in the CVE.
The STIX 2.1 Specification contains a Software SCO that can be used to construct these patterns, here is the specification for it.
The problem here is CPEs cover applications (software), operating systems (also software), and hardware (not software).
As a trade-off to ensure consistency I decided to model all CPEs as Software SCOs regardless of whether they are software or not.
The pattern
object is always constructed from STIX Software SCOs CPE property (software.cpe
).
For example, if the CVE contained a simple node configuration with the following CPE URI cpe:2.3:o:tesla:model_3_firmware:-:*:*:*:*:*:*:*
the pattern would read;
"pattern": "[software.cpe = 'cpe:2.3:o:tesla:model_3_firmware:-:*:*:*:*:*:*:*']",
The logic to create the pattern is based on the node configurations inside the CVE (the operators used AND
, OR
, and parenthesis). This was explained in part 2.
Here is the structure of the Indicator SDO and how cve2stix populates it;
{
"type": "indicator",
"spec_version": "2.1",
"id": "indicator--<GENERATED BY STIX2 LIBRARY>",
"created_by_ref": "identity--<CVE2STIX IDENTITY ID>",
"created": "<vulnerabilities.cve.published>",
"modified": "<vulnerabilities.cve.lastModifiedDate>",
"indicator_types": ["compromised"],
"name": "<vulnerability.id>",
"description": "vulnerabilities.cve.description.description_data.value> (if multiple, where lan = en, else first result)",
"pattern": "(<CPE PATTERN [1]>) OR (<CPE PATTERN [N]>)",
"valid_from": "<vulnerabilities.cve.publishedDate>",
"object_marking_refs": [
"marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9"
],
"external_references": [
{
"source_name": "cve2stix",
"external_id": "<vulnerability.id>",
"url": "https://<HOST>/cve/<CVE ID>"
}
],
"extensions": {
"extension-definition--b2b5f2cd-49e6-4091-a0e0-c0bb71543e23": {
"extension_type": "property-extension",
"cve": {
"<ENTIRE NVD API PAYLOAD FOR CVE RECORD>"
},
"cveChanges": {
"<ENTIRE NVD API PAYLOAD FOR CVE CHANGE HISTORY>"
}
}
}
}
In the case of multiple CPEs present in the match pattern, the name
of the Indicator SDO considers all CPEs joined with the same operator as the node.
To ensure no data loss, a STIX Indicator Object the entire NVD API payload (from CVE and change history endpoint) is captured in a custom STIX 2.1 extension (extension-definition--b2b5f2cd-49e6-4091-a0e0-c0bb71543e23
).
The extensions definitions configurations
object is the critical part of the CVE for matching to the CVE to affected software, however, the Vulnerability STIX 2.1 Object created, nor the Pattern inside the Indicator SDO (described shortly) actually describe which of the products that match a patterm are actually vulnerable.
Note, a CVE can have zero or more match patterns. In cve2stix a Vulnerability SDO only ever has one Indicator SDO linked to it. In this case each match pattern is joined using an OR
statement in the pattern field.
Let me demonstrate using the examples of varying complexity from the last post…
Simple Relationships
CVE-2022-29098 offers a good example of simple relationships.
In this case, the 6 configurations variations that lead to matches inside one nodes
.
"configurations": [
{
"nodes": [
{
"operator": "OR",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:a:dell:powerscale_onefs:9.0.0:*:*:*:*:*:*:*",
"matchCriteriaId": "30687628-5C7F-4BB5-B990-93703294FDF0"
},
{
"vulnerable": true,
"criteria": "cpe:2.3:a:dell:powerscale_onefs:9.1.0:*:*:*:*:*:*:*",
"matchCriteriaId": "68291D44-DBE1-4923-A848-04E64288DC23"
},
{
"vulnerable": true,
"criteria": "cpe:2.3:a:dell:powerscale_onefs:9.1.1:*:*:*:*:*:*:*",
"matchCriteriaId": "DCC55FA4-AD91-4DA6-B60E-A4E34DDAE95A"
},
{
"vulnerable": true,
"criteria": "cpe:2.3:a:dell:powerscale_onefs:9.2.0:*:*:*:*:*:*:*",
"matchCriteriaId": "B948CD53-3D17-4230-9B77-FCE8E0E548B9"
},
{
"vulnerable": true,
"criteria": "cpe:2.3:a:dell:powerscale_onefs:9.2.1:*:*:*:*:*:*:*",
"matchCriteriaId": "5AB99A1A-8DD3-4DDE-B70C-0E91D1D3B682"
},
{
"vulnerable": true,
"criteria": "cpe:2.3:a:dell:powerscale_onefs:9.3.0:*:*:*:*:*:*:*",
"matchCriteriaId": "61F14753-D64C-4E8B-AA94-07E014848B4D"
}
]
}
]
}
],
- Dell PowerScale OneFS version 9.0.0 (
"matchCriteriaId": "30687628-5C7F-4BB5-B990-93703294FDF0"
)OR
, - Dell PowerScale OneFS version 9.1.0 (
"matchCriteriaId": "68291D44-DBE1-4923-A848-04E64288DC23"
)OR
, - Dell PowerScale OneFS version 9.1.1 (
"matchCriteriaId": "DCC55FA4-AD91-4DA6-B60E-A4E34DDAE95A"
)OR
, - Dell PowerScale OneFS (version 9.2.0) (
"matchCriteriaId": "B948CD53-3D17-4230-9B77-FCE8E0E548B9"
)OR
, - Dell PowerScale OneFS (version 9.2.1) (
"matchCriteriaId": "5AB99A1A-8DD3-4DDE-B70C-0E91D1D3B682"
)OR
, - Dell PowerScale OneFS (version 9.3.0) (
"matchCriteriaId": "61F14753-D64C-4E8B-AA94-07E014848B4D"
)
Note, all matchCriteriaId
responses contain only one CPE URI.
In this example the pattern
in the Indicator would be as follow;
"pattern": "[ software.cpe='cpe:2.3:a:dell:powerscale_onefs:9.0.0:*:*:*:*:*:*:*' ] OR [ software.cpe='cpe:2.3:a:dell:powerscale_onefs:9.1.0:*:*:*:*:*:*:*' ] OR [ software.cpe='cpe:2.3:a:dell:powerscale_onefs:9.1.1:*:*:*:*:*:*:*' ] OR [ software.cpe='cpe:2.3:a:dell:powerscale_onefs:9.2.0:*:*:*:*:*:*:*' ] OR [ software.cpe='cpe:2.3:a:dell:powerscale_onefs:9.2.1:*:*:*:*:*:*:*' ] OR [ software.cpe='cpe:2.3:a:dell:powerscale_onefs:9.3.0:*:*:*:*:*:*:*' ]"
Running On/With Relationships
Let me demonstrate how more complex Relationships are modelled using the example CVE-2022-27948.
In total there are 24 possible product combinations that are vulnerable in this CVE (see the last post for an explanation).
"configurations": [
{
"operator": "AND",
"nodes": [
{
"operator": "OR",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:o:tesla:model_3_firmware:*:*:*:*:*:*:*:*",
"versionEndIncluding": "2022-03-26",
"matchCriteriaId": "86619D7A-ACB6-489C-9C29-37C6018E5B4B"
},
{
"vulnerable": true,
"criteria": "cpe:2.3:o:tesla:model_s_firmware:*:*:*:*:*:*:*:*",
"versionEndIncluding": "2022-03-26",
"matchCriteriaId": "FD68704D-C711-491F-B278-B02C6866738C"
},
{
"vulnerable": true,
"criteria": "cpe:2.3:o:tesla:model_x_firmware:*:*:*:*:*:*:*:*",
"versionEndIncluding": "2022-03-26",
"matchCriteriaId": "C3517683-8493-4D0D-9792-5C9034B1F0B3"
}
]
},
{
"operator": "OR",
"negate": false,
"cpeMatch": [
{
"vulnerable": false,
"criteria": "cpe:2.3:h:tesla:model_3:-:*:*:*:*:*:*:*",
"matchCriteriaId": "825A79FD-C872-4564-9782-83BEEADDF5D9"
},
{
"vulnerable": false,
"criteria": "cpe:2.3:h:tesla:model_s:-:*:*:*:*:*:*:*",
"matchCriteriaId": "8D28E699-B843-4641-9BA6-406D88231E7C"
},
{
"vulnerable": false,
"criteria": "cpe:2.3:h:tesla:model_x:-:*:*:*:*:*:*:*",
"matchCriteriaId": "C550FF8A-58ED-4265-B33F-10AFDEA95519"
}
]
}
]
}
],
Here the is one nodes
again, however this time there are also two cpeMatch
es.
Note how in the Simple Relationships pattern all CPE key values were wrapped in square brackets ([]
). Each CPE inside a cpeMatch
is wrapped in square brackets.
So in this example I get pattern that will look like;
"pattern": "[ software.cpe = 'A' OR software.cpe = 'B' OR software.cpe = 'N'] AND [ software.cpe = '1' OR software.cpe = '2' OR software.cpe = '0']",
Note how the AND
joins the two square brackets, that’s because the top level operator in the CVE response shown above is an AND
.
For CVE-2022-27948 I get a pattern inside the Indicator SDO that reads;
"pattern": "[ software.cpe = 'cpe:2.3:o:tesla:model_3_firmware:-:*:*:*:*:*:*:*' OR software.cpe = 'cpe:2.3:o:tesla:model_3_firmware:11.0:*:*:*:*:*:*:*' OR software.cpe = 'cpe:2.3:o:tesla:model_3_firmware:2022-03-26:*:*:*:*:*:*:*' OR software.cpe = 'cpe:2.3:o:tesla:model_s_firmware:-:*:*:*:*:*:*:*' OR software.cpe = 'cpe:2.3:o:tesla:model_s_firmware:2022-03-26:*:*:*:*:*:*:*' OR software.cpe = 'cpe:2.3:o:tesla:model_x_firmware:-:*:*:*:*:*:*:*' OR software.cpe = 'cpe:2.3:o:tesla:model_x_firmware:2020-11-23:*:*:*:*:*:*:*' OR software.cpe = 'cpe:2.3:o:tesla:model_x_firmware:2022-03-26:*:*:*:*:*:*:*'] AND [ software.cpe = 'cpe:2.3:h:tesla:model_3:-:*:*:*:*:*:*:*' OR software.cpe = 'cpe:2.3:h:tesla:model_s:-:*:*:*:*:*:*:*' OR software.cpe = 'cpe:2.3:h:tesla:model_x:-:*:*:*:*:*:*:*']",
If you’re wondering where some of the CPEs in this pattern of come from, in this case I needed to get all the CPEs in all the matchCriteriaId
s (using the CPE Match Criteria API) to check the list of CPEs that are associated with them.
Advanced Relationships
I will use CVE-2019-18939 to demonstrate another more complex configuration
.
"configurations": [
{
"nodes": [
{
"operator": "AND",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:a:hm-print_project:hm-print:1.2a:*:*:*:*:*:*:*",
"matchCriteriaId": "286DA904-5631-4AAF-86DE-97C23982D2C5"
},
{
"vulnerable": false,
"criteria": "cpe:2.3:h:eq-3:homematic_ccu2:-:*:*:*:*:*:*:*",
"matchCriteriaId": "9C2CF19C-7EDE-4E3C-A736-E6736FF03FDC"
},
{
"vulnerable": true,
"criteria": "cpe:2.3:o:eq-3:homematic_ccu2_firmware:2.47.20:*:*:*:*:*:*:*",
"matchCriteriaId": "38BE17DA-7C5E-427E-B824-151EB27CFF26"
}
]
}
]
},
{
"nodes": [
{
"operator": "AND",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:a:hm-print_project:hm-print:1.2:*:*:*:*:*:*:*",
"matchCriteriaId": "F5D8290F-3541-4452-99CB-0766CDC59073"
},
{
"vulnerable": false,
"criteria": "cpe:2.3:h:eq-3:homematic_ccu3:-:*:*:*:*:*:*:*",
"matchCriteriaId": "33113AD0-F378-49B2-BCFC-C57B52FD3A04"
},
{
"vulnerable": true,
"criteria": "cpe:2.3:o:eq-3:homematic_ccu3_firmware:3.47.18:*:*:*:*:*:*:*",
"matchCriteriaId": "285F4E29-E299-4F83-9F7E-BB19933AD654"
}
]
}
]
},
{
"nodes": [
{
"operator": "AND",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:a:hm-print_project:hm-print:1.2a:*:*:*:*:*:*:*",
"matchCriteriaId": "286DA904-5631-4AAF-86DE-97C23982D2C5"
},
{
"vulnerable": false,
"criteria": "cpe:2.3:h:eq-3:homematic_ccu3:-:*:*:*:*:*:*:*",
"matchCriteriaId": "33113AD0-F378-49B2-BCFC-C57B52FD3A04"
},
{
"vulnerable": true,
"criteria": "cpe:2.3:o:eq-3:homematic_ccu3_firmware:3.47.18:*:*:*:*:*:*:*",
"matchCriteriaId": "285F4E29-E299-4F83-9F7E-BB19933AD654"
}
]
}
]
},
{
"nodes": [
{
"operator": "AND",
"negate": false,
"cpeMatch": [
{
"vulnerable": true,
"criteria": "cpe:2.3:a:hm-print_project:hm-print:1.2:*:*:*:*:*:*:*",
"matchCriteriaId": "F5D8290F-3541-4452-99CB-0766CDC59073"
},
{
"vulnerable": false,
"criteria": "cpe:2.3:h:eq-3:homematic_ccu2:-:*:*:*:*:*:*:*",
"matchCriteriaId": "9C2CF19C-7EDE-4E3C-A736-E6736FF03FDC"
},
{
"vulnerable": true,
"criteria": "cpe:2.3:o:eq-3:homematic_ccu2_firmware:2.47.20:*:*:*:*:*:*:*",
"matchCriteriaId": "38BE17DA-7C5E-427E-B824-151EB27CFF26"
}
]
}
]
}
],
In this CVE there are four nodes, so this time cve2stix will join the patterns with OR
statement.
Each pattern is fairly simplistic to create. Each of the nodes
only has one cpeMatch
. The operator in each cpeMatch
is an AND
. All matchCriteriaId
responses contain only one CPE URI.
This gives four patterns…
Pattern one;
"pattern": "[ software.cpe = 'cpe:2.3:a:hm-print_project:hm-print:1.2a:*:*:*:*:*:*:*' AND software.cpe = 'cpe:2.3:h:eq-3:homematic_ccu2:-:*:*:*:*:*:*:*' AND software.cpe = 'cpe:2.3:o:eq-3:homematic_ccu2_firmware:2.47.20:*:*:*:*:*:*:*']",
Pattern two;
"pattern": "[ software.cpe = 'cpe:2.3:a:hm-print_project:hm-print:1.2:*:*:*:*:*:*:*' AND software.cpe = 'cpe:2.3:h:eq-3:homematic_ccu3:-:*:*:*:*:*:*:*' AND software.cpe = 'cpe:2.3:o:eq-3:homematic_ccu3_firmware:3.47.18:*:*:*:*:*:*:*']",
Pattern three;
"pattern": "[ software.cpe = 'cpe:2.3:a:hm-print_project:hm-print:1.2a:*:*:*:*:*:*:*' AND software.cpe = 'cpe:2.3:h:eq-3:homematic_ccu3:-:*:*:*:*:*:*:*' AND software.cpe = 'cpe:2.3:o:eq-3:homematic_ccu3_firmware:3.47.18:*:*:*:*:*:*:*']",
Pattern four;
"pattern": "[ software.cpe = 'cpe:2.3:a:hm-print_project:hm-print:1.2:*:*:*:*:*:*:*' AND software.cpe = 'cpe:2.3:h:eq-3:homematic_ccu2:-:*:*:*:*:*:*:*' AND software.cpe = 'cpe:2.3:o:eq-3:homematic_ccu2_firmware:2.47.20:*:*:*:*:*:*:*']",
Which form a single pattern inside the Indicator SDO as follows;
"pattern": "([ software.cpe = 'cpe:2.3:a:hm-print_project:hm-print:1.2a:*:*:*:*:*:*:*' AND software.cpe = 'cpe:2.3:h:eq-3:homematic_ccu2:-:*:*:*:*:*:*:*' AND software.cpe = 'cpe:2.3:o:eq-3:homematic_ccu2_firmware:2.47.20:*:*:*:*:*:*:*']) OR ([ software.cpe = 'cpe:2.3:a:hm-print_project:hm-print:1.2:*:*:*:*:*:*:*' AND software.cpe = 'cpe:2.3:h:eq-3:homematic_ccu3:-:*:*:*:*:*:*:*' AND software.cpe = 'cpe:2.3:o:eq-3:homematic_ccu3_firmware:3.47.18:*:*:*:*:*:*:*']) OR ([ software.cpe = 'cpe:2.3:a:hm-print_project:hm-print:1.2a:*:*:*:*:*:*:*' AND software.cpe = 'cpe:2.3:h:eq-3:homematic_ccu3:-:*:*:*:*:*:*:*' AND software.cpe = 'cpe:2.3:o:eq-3:homematic_ccu3_firmware:3.47.18:*:*:*:*:*:*:*']) OR ([ software.cpe = 'cpe:2.3:a:hm-print_project:hm-print:1.2:*:*:*:*:*:*:*' AND software.cpe = 'cpe:2.3:h:eq-3:homematic_ccu2:-:*:*:*:*:*:*:*' AND software.cpe = 'cpe:2.3:o:eq-3:homematic_ccu2_firmware:2.47.20:*:*:*:*:*:*:*'])",
Relationship SROs
Now that the CVE is modelled as a STIX Vulnerability and STIX Indicator Objects the relationship between them needs to be defined.
cve2stix uses STIX Relationship SROs to do this. They are structured like so;
{
"type": "relationship",
"spec_version": "2.1",
"id": "relationship--<GENERATED BY STIX2 LIBRARY>",
"created_by_ref": "identity--<CVE2STIX IDENTITY ID>",
"created": "<vulnerabilities.cve.published>",
"modified": "<vulnerabilities.cve.lastModifiedDate>",
"relationship_type": "indicates",
"source_ref": "indicator--<INDICATOR STIX OBJECT>",
"target_ref": "vulnerability--<VULNERABILITY STIX OBJECT>",
"object_marking_refs": [
"marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9"
],
"external_references": [
{
"source_name": "cve2stix",
"external_id": "<vulnerability.id>",
"url": "https://<HOST>/cve/<CVE ID>"
}
],
}
Note, one Relationship SRO is created for every Indicator SDO created for the CVE, but they all point to the same Vulnerability SDO.
Software SCOs
I’ve already shown the Software SCOs used in the STIX pattern. For each Software SCO referenced in a pattern, an SCO will already exist (because cve2stix backfills CPEs from the CPE API on first run).
Software SCOs have a default cpe
, languages
, vendor
, and version
Properties that are all found in CPE URIs used to generate them.
cve2stix creates Software SCOs for CPEs as follows;
{
"type": "software",
"spec_version": "2.1",
"id": "software--<GENERATED BY STIX2 LIBRARY>",
"name": "<products.cpe.titles.title> (if multiple, where lan = en, else first result)",
"cpe": "<products.cpe.cpeName>",
"version": "<products.cpe.cpeName[version_section]>",
"vendor": "<products.cpe.cpeName[vendor_section]>",
"languages": ["<products.cpe.titles.lang>"],
"object_marking_refs": [
"marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9"
]
}
When a CVE is created and an Indicator pattern references a Software SCO, the two are joined with a Relationship SRO as follows;
{
"type": "relationship",
"spec_version": "2.1",
"id": "relationship--<GENERATED BY STIX2 LIBRARY>",
"created_by_ref": "identity--<CVE2STIX IDENTITY ID>",
"created": "<vulnerabilities.cve.published>",
"modified": "<vulnerabilities.cve.lastModifiedDate>",
"relationship_type": "pattern-contains",
"source_ref": "indicator--<INDICATOR STIX OBJECT>",
"target_ref": "software--<SOFTWARE STIX OBJECT>",
"object_marking_refs": [
"marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9"
],
"external_references": [
{
"source_name": "cve2stix",
"external_id": "<vulnerability.id>",
"url": "https://<HOST>/cve/<CVE ID>"
}
]
}
Grouping SDOs
Finally a STIX Grouping Object is used to group all the Objects related to the CVE together;
{
"type": "grouping",
"spec_version": "2.1",
"id": "grouping--<UUID OF VULNERABILITY OBJECT>",
"created_by_ref": "identity--<CVE2STIX IDENTITY ID>",
"created": "<vulnerabilities.cve.published>",
"modified": "<vulnerabilities.cve.lastModifiedDate>",
"name": "CVE: <vulnerabilities.cve.id>",
"description": "<vulnerabilities.cve.descriptions.description_data.value> (if multiple, where lan = en, else first result)",
"context": "unspecified",
"object_refs": [
"<IDENTITY, VULNERABILITY, INDICATOR, SOFTWARE, RELATIONSHIP IDs>"
],
"object_marking_refs": [
"marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9"
],
"external_references": [
{
"source_name": "cve2stix",
"external_id": "<vulnerability.id>",
"url": "https://<HOST>/cve/<CVE ID>"
}
]
}
A deeper dive on how updates work
All objects, except for Software SCOs, Relationship SROs and any imported Objects from enrichments, can recieve updates (via a minor verson update, meaning the id
property of the orignal object always remains the same) when changes are made to the respective NVD data. In summary the following objects are changed on the following triggers;
- Vulnerablity SDOs: when Vulnerability (CVE) data is updated, including revocations.
- Indicator SDOs: when Vulnerability (CVE) data is updated with new CPE match criteria.
- Grouping SDOs: when the the Vulnerability (CVE) data is updated and it results in any new Object having a direct or indirect relationship to the Vulnerablity SDO.
Updates can be detected by querying the CVE change history endpoint daily (using changeStartDate
and changeEndDate
parameters). The response will show any CVE’s that have changed in this period and a description of what the change was. When a CVE has been marked as updated, the CVE ID endpoint is used to update the relevant STIX objects to reflect the changes.
In the backend, this is handled by the stix2 Python library, which ultimately creates new objects for each version. To demostrate this, let me show you an example;
git clone https://github.com/signalscorps/tutorials
cd tutorials/cve2stix-python-stix2-versioning
python3 -m venv tutorial_env
source tutorial_env/bin/activate
pip3 install -r requirements.txt
cd examples
Now using the example scripts I will create the first Vulnerability;
python3 01-create-first-cve.py
cd /tmp/stix2_store/vulnerability
ls
cd vulnerability--9a48ec87-c5b0-46b2-95f1-6869009ce1ca
ls
You’ll see in the vulnerability/
a directory named with the objects id
(e.g. in my case vulnerability--9a48ec87-c5b0-46b2-95f1-6869009ce1ca/
). Inside this directory is the first version of the object, named with the datetime of generation (e.g 20230217101303144117.json
which was generated on 2023-02-17 at 10:13:03.144117 UTC).
The next script is almost the same as the first, with three differences;
- explitly passes the
id
generated by script one (id="vulnerability--9a48ec87-c5b0-46b2-95f1-6869009ce1ca"
. In the case of cve2stix, when an update is observed it will lookup theid
of the original object. - explitly passes the
created
property (created="2023-02-17T10:13:03.144117Z"
) from version one to ensure it stays the same (else the stix2 lib will use the time of script execution for the value) - updates the
description
with new text (description="Updated description"
).
cd ../../..
python3 02-update-first-cve.py
cd /tmp/stix2_store/vulnerability/vulnerability--9a48ec87-c5b0-46b2-95f1-6869009ce1ca
ls
Looking in the the vulnerability--9a48ec87-c5b0-46b2-95f1-6869009ce1ca/
you’ll now see two versions of the object in my case 20230217101303144117.json
(the original version);
{
"type": "vulnerability",
"spec_version": "2.1",
"id": "vulnerability--9a48ec87-c5b0-46b2-95f1-6869009ce1ca",
"created": "2023-02-17T10:13:03.144117Z",
"modified": "2023-02-17T10:13:03.144117Z",
"name": "CVE-XXXX-XXXXX",
"description": "Used for tutorial content"
}
and 20230217101756888626.json
(the updated version);
{
"type": "vulnerability",
"spec_version": "2.1",
"id": "vulnerability--9a48ec87-c5b0-46b2-95f1-6869009ce1ca",
"created": "2023-02-17T10:13:03.144117Z",
"modified": "2023-02-17T10:17:56.888626Z",
"name": "CVE-XXXX-XXXXX",
"description": "Updated description"
}
So, going back to the orignal point, what this means is, for each object created by cve2stix there is likely to be one or more versions of it that exist. cve2stix only ever needs the latest version (identified by most recent filename time).
Versioning will become important later, as each time an update happens to an object, a new bundle (version) is also generated. This means you can have a timeseries of bundles that track the changes made by NVD to the CVE. Generally you will only want the latest, but for research you might want to view it’s history in this way.
It is important to note, cve2stix will only version STIX objects after initial import. That is to say, on first install cve2stix will download the most recent version of all CVEs. These are all version 1, even if they have NVD change history. Any updates from this date will then be versioned by cve2stix.
That said, as shown above, the Indicator STIX object also includes a copy of the change history recorded by the NVD change history API, should specific historic change information be required.
Next up: Enriching NVD CPEs
To provide even more context to those looking at CVEs, cve2stix enriches CVEs using CAPEC and ATT&CK external knowledge-bases.
In the next post I will explain where the data for these enrichments comes from and how it is handled by cve2stix.
Discuss this post

Never miss an update
Sign up to receive new articles in your inbox as they published.