tl;dr
CVEs often contain a CWE reference, but that coupling is weak.
It tells you which weakness was assigned to the vulnerability, but it does not give you a structured weakness object you can actually traverse, enrich, and reuse.
We built cwe2stix to fix that, then used the result in Vulmatch so vulnerabilities and weaknesses could sit in the same STIX graph.
Problem: coupling CVE to CWE is not enough
If you look at the STIX Vulnerability objects in Vulmatch, you will see they can include a CWE reference.
{
"created": "2026-04-05T21:16:47.140Z",
"created_by_ref": "identity--3644753d-7db4-5c2f-af5e-4dc9b8d196ba",
"description": "phpBB contains an arbitrary file upload vulnerability that allows authenticated attackers to upload malicious files by exploiting the plupload functionality and phar:// stream wrapper. Attackers can upload a crafted zip file containing serialized PHP objects that execute arbitrary code when deserialized through the imagick parameter in attachment settings.",
"extensions": {
"extension-definition--2c5c13af-ee92-5246-9ba7-0b958f8cd34a": {
"extension_type": "toplevel-property-extension"
},
"extension-definition--ec658473-1319-53b4-879f-488e47805554": {
"extension_type": "toplevel-property-extension"
}
},
"external_references": [
{
"source_name": "cve",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2019-25685",
"external_id": "CVE-2019-25685"
},
{
"source_name": "cwe",
"url": "https://cwe.mitre.org/data/definitions/22.html",
"external_id": "CWE-22"
},
{
"source_name": "[email protected]",
"description": "Exploit,VDB Entry",
"url": "https://www.exploit-db.com/exploits/46512"
},
{
"source_name": "[email protected]",
"description": "Third Party Advisory",
"url": "https://www.vulncheck.com/advisories/phpbb-arbitrary-file-upload-via-phar-deserialization"
},
{
"source_name": "vulnStatus",
"description": "Analyzed"
}
],
"id": "vulnerability--f8d531b0-3280-5b9c-b327-13026d2d8bfb",
"modified": "2026-04-09T19:07:46.060Z",
"name": "CVE-2019-25685",
"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": [
{
"vector_string": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
"exploitability_score": 2.8,
"impact_score": 5.9,
"base_score": 8.8,
"base_severity": "HIGH",
"source": "[email protected]",
"type": "Primary"
}
],
"v4_0": [
{
"vector_string": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/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",
"base_score": 8.7,
"base_severity": "HIGH",
"source": "[email protected]",
"type": "Secondary"
}
]
},
"x_opencti_cvss_base_score": 8.8,
"x_opencti_cvss_base_severity": "HIGH",
"x_opencti_cvss_v4_base_score": 8.7,
"x_opencti_cvss_v4_base_severity": "HIGH",
"x_opencti_cvss_v4_vector_string": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/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",
"x_opencti_cvss_vector_string": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
"x_opencti_cwe": [
"CWE-22"
],
"x_opencti_epss_percentile": 0.40065,
"x_opencti_epss_score": 0.00183
}
Source: CVE-2019-25685
That is useful, but only up to a point.
A CVE is a published vulnerability record.
A CWE is a model of the underlying weakness that may appear across many vulnerabilities.
Putting a CWE ID inside a CVE tells you there is a relationship, but it does not model the weakness itself as a first-class object.
That creates a few problems.
- the CWE is effectively a label, not a graph node
- you cannot pivot cleanly from one weakness to related weaknesses
- you cannot store CWE-native fields in a structured STIX object
- you cannot treat weaknesses as reusable intelligence across other products and workflows
This matters because the value of CWE is not just classification.
The value is the ability to move from a single vulnerability to the broader class of engineering problem behind it.
That is the difference between seeing CVE-2026-xxxx with CWE-798 attached and being able to ask:
- what other vulnerabilities share this weakness?
- what attack patterns are associated with this weakness?
- what recurring software engineering problem does this product keep exhibiting?
If the CWE is only coupled to the CVE as a reference, that pivot is weak.
Why we decided to make a new STIX object
The obvious alternative was to force CWE data into an existing STIX object type, most likely vulnerability.
We did not do that.
A CWE is not a vulnerability.
It is a weakness class.
Using the same object type for both blurs the semantics and makes the data model harder to reason about. We had already seen that confusion in practice.
So we chose the stricter approach:
create a dedicated STIX object for weaknesses.
That decision gave us a few things immediately.
- a clear distinction between vulnerability instances and weakness classes
- a place to store CWE-specific properties properly
- stable graph nodes that can be linked to other datasets
- a cleaner model for reuse in products like Vulmatch
The point was not to create a custom STIX object for its own sake. The point was to make vulnerability intelligence more useful downstream.
Briefly: how we designed it in cwe2stix
We implemented the model in cwe2stix.
The design is straightforward.
There is no native STIX 2.1 object for weaknesses, so cwe2stix defines a custom weakness SDO using an Extension Definition (read this post in custom STIX Objects if this unfamiliar).
That lets each CWE become a proper STIX object with its own:
- ID
- name
- description
- external references
- modes of introduction
- common consequences
- likelihood of exploit
- detection methods
We also model the relationships between weaknesses as STIX Relationship objects rather than flattening them into text.
That means the MITRE CWE hierarchy and related-weakness mappings can be traversed like the rest of the graph.
What it looks like in Vulmatch and why it is useful
This is the part that matters most to users.
In Vulmatch, we use this model so STIX Vulnerability and Weakness objects can exist together in the same graph.
That means a user can move from a vulnerability to the weakness behind it, then outward to the rest of the connected intelligence.
The immediate product benefit is that CWE is no longer buried as a field on the CVE record.
In Vulmatch, weaknesses can be surfaced as their own view and used as the starting point for analysis.
That is a materially better workflow than treating CWE as an annotation.
It means a user can:
- start from the weakness itself, not just an individual CVE
- see which weakness classes are driving the most vulnerabilities
- pivot from one weakness to the set of CVEs assigned to it
- use the weakness as a stable join point for the rest of the intelligence graph
Instead of treating CWE-89 or CWE-798 as a string attached to a vulnerability, Vulmatch can treat it as an entity with its own page, its own relationships, and its own analytical value.
That is useful for both tactical and strategic work.
Tactically, it gives an analyst a fast way to move from a CVE to the class of software problem behind it.
Strategically, it lets teams look at which weakness classes recur most often across the vulnerabilities they care about.
Known limitations
The main limitation is interoperability with tools that claim STIX support but do not support arbitrary custom STIX objects well.
The clearest example is OpenCTI.
As described in this post, OpenCTI is not a general-purpose STIX object store. It ingests STIX, transforms it into its own internal model, and does not reliably preserve custom SDOs or their Extension Definitions.
That creates a practical limitation for any model built around custom STIX Weakness objects:
- the STIX is valid
- the model is useful
- but some downstream tools will still drop or partially rewrite it
That is not a problem with the CWE model itself. It is a limitation of the surrounding tooling ecosystem.
Summary
Coupling a CVE to a CWE reference is not enough if you want the weakness to be searchable, traversable, and reusable as intelligence.
So we created a dedicated STIX Weakness object, implemented it in cwe2stix, and used that model in Vulmatch to make vulnerability intelligence more useful.
Once you do that, CWE stops being a dead-end label on a CVE and becomes a real pivot point in the graph.
Join the discussion
Head over to the dogesec community to discuss this post with other readers and practitioners.
