CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-23967
7.50.01%

The Doppelgänger Signature: Inside CVE-2026-23967

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 15, 2026·5 min read·9 visits

No Known Exploit

Executive Summary (TL;DR)

The sm-crypto library failed to strictly enforce canonical ranges for SM2 signatures. Attackers can take an existing valid signature `(r, s)` and generate a new valid signature `(r, s')` by exploiting modular arithmetic (e.g., adding the curve order `n` to `s`). While they cannot forge signatures for *new* messages, they can change the binary representation of existing signatures, breaking systems that rely on unique signature hashes (like transaction IDs).

A signature malleability vulnerability in the popular sm-crypto library allows attackers to mutate valid SM2 signatures into new, mathematically valid signatures without access to the private key. This flaw creates potential integrity issues for applications relying on signature uniqueness, such as blockchain ledgers or deduplication systems.

The Hook: Cryptographic Shapeshifting

In the world of cryptography, we generally assume a one-to-one relationship between a specific signature blob and the mathematical proof it represents. If I sign a document, that signature is the digital seal. If you change a single bit of that seal, it should break. That's the theory. In practice, implementation details are where dreams go to die.

sm-crypto is the de facto JavaScript implementation for the Chinese National Standard (Guobiao) cryptographic algorithms: SM2 (ECC), SM3 (Hashing), and SM4 (Block Cipher). It's widely used in financial tech and government-adjacent applications within the Node.js ecosystem.

CVE-2026-23967 is a "Signature Malleability" vulnerability. It sounds academic, almost harmless. "So what if I can change the signature? I can't forge it!" you might say. But in systems where the signature's hash is used as a unique identifier—like blockchain transaction IDs or audit logs—malleability is a nightmare. It allows an attacker to take your valid transaction, clone it into a mathematically identical but binary-distinct "evil twin," and potentially trick the system into processing the wrong one or messing up accounting logic. It's not about stealing the key; it's about gaslighting the database.

The Flaw: When Math Meets Reality

To understand this bug, we have to look at how SM2 signatures work. An SM2 signature consists of two integers: (r, s). The verification involves checking if these numbers satisfy a specific elliptic curve equation involving the signer's public key.

The math operates in a finite field modulo n (the order of the curve group). In modular arithmetic, numbers wrap around. Ideally, a signature implementation should force r and s to be in the canonical range [1, n-1].

The vulnerability in sm-crypto stems from a failure to enforce this strict range. It likely allowed non-canonical representations of s. For example, if s is a valid solution, then s + n is often mathematically equivalent in the verification equation because the first step usually involves a modulo operation (s + n) mod n == s.

By accepting s' = s + n (or other non-canonical forms depending on the exact implementation oversight), the library validates the signature. However, the byte representation of s and s' is totally different. The computer sees 0x12... vs 0x99.... If your application hashes these bytes to create a "Transaction ID," you now have two valid IDs for the exact same action. The cryptography works, but the application logic crumbles.

The Code: The Missing Boundary Check

Let's look at what the vulnerable code conceptually does versus what it should do. In elliptic curve libraries, the verify function usually looks something like this:

// Vulnerable Logic (Conceptual)
function verify(msg, signature, publicKey) {
    const r = signature.r;
    const s = signature.s;
    
    // The missing check:
    // We proceed directly to math without checking if s > n
    const t = (r + s) % n;
    if (t === 0) return false;
    
    const point = G.mul(s).add(publicKey.mul(t));
    // ... rest of verification
}

The fix introduced in version 0.3.14 is boring but critical. It forces the inputs to be strictly within the group order n. It slams the door on the s + n trick.

// Patched Logic
function verify(msg, signature, publicKey) {
    const r = signature.r;
    const s = signature.s;
 
    // STRICT RANGE ENFORCEMENT
    if (r.cmp(1) < 0 || r.cmp(n) >= 0) return false;
    if (s.cmp(1) < 0 || s.cmp(n) >= 0) return false;
 
    const t = (r + s) % n;
    // ... rest of verification
}

By adding those cmp(n) >= 0 checks, the library ensures that there is only one valid byte representation for every mathematical signature. The "evil twin" is now rejected as invalid input.

The Exploit: The Zombie Transaction

How do we weaponize this? Let's assume you are attacking a ledger system that uses sm-crypto. This system creates a TxID by hashing the signed payload.

The Scenario:

  1. Alice sends 100 coins to Bob. She signs the message m. Her signature is (r, s).
  2. The TxID is SHA256(m || r || s).
  3. Mallory (the attacker) sees this transaction in the memory pool (waiting to be confirmed).
  4. Mallory takes s and calculates s' = s + n.
  5. Mallory broadcasts a modified transaction: m with signature (r, s').

The Result: Both transactions are valid. They spend the same inputs. But they have different hashes (TxID_1 vs TxID_2).

If the network processes Mallory's transaction first, Alice's original transaction is rejected (double spend). However, Alice's wallet is watching for TxID_1. It never confirms. Alice thinks the payment failed. She might resend the payment, paying Bob twice. Meanwhile, the first payment did go through, just under TxID_2.

This is a classic "Transaction Malleability" attack, famously plaguing Bitcoin exchanges like Mt.Gox in the early days (though via DER encoding issues there, the principle is identical).

The Fix: Strictness is Security

The remediation is straightforward: upgrade sm-crypto to version 0.3.14 or later. This version introduces the strict range checks required to prevent malleability.

If you cannot upgrade immediately, you must implement the check in your application layer before passing the signature to the library:

const n = new BigInteger('...'); // SM2 curve order
if (signature.s.greaterThanOrEqualTo(n)) {
    throw new Error('Signature malleability detected: s value non-canonical');
}

This acts as a bouncer at the door, rejecting the bloated s values before the vulnerable library processes them.

Official Patches

GitHubOfficial Advisory GHSA-qv7w-v773-3xqm

Technical Appendix

CVSS Score
7.5/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
EPSS Probability
0.01%
Top 99% most exploited

Affected Systems

Node.js applications using sm-crypto < 0.3.14Financial systems using SM2 for transaction signingRegulatory compliance software using Chinese National Standards

Affected Versions Detail

Product
Affected Versions
Fixed Version
sm-crypto
JuneAndGreen
< 0.3.140.3.14
AttributeDetail
CWECWE-347 (Improper Verification of Cryptographic Signature)
CVSS7.5 (High)
ImpactIntegrity / Signature Malleability
Attack VectorNetwork (AV:N)
EPSS Score0.00008 (Low)
Patch StatusFixed in v0.3.14

MITRE ATT&CK Mapping

T1553Subvert Trust Controls
Defense Evasion
CWE-347
Improper Verification of Cryptographic Signature

The software verifies a cryptographic signature but does not verify that the signature is in a unique, canonical format, allowing malleability.

Known Exploits & Detection

N/ANo public PoC available, but the attack vector is mathematically trivial.

Vulnerability Timeline

GHSA Published
2026-01-20
CVE Assigned
2026-01-22
CVSS Score Updated
2026-01-26

References & Sources

  • [1]GHSA Advisory
  • [2]NVD CVE Record

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.