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-23965
7.50.01%

Signed, Sealed, Forged: The SM2 Logic Bomb in sm-crypto

Alon Barad
Alon Barad
Software Engineer

Feb 15, 2026·7 min read·15 visits

PoC Available

Executive Summary (TL;DR)

The 'sm-crypto' library (versions < 0.4.0) defaulted the 'hash' parameter to false during signature verification. This allowed attackers to supply a raw mathematical integer as the message, enabling a trivial algebraic signature forgery. Attackers could generate valid signatures for arbitrary public keys, bypassing authentication and integrity checks.

A critical logic flaw in the popular 'sm-crypto' library allowed attackers to bypass SM2 digital signature verification by exploiting a permissive default configuration. By defaulting to 'raw' mode instead of hashing inputs, the library enabled mathematical signature forgery, allowing unauthorized actors to masquerade as any user without possessing the private key.

The Hook: National Standards, Global Failures

Cryptography is hard. Implementing cryptography in JavaScript is harder. Implementing specific national standards in JavaScript while ignoring secure defaults is a recipe for disaster. Enter sm-crypto, a widely used npm package implementing China's National Standard algorithms (SM2, SM3, SM4). For developers working with Chinese compliance requirements or integrating with systems like WeChat Pay or government gateways, this library is often the path of least resistance.

But here is the thing about "least resistance": it often leads to a cliff. In early 2026, researchers discovered that sm-crypto had a gaping hole in its SM2 Digital Signature Algorithm (DSA) implementation. It wasn't a buffer overflow, and it wasn't a side-channel leak. It was a simple, boring, devastating default parameter configuration.

For years, the library operated on a "trust the developer" model. If you didn't explicitly tell the library to hash your message before verifying the signature, it assumed you had already done the math yourself. This sounds like a convenient feature for power users, but in the wild, it meant that the library was accepting raw integers where it should have been demanding message digests. This subtle distinction turned the cryptographic fortress of SM2 into a bouncy castle.

The Flaw: A Question of defaults

To understand the gravity of CVE-2026-23965, we need to look at how Elliptic Curve Digital Signature Algorithms (ECDSA) and their cousins like SM2 work. In a proper verification flow, the verifier takes a message $M$, hashes it (usually with SM3) to get a digest $e$, and then uses the public key to verify that the signature $(r, s)$ corresponds to that digest.

The math relies on the fact that $e$ is a one-way hash of the message. You cannot reverse-engineer a message that produces a specific $e$. However, in sm-crypto versions prior to 0.4.0, the doVerifySignature function had an optional options object. Inside that object was a property called hash. If you didn't provide it, it defaulted to... false.

When hash is false, the library treats the input message string not as a text to be hashed, but effectively as the raw digest $e$ itself. This breaks the fundamental binding between the message content and the signature. It transforms the problem from "find a signature for this message" to "find a message that fits this signature." In the world of elliptic curves, the latter is mathematically trivial if you get to define the "message" as an arbitrary integer.

The Math: Algebraic Prestidigitation

Let's get our hands dirty with the algebra. The SM2 verification equation checks if $r \equiv (e + x_1) \pmod n$. Here, $x_1$ is derived from the elliptic curve point calculation involving the signature components $(s, t)$ and the public key $P_A$. Specifically, the point is $(x_1, y_1) = [s]G + [t]P_A$, where $t = r + s$.

Normally, $e$ is fixed (it's the hash of the message). The attacker has to find $r$ and $s$ that satisfy the equation. That requires the private key (solving the Discrete Logarithm Problem). But thanks to the vulnerability, we (the attacker) get to choose $e$. This reverses the dependency chain. Instead of solving for the signature, we can pick a random signature and solve for the message.

Here is the recipe for disaster:

  1. Generate Randomness: Pick random values for $s$ and $t$.
  2. Calculate the Point: Compute $(x_1, y_1) = [s]G + [t]P_A$. We can do this because we know the standard generator $G$ and the victim's public key $P_A$.
  3. Fabricate $r$: Set $r = (t - s) \pmod n$. We now have a valid-looking signature pair $(r, s)$.
  4. Fabricate the Message: Solve the verification equation for $e$. We need $r \equiv e + x_1$, so we simply calculate $e = (r - x_1) \pmod n$.

If we send this calculated value $e$ as our "message" along with the signature $(r, s)$ to a vulnerable sm-crypto instance, the library will skip the hashing, plug our $e$ directly into the equation, and return true. The signature is valid. The check passes. Access granted.

The Code: The Smoking Gun

The vulnerability lived in src/sm2/index.js. It is a classic example of implicit boolean coercion logic going wrong. The developers likely intended to support advanced use cases where the hash is pre-calculated, but by making it the default behavior, they endangered everyone using the "simple" API.

Here is the vulnerable logic in the verification flow:

// Vulnerable Code (Pre-0.4.0)
function doVerifySignature(msg, signHex, publicKey, { der, hash, userId } = {}) {
  // ... parsing logic ...
 
  // If 'hash' is undefined, it evaluates to falsy.
  // The library assumes 'msg' is already the digest 'e'
  if (hash) {
     msg = sm3(msg); // This step was skipped by default!
  }
  
  // ... proceed to elliptic curve math using raw 'msg'
}

The fix was painfully simple. In commit 85295a859d0766222d12ce2be3e6fce7b438b510, the maintainers changed the default destructuring assignment to enforce hashing by default.

// Patched Code (0.4.0)
function doVerifySignature(msg, signHex, publicKey, { der, hash = true, userId } = {}) {
  // Now, if 'hash' is undefined, it defaults to TRUE.
  // The input 'msg' is forced through SM3.
}

This single boolean flip restored the security model. Now, if an attacker sends the calculated garbage value $e$, the library hashes it ($SM3(e)$), resulting in a completely different value $e'$, causing the verification equation to fail ($r \neq e' + x_1$).

The Exploit: From Math to Mayhem

So, how does a hacker weaponize this? You might think, "But the forged message $e$ is just a random large integer! How is that useful?" In many modern web applications, format matters. A backend expecting a JSON payload isn't going to process a raw BigInt.

However, this is where the "Prefix Attack" comes into play. The attacker can iterate. Instead of picking one random pair of $(s, t)$, they can pick millions. They keep generating $(s, t)$ pairs, calculating the resulting $e$, and converting that $e$ to a hex string or byte array. They look for an $e$ that starts with specific bytes—for example, the hex encoding of {"admin":true.

Because the message space is huge and the verification logic is purely mathematical, the attacker can brute-force the randomness until the "garbage" message $e$ aligns with the target application's parsing logic. If the application parser is lenient (ignoring trailing garbage after a valid JSON object), the attacker wins.

Attack Scenario:

  1. Target: A Node.js microservice using sm-crypto to verify API requests signed by a master server.
  2. Recon: Attacker grabs the master server's Public Key from a metadata endpoint.
  3. Forge: Attacker runs a script locally to generate a forged signature $(r, s)$ and a message $e$ that starts with {"cmd":"shutdown"....
  4. Fire: Attacker sends the forged payload to the microservice.
  5. Impact: The sm-crypto library validates the signature. The application parses the JSON. The server shuts down.

The Mitigation: Stop the Bleeding

If you are using sm-crypto, drop what you are doing and check your package.json. If you see a version starting with 0.1, 0.2, or 0.3, you are vulnerable. The remediation is straightforward: Update to version 0.4.0 immediately.

However, software supply chains are messy. If you cannot update immediately (perhaps due to a frozen dependency tree in a legacy banking app), you must manually intervene in every call to doVerifySignature and doSignature. You must explicitly pass { hash: true } in the options object.

// WORKAROUND for older versions
const sm2 = require('sm-crypto').sm2;
const valid = sm2.doVerifySignature(
  msg, 
  sig, 
  publicKey, 
  { hash: true } // FORCE THIS
);

Going forward, this serves as a stark reminder for library maintainers: Secure defaults are not optional. A crypto library should never assume the user knows what they are doing. Always assume the user is tired, under-caffeinated, and about to pass a raw string into a mathematical engine.

Official Patches

JuneAndGreen (GitHub)Commit fixing the default hash parameter

Fix Analysis (1)

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

sm-crypto < 0.4.0Node.js applications using SM2 signatures via sm-cryptoFrontend applications using sm-crypto for client-side signing

Affected Versions Detail

Product
Affected Versions
Fixed Version
sm-crypto
JuneAndGreen
< 0.4.00.4.0
AttributeDetail
CWE IDCWE-347
Attack VectorNetwork
CVSS Score7.5 (High)
EPSS Probability0.008%
ImpactSignature Forgery / Integrity Compromise
Exploit StatusPoC Available (Mathematical)

MITRE ATT&CK Mapping

T1553Subvert Trust Controls
Defense Evasion
T1606Forge Web Credentials
Credential Access
CWE-347
Improper Verification of Cryptographic Signature

Improper Verification of Cryptographic Signature

Known Exploits & Detection

GitHub AdvisoryMathematical procedure for SM2 signature forgery

Vulnerability Timeline

Fix commit pushed to GitHub
2026-01-20
CVE-2026-23965 assigned
2026-01-21
Public disclosure via GitHub Advisory
2026-01-22

References & Sources

  • [1]GHSA-hpwg-xg7m-3p6m Advisory
  • [2]NVD - CVE-2026-23965

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.