Oct 31, 2026·10 min read·3 visits
In node-forge versions prior to 1.4.1, asn1.oidToDer() does not validate the first two arcs of an OID string and truncates their combined value to 8 bits. This allows crafted OID strings (such as 0.42.840.113549.1.1.1) to bypass string-based allowlists or denylists while serializing into valid DER bytes for restricted OIDs (such as 1.2.840.113549.1.1.1 for rsaEncryption). Upgrade to node-forge 1.4.1 or later to resolve this issue.
CVE-2026-118775 (GHSA-4642-5mcj-2rqw) is an integer overflow and validation flaw in the ASN.1 serialization module of Forge (node-forge) prior to version 1.4.1. The asn1.oidToDer() function fails to validate the range of the first two arcs of an Object Identifier (OID) and truncates the packed initial subidentifier to a single 8-bit integer. Applications that perform policy checks on untrusted OID strings before encoding them into Distinguished Encoding Rules (DER) format can be tricked into emitting DER bytes that correspond to a completely different, restricted OID.
Forge (distributed on npm as node-forge) is a widely deployed native JavaScript implementation of Transport Layer Security (TLS) and cryptographic utilities. It provides low-level modules for constructing, parsing, and serializing Abstract Syntax Notation One (ASN.1) data structures into Distinguished Encoding Rules (DER) binary payloads. These capabilities are frequently used by JavaScript applications to process X.509 certificates, Public-Key Cryptography Standards (PKCS) messages, and cryptographic signatures.
Within the ASN.1 subsystem (lib/asn1.js), the asn1.oidToDer() utility converts dot-delimited Object Identifier (OID) strings into their binary DER byte representations. OIDs are standardized identifiers used across cryptographic protocols to specify algorithms, certificate extensions, and naming attributes. For example, the string 1.2.840.113549.1.1.1 uniquely identifies the rsaEncryption algorithm.
In node-forge versions prior to 1.4.1, asn1.oidToDer() contains an integer truncation and input validation vulnerability tracked as CVE-2026-118775 (and CWE-190). Specifically, the function does not enforce the mathematical constraints defined by ITU-T X.660 and X.690 on the first two arcs of an OID, and it unconditionally writes the packed initial subidentifier using a single-byte buffer operation.
This flaw serves as a patch completion for an earlier vulnerability, CVE-2025-66030, which enforced 32-bit range limits on subsequent OID arcs (index 2 and above) but left the initial two arcs unvalidated. If an application validates an untrusted OID string against an access control policy and subsequently passes that string to asn1.oidToDer(), an attacker can supply an out-of-range or ambiguous OID string that evaluates to a different, restricted OID in the resulting DER binary.
To understand the root cause of CVE-2026-118775, it is necessary to examine the binary encoding specification for ASN.1 Object Identifiers. According to ITU-T Recommendation X.690 §8.19, an OID consists of an ordered sequence of integer arcs $(A_0, A_1, A_2, \dots, A_n)$. To optimize storage, the first two arcs $A_0$ and $A_1$ are combined into a single initial numerical subidentifier using the formula:
$$\text{Subidentifier}_0 = (40 \times A_0) + A_1$$
To ensure that every valid OID string maps bijectively to a unique subidentifier value without collisions, ITU-T Recommendation X.660 enforces strict domain boundaries on $A_0$ and $A_1$. The first arc $A_0$ is restricted to three possible root authorities: 0 (ITU-T), 1 (ISO), or 2 (joint-iso-itu-t). Furthermore, when $A_0$ is 0 or 1, the second arc $A_1$ is strictly bounded to the range $0 \le A_1 \le 39$. Only when $A_0 = 2$ may $A_1$ exceed 39.
Prior to version 1.4.1, asn1.oidToDer() violated this specification in two distinct ways. First, it omitted the domain checks verifying that $A_0 \in {0, 1, 2}$ and that $A_1 \le 39$ when $A_0 < 2$. Without this validation, the packing formula $(40 \times A_0) + A_1$ loses injectivity. Specifically, incrementing $A_1$ by 40 produces the exact same arithmetic sum as incrementing $A_0$ by 1. Consequently, the invalid OID prefix 0.42 yields $(40 \times 0) + 42 = 42$, which is identical to the valid prefix 1.2 where $(40 \times 1) + 2 = 42$.
Second, instead of encoding the initial subidentifier as a variable-length quantity (VLQ) in base-128 like the remaining arcs, the function passed the result of 40 * parseInt(values[0], 10) + parseInt(values[1], 10) directly into bytes.putByte(). Inside Forge's byte buffer implementation, putByte() truncates its input to an 8-bit unsigned integer via bitwise masking (& 0xFF). Any combined subidentifier value exceeding 255 undergoes modulo-256 wraparound, allowing large integer inputs to wrap back into the byte values of standard OIDs.
In node-forge versions 1.4.0 and earlier, the asn1.oidToDer() and asn1.derToOid() functions in lib/asn1.js handled the first byte separately from the base-128 variable-length encoding loop used for subsequent arcs.
// Vulnerable implementation in lib/asn1.js (prior to 1.4.1)
asn1.oidToDer = function(oid) {
var values = oid.split('.');
var bytes = forge.util.createBuffer();
// FLAW 1: No validation on values[0] or values[1]
// FLAW 2: putByte() masks the sum to 8 bits (& 0xFF), causing wraparound
bytes.putByte(40 * parseInt(values[0], 10) + parseInt(values[1], 10));
var last, valueBytes, value, b;
// Loop starts at index 2, skipping VLQ encoding for the first subidentifier
for(var i = 2; i < values.length; ++i) {
last = true;
valueBytes = [];
value = parseInt(values[i], 10);
if(value > 0xffffffff) {
throw new Error('OID value too large; max is 32-bits.');
}
// ... base-128 encoding logic ...
}
};In commit e56a3dde8e4e63bae566bb32546eeaf92f515e1e, the maintainers resolved both the missing range checks and the single-byte truncation by validating the initial arcs against ITU-T X.660 rules and folding the first subidentifier into the main base-128 encoding loop.
// Patched implementation in lib/asn1.js (v1.4.1)
asn1.oidToDer = function(oid) {
var values = oid.split('.');
var bytes = forge.util.createBuffer();
// FIX 1: Enforce ITU-T X.660 bounds on the first two arcs
var first = parseInt(values[0], 10);
var second = parseInt(values[1], 10);
if(!(first >= 0 && first <= 2 && second >= 0) ||
(first < 2 && second > 39)) {
throw new Error('Invalid OID; the first two arcs are out of range.');
}
var last, valueBytes, value, b;
// FIX 2: Start loop at index 1 so the first subidentifier (40 * first + second)
// undergoes 32-bit bounds checking and multi-byte base-128 VLQ encoding
for(var i = 1; i < values.length; ++i) {
last = true;
valueBytes = [];
value = (i === 1) ? 40 * first + second : parseInt(values[i], 10);
if(value > 0xffffffff) {
throw new Error('OID value too large; max is 32-bits.');
}
// ... base-128 encoding logic ...
}
};The patch also updated the complementary decoding function asn1.derToOid(). Previously, derToOid() read a single byte via bytes.getByte() and computed Math.floor(b / 40) + '.' + (b % 40), which corrupted legitimate multi-byte initial subidentifiers where $A_0 = 2$ and $A_1 \ge 48$ (such as 2.999). The patched decoder processes all subidentifiers through the base-128 state machine and splits the first decoded integer using (value < 80) ? Math.floor(value / 40) + '.' + (value % 40) : '2.' + (value - 80).
> [!NOTE]
> Fix Completeness Assessment: While commit e56a3dde8e4e63bae566bb32546eeaf92f515e1e eliminates arithmetic aliasing and 8-bit truncation in oidToDer(), it continues to rely on parseInt(values[i], 10). Because parseInt() ignores trailing non-numeric characters in a segment (e.g., parseInt('2a', 10) evaluates to 2) and tolerates leading zeros or whitespace, applications that perform strict string comparisons before calling oidToDer() should still canonicalize or regex-validate OID strings prior to policy evaluation.
Exploiting CVE-2026-118775 requires a target application that accepts an OID string from an untrusted source, performs a security-relevant check (such as an allowlist or denylist comparison) on that string representation, and then passes the string to forge.asn1.oidToDer() to construct an ASN.1 structure. Note that applications parsing incoming binary DER structures via asn1.derToOid() are not vulnerable to OID spoofing, as DER decoding is deterministic.
An attacker can construct two distinct classes of payload strings that alias to a target OID such as 1.2.840.113549.1.1.1 (rsaEncryption), whose initial subidentifier byte is 0x2A (decimal 42):
0.42 Alias): By setting $A_0 = 0$ and $A_1 = 42$, the expression $(40 \times 0) + 42$ evaluates to 42. Supplying 0.42.840.113549.1.1.1 bypasses any string filter checking for the 1.2. prefix while producing the exact DER byte sequence for rsaEncryption.bytes.putByte(val) retains only the lowest 8 bits (val & 0xFF), any combination of $A_0$ and $A_1$ satisfying $(40 \times A_0 + A_1) \equiv 42 \pmod{256}$ will emit 0x2A. For example, with $A_0 = 4294977127$ and $A_1 = 18$:$$(40 \times 4294977127) + 18 = 171799085098$$
In hexadecimal, 171799085098 is 0x280009702A. When masked with 0xFF inside putByte(), the value truncates to 0x2A (42).
const forge = require('node-forge'); // Version <= 1.4.0
const targetOid = '1.2.840.113549.1.1.1'; // rsaEncryption
const spoofedOid1 = '0.42.840.113549.1.1.1';
const spoofedOid2 = '4294977127.18.840.113549.1.1.1';
// Application string-level policy check
if (spoofedOid1 === targetOid || spoofedOid2 === targetOid) {
throw new Error('Blocked prohibited OID');
}
// Both spoofed strings serialize to '2a864886f70d010101', identical to targetOid
console.log(forge.asn1.oidToDer(targetOid).toHex()); // 2a864886f70d010101
console.log(forge.asn1.oidToDer(spoofedOid1).toHex()); // 2a864886f70d010101
console.log(forge.asn1.oidToDer(spoofedOid2).toHex()); // 2a864886f70d010101The security impact of CVE-2026-118775 is scored at CVSS v3.1 3.7 (Low) with the vector CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N. The vulnerability does not affect confidentiality or availability, and its integrity impact is constrained by the high attack complexity (AC:H).
Exploitation is contingent on a specific architectural pattern: the target system must expose an interface where an unauthenticated or low-privileged user can specify raw OID strings that are validated as strings prior to ASN.1 DER serialization. In standard TLS handshakes, PKCS#7/CMS verification, or X.509 certificate parsing workflows, OIDs arrive as binary DER structures and are decoded via asn1.derToOid(), which is unaffected by this spoofing vector.
Where the vulnerable pattern exists—such as in custom Certificate Authority (CA) enrollment portals, dynamic CSR generators, or remote signing services that block specific Extended Key Usage (EKU) or algorithm OIDs via string matching—an attacker can bypass those policy controls. For instance, a certificate issuance service that prohibits the id-kp-serverAuth or anyExtendedKeyUsage OID by checking input strings against a denylist could be coerced into signing a certificate containing the restricted extension in its final DER-encoded X.509 body.
Threat intelligence metrics reflect these specialized preconditions. The Exploit Prediction Scoring System (EPSS) assigns CVE-2026-118775 a score of 0.00313 (0.313%, 24.28th percentile), and the vulnerability is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog.
The primary remediation is to upgrade node-forge to version 1.4.1 or later, which incorporates commit e56a3dde8e4e63bae566bb32546eeaf92f515e1e and throws an error when asn1.oidToDer() receives out-of-range initial arcs.
npm install node-forge@^1.4.1For environments where upgrading node-forge cannot be performed immediately, developers should implement strict input validation and canonicalization before evaluating any OID string in policy logic or passing it to asn1.oidToDer(). The validation function must enforce that the string contains only dot-separated base-10 integers without leading zeros, and that the first two arcs satisfy ITU-T X.660 boundaries.
function validateCanonicalOid(oid) {
if (typeof oid !== 'string') {
return false;
}
const arcs = oid.split('.');
if (arcs.length < 2) {
return false;
}
// Reject non-digits, leading zeros, whitespace, and trailing characters
if (!arcs.every(arc => /^(0|[1-9]\d*)$/.test(arc))) {
return false;
}
const numbers = arcs.map(Number);
// Ensure all arcs fit within unsigned 32-bit integer limits
if (!numbers.every(n => Number.isSafeInteger(n) && n >= 0 && n <= 0xffffffff)) {
return false;
}
const [first, second] = numbers;
// Enforce ITU-T X.660 constraints on the first two arcs
if (first > 2) {
return false;
}
if (first < 2 && second > 39) {
return false;
}
return true;
}As a defense-in-depth measure, security engineers designing cryptographic workflows should avoid performing authorization checks on unparsed string representations prior to serialization. Alternatively, after serializing an OID with asn1.oidToDer(), applications can round-trip the resulting buffer through asn1.derToOid() and verify that the decoded string matches the original input string and satisfies the policy rules.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
node-forge digitalbazaar | < 1.4.1 | 1.4.1 |
| Attribute | Detail |
|---|---|
| Vulnerability ID | CVE-2026-118775 / GHSA-4642-5mcj-2rqw |
| Weakness Class | CWE-190 (Integer Overflow or Wraparound) |
| Affected Component | asn1.oidToDer() in lib/asn1.js (node-forge < 1.4.1) |
| Attack Vector | Network (High Attack Complexity) |
| CVSS v3.1 Score | 3.7 Low (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N) |
| EPSS Score | 0.313% (24.28th Percentile) |
| Impact | Bypass of OID string-based authorization or policy checks via DER aliasing |
| Exploit & KEV Status | PoC Available (Unit Tests) / Not in CISA KEV |
The product performs a calculation that can produce an integer overflow or wraparound, when the logic assumes that the resulting value will always be larger than the original value. This occurs when an integer value is incremented to a value that is too large to store in the associated representation.
CVE-2026-118777 is a prototype pollution vulnerability (CWE-1321) in the Forge JavaScript cryptography and networking library (distributed on npm as node-forge) prior to version 1.4.1. The flaw resides in the forge.http client subsystem, where incoming HTTP cookie names and paths parsed from Set-Cookie response headers are stored directly into standard JavaScript objects without key validation or property isolation. By returning crafted Set-Cookie headers containing keys such as __proto__ or constructor, a malicious HTTP server can inject arbitrary properties into Object.prototype, attach attributes to the global Object constructor, or delete built-in prototype methods to cause an application-wide denial of service.
CVE-2026-119194 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting versions 0.3.3 through 0.6.4. The flaw resides in the schemaless decoding routine ConstructedPayloadDecoderBase._decodeComponentsSchemaless used across BER, CER, and DER codecs. By sending an ASN.1 container populated with child components whose tag identifiers are separated by multiples of CPython's integer hash modulus (sys.hash_info.modulus), an unauthenticated attacker can force worst-case hash collisions in an unbounded Python set, degrading decoding performance from O(N) to O(N^2) and causing a Denial of Service (DoS).
CVE-2026-119195 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting all versions prior to 0.6.5. When decoding constructed strings or indefinite-length ANY types across BER, CER, and certain DER code paths, the payload decoders repeatedly concatenate incoming fragments onto an immutable bytes object using the += operator. Because Python bytes objects cannot be resized in-place, each fragment iteration allocates a new buffer and copies all previously accumulated bytes, resulting in quadratic O(N^2) time and memory copy complexity. An unauthenticated remote attacker can supply a highly fragmented ASN.1 stream to exhaust CPU cycles and stall application worker threads.
An algorithmic complexity vulnerability (CWE-407) in pyasn1 versions prior to 0.6.5 allows unauthenticated attackers to cause a Denial of Service (DoS) via crafted ASN.1 payloads. The flaw resides in the binary-encoded ASN.1 REAL mantissa deserialization routine within RealPayloadDecoder.valueDecoder. By performing iterative byte-string slicing and repeated bitwise shifts on a growing arbitrary-precision integer, the decoder exhibits quadratic time and memory allocation complexity relative to the mantissa length. Supplying a BER-, CER-, or DER-encoded payload with a large REAL mantissa causes excessive CPU consumption and blocks application worker processes.
A vulnerability classified under CWE-407 (Inefficient Algorithmic Complexity) exists in the pyasn1 library prior to version 0.6.5. When parsing constructed ASN.1 BIT STRING payloads under BER, CER, or schema-driven DER encoding rules, the BitStringPayloadDecoder repeatedly shifts and copies the entire accumulated bit value for each incoming fragment. Processing inputs with a large number of small constructed fragments induces quadratic CPU consumption, allowing unauthenticated remote attackers to cause a denial of service via resource exhaustion during the initial decoding phase.
CVE-2026-119209 is an uncontrolled resource consumption vulnerability in the pypdf Python library prior to version 6.21.3. The flaw exists within the PDF 1.5 cross-reference (XRef) stream parsing routines in pypdf/_reader.py. Due to the absence of a document-wide entry limit across chained cross-reference streams and improper validation of negative entry counts in the /Index array, an attacker can craft a compact PDF file that triggers excessive memory allocation, resulting in a Denial of Service (DoS) via MemoryError or process termination.