Skip to content

DER signature decoder accepts non-minimal integer encodings, so the malleability CVE-2026-23967 describes is still reproducible at 0.5.4

Moderate
JuneAndGreen published GHSA-75qp-p6cx-h553 Aug 4, 2026

Package

npm sm-crypto (npm)

Affected versions

>= 0, <= 0.5.4

Patched versions

0.5.5

Description

CVE-2026-23967 (GHSA-qv7w-v773-3xqm) states its impact as "An attacker can derive a new valid signature for a previously signed message from an existing signature." That is still true on 0.5.4. The fix that shipped added a range check on the decoded integers, but the encoding is what needs checking.

decodeDer in src/sm2/asn1.js reads R and S by their own length bytes and nothing else:

const vIndexR = getStartOfV(input, start)
const lR = getL(input, start)
const vR = input.substr(vIndexR, lR * 2)
...
const r = new BigInteger(vR, 16)

There is no minimal-encoding requirement, no check on the 02 tags, and the outer SEQUENCE length is never compared against the actual input length. doVerifySignature in src/sm2/index.js then range-checks that r and s lie in [1, n-1], but a 00-padded R decodes to the identical in-range integer, so that check passes.

Prepending 00 bytes to the R INTEGER and fixing up the two length bytes therefore yields distinct signature strings that all verify against the same message and public key. Against the published npm package 0.5.4 on Node v26.5.0:

orig   3046022100ace9e76a5279c85c56f3514d929ecab6...  true
pad=1  distinct=true  verifies=true
pad=2  distinct=true  verifies=true
pad=3  distinct=true  verifies=true
pad=8  distinct=true  verifies=true

Because the SEQUENCE length is not validated against the input, the padding is effectively unbounded, so a single signature has many valid encodings. The fixed-width r||s path (der: false) is not affected: prepending changes r, and appending pushes s out of range.

Impact is limited to anything a caller builds on signature-byte identity, such as replay protection keyed on a signature, deduplication, or idempotency tokens. It does not let an attacker sign a new message, which is why I have scored this lower than the original advisory.

A fix would be to require canonical DER on the verify path: reject non-minimal INTEGERs (a leading 00 is only legal when the next byte has its high bit set), check the 02 tags, and require the outer SEQUENCE length to equal the remaining input.

I used AI assistance while reading the code and preparing this report. The reproduction above I ran myself against the published npm package.

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
Low
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N

CVE ID

No known CVE

Weaknesses

Improper Verification of Cryptographic Signature

The product does not verify, or incorrectly verifies, the cryptographic signature for data. Learn more on MITRE.

Credits