GHSA-rhr8-3pm8-cq29

Suggest an improvement
Source
https://github.com/advisories/GHSA-rhr8-3pm8-cq29
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/11/GHSA-rhr8-3pm8-cq29/GHSA-rhr8-3pm8-cq29.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-rhr8-3pm8-cq29
Aliases
Published
2026-11-10T15:57:41Z
Modified
2026-11-10T16:15:05Z
Severity
  • 7.5 (High) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H CVSS Calculator
Summary
Forge: ASN.1 resource exhaustion via BIT STRING decoding in asn1.fromDer
Details

Summary

asn1.fromDer() decodes the contents of BIT STRINGs as ASN.1 by default. That decode ignored the maxDepth recursion limit (an incomplete fix of CVE-2025-66031) and also decoded nested BIT STRINGs, keeping a full copy of the decoded contents at each level, so small crafted DER or PEM inputs (certificates, keys, PKCS structures) use excessive CPU time and memory and can abort the Node.js process. Fixed in node-forge 1.4.1.

Details

asn1.fromDer() attempts by default to decode the contents of primitive BIT STRINGs as ASN.1 (decodeBitStrings, default true). Two flaws in that decode in lib/asn1.js allow resource exhaustion:

  1. _fromDer() started the BIT STRING decode with a new options object, {strict: true, decodeBitStrings: true}, that did not include maxDepth. Inside that decode the depth check compared against undefined and never triggered, so the recursion limit added for CVE-2025-66031 (GHSA-554w-wpv2-vw27) was not enforced for anything below a decoded BIT STRING. Both the global asn1.maxDepth (default 256) and the per-call maxDepth option were ignored there. Errors in this speculative decode, including stack overflows, are caught and the contents kept as raw bytes, so deep nesting is parsed instead of rejected. This is an incomplete fix of CVE-2025-66031.
  2. BIT STRINGs nested inside decoded BIT STRING contents were decoded as well, and asn1.create() gives each decoded BIT STRING a deep copy of its decoded contents (obj.original, used by toDer() to detect changes). Memory use therefore grows quadratically with the nesting depth, even within the default depth limit.

No specification is violated; the parser lacks a resource limit.

The fix passes maxDepth to the BIT STRING decode, and decodes BIT STRING contents with decodeBitStrings: false, so only one level of BIT STRING contents is decoded. Contents that would exceed the limit, and BIT STRINGs inside decoded BIT STRING contents, are kept as undecoded BIT STRING values; toDer() writes their original bytes. Common structures such as SubjectPublicKeyInfo only use one level. Code that relied on nested decoding can decode the inner value explicitly with asn1.fromDer().

Patches

Fixed in node-forge 1.4.1. The maxDepth limit now applies to ASN.1 decoded from BIT STRING contents, and only one level of BIT STRING contents is decoded.

Workarounds

Code that calls forge.asn1.fromDer() directly and does not need encapsulated BIT STRING contents decoded can pass {decodeBitStrings: false}. This changes the parsed structure. The higher-level PKI, PKCS and TLS APIs do not expose this option and rely on the decoded form. For those, the only remedy is upgrading.

References

Severity

CVSS 3.1: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H (7.5, High) CVSS 4.0 (informational): CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N (8.7) Both versions rate this High. The severity rating follows CVSS 3.1.

Credits

Reported by Ethan Kim (cremit.io), Youngjoon Kim (@acorn421) and Brian Lee (@hibrian827) of Georgia Tech SSLab, @arpitjain099, and Mike Read.

Proof of concept

Input shape: n nested primitive BIT STRINGs (03 <len> 00 <inner>) around a NULL. For example:

var asn1 = forge.asn1;
var obj = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '');
for(var i = 0; i < 2000; ++i) {
  obj = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, [obj]);
}
var der = asn1.toDer(obj).getBytes(); // 9,886 bytes
asn1.fromDer(der, {maxDepth: 10});

On 1.4.0 (Node.js 24) this parses all 2,000 levels despite maxDepth: 10, taking about 0.5-1 s and 600 MB. One SEQUENCE holding 20 such chains (about 198 KB) aborts a process with the default heap with "FATAL ERROR: JavaScript heap out of memory", which the application cannot catch. Within the depth limit, 80 chains of 254 levels (92 KB) still use about 0.8 GB. The same inputs wrapped as PEM reach pki.publicKeyFromPem() and pki.certificateFromPem() (a 20 KB certificate PEM used 1.3 GB before being rejected). On 1.4.1 these inputs parse in a few milliseconds and about 55-65 MB.

Regression tests in tests/unit/asn1.js: "should limit depth in decoded BIT STRINGs (default)", "should limit depth in decoded BIT STRINGs (maxDepth)", "should only decode one level of BIT STRINGs" and "should not decode BIT STRINGs in many BIT STRINGs".

Impact

A remote, unauthenticated attacker can supply a small DER or PEM structure with nested BIT STRINGs to any API that parses with asn1.fromDer() using default options. This includes pki.certificateFromPem(), pki.publicKeyFromPem(), the PKCS#7, PKCS#8 and PKCS#12 loaders, and server certificates received by forge's TLS client. Parsing such input consumes excessive CPU time and memory before the structure is rejected. On Node.js 24 a 10 KB input used about 600 MB and a 25 KB input about 1.4 GB. Inputs of about 25 KB abort a process limited to a 512 MB heap, and about 200 KB abort a process with the default heap, with a fatal out-of-memory error that the application cannot catch. Inputs that stay within the depth limit still reach about 0.8 GB at 92 KB and 1.8 GB at 231 KB. The result is denial of service. There is no confidentiality or integrity impact.

Database specific
{
    "cwe_ids": [
        "CWE-400",
        "CWE-674"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-11-10T15:57:41Z",
    "nvd_published_at": "2026-10-29T20:17:30Z",
    "severity": "HIGH"
}
References

Affected packages

npm / node-forge

Package

Affected ranges

Type
SEMVER
Events
Introduced
0 Unknown introduced version / All previous versions are affected
Fixed
1.4.1

Database specific

last_known_affected_version_range
"<= 1.4.0"
source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/11/GHSA-rhr8-3pm8-cq29/GHSA-rhr8-3pm8-cq29.json"