GHSA-5x38-2vff-x55pHighCVSS 7.4

Forge: X.509 basicConstraints bypass in verifyCertificateChain() via deprecated extension OIDs 2.5.29.10 and 2.5.29.13

Published
October 30, 2026
Last Modified
October 30, 2026

🔗 CVE IDs covered (1)

📋 Description

Summary

node-forge parses certificate extensions with the deprecated OIDs 2.5.29.10 and 2.5.29.13 as basicConstraints, and forge.pki.verifyCertificateChain() used the first extension with that name. A certificate with a forged deprecated-OID extension (cA TRUE) before its genuine basicConstraints extension (cA FALSE) is accepted as a CA certificate, and the same technique can hide a pathLenConstraint. Fixed in node-forge 1.4.1.

Details

lib/oids.js maps the deprecated certificate extension OIDs 2.5.29.10 and 2.5.29.13 to the name basicConstraints, the same name as the RFC 5280 extension 2.5.29.19. certificateExtensionFromAsn1() in lib/x509.js decodes extensions by name, so these extensions are decoded into the same cA and pathLenConstraint fields. cert.getExtension() returns the first extension with a matching name, and pki.verifyCertificateChain() looked up basicConstraints by name.

As a result, a certificate that contains a 2.5.29.10 (or 2.5.29.13) extension with cA TRUE before its genuine basicConstraints extension with cA FALSE is accepted as a CA certificate. The same technique can hide a CA certificate's pathLenConstraint. A deprecated-OID extension on its own also satisfies the check added for GHSA-2328-f5f3-gj25 (CVE-2026-33896) that basicConstraints is present. RFC 5280 section 4.2.1.9 defines basicConstraints only as id-ce 19 (2.5.29.19), and the path validation checks in section 6.1.4 apply to that extension.

The fix makes chain verification look up basicConstraints by OID (cert.getExtension({id: '2.5.29.19'})). This covers both the cA and the pathLenConstraint checks. Certificate parsing, the OID table and cert.getExtension('basicConstraints') are unchanged.

Patches

Fixed in node-forge 1.4.1. Chain verification now uses only the basicConstraints extension with OID 2.5.29.19.

Workarounds

None in node-forge itself. Applications that pass an options.verify callback to pki.verifyCertificateChain() (or verify to forge.tls.createConnection()) can reject chains in which a non-leaf certificate has no 2.5.29.19 extension with cA TRUE, for example with cert.getExtension({id: '2.5.29.19'}). CA operators can avoid copying unrecognized extensions from certificate requests.

Credits

WorkOS Security Team· security@workos.com (@nickcollisson-workos)

Proof of concept

Have a trusted CA issue an end-entity certificate whose extensions are, in this order: a 2.5.29.10 (or 2.5.29.13) extension with the value of a basicConstraints cA TRUE (DER 30 03 01 01 FF), then the CA's own 2.5.29.19 basicConstraints with cA FALSE, and no keyUsage. For example, openssl x509 -req -copy_extensions copy with an extensions file that sets basicConstraints = CA:FALSE copies the extension from the CSR and produces this order. Use that certificate's key to sign a leaf certificate for any name.

On node-forge <= 1.4.0, forge.pki.verifyCertificateChain(caStore, [leaf, eeCert]) accepts the chain, and eeCert.getExtension('basicConstraints') returns the 2.5.29.10 extension with cA true. The same certificate without the deprecated-OID extension is rejected ("Certificate basicConstraints indicates the certificate is not a CA."). A certificate with only the deprecated-OID extension is also accepted.

The fix adds regression tests in tests/unit/x509.js and the reporter's PoC as tests/pocs/ghsa-5x38-2vff-x55p.js.

Impact

Applications that verify certificate chains with forge.pki.verifyCertificateChain(), including TLS peer certificate verification in forge.tls, are affected.

Exploitation requires a CA trusted by the application to issue a certificate that contains the attacker-supplied deprecated-OID extension, typically a CA that copies extensions from certificate signing requests. For example, openssl x509 -req -copy_extensions copy (or copyall) with an extensions file that sets basicConstraints = CA:FALSE and no keyUsage produces such a certificate. If the issuing CA adds a keyUsage extension without keyCertSign, node-forge rejects the chain.

The holder of such a certificate can then issue certificates for any name that pki.verifyCertificateChain() accepts. In forge's own TLS stack this allows impersonating servers (or clients, where client certificates are verified) and intercepting traffic.

🎯 Affected products1

  • npm/node-forge:<= 1.4.0

🔗 References (5)