Improper Verification of Cryptographic Signature Affecting jose package, versions >=1.0.0 <1.11.13


Severity

Recommended
0.0
medium
0
10

CVSS assessment by Snyk's Security Team. Learn more

Threat Intelligence

Exploit Maturity
Proof of Concept
EPSS
0.09% (1st percentile)

Do your applications use this vulnerable package?

In a few clicks we can analyze your entire application and see what components are vulnerable in your application, and suggest you quick fixes.

Test your applications
  • Snyk IDSNYK-HEX-JOSE-21755370
  • published27 Oct 2026
  • disclosed26 Oct 2026
  • creditPeter Ullrich

Introduced: 26 Oct 2026

NewCVE-2026-65632  (opens in a new tab)
CWE-347  (opens in a new tab)

How to fix?

Upgrade jose to version 1.11.13 or higher.

Overview

Affected versions of this package are vulnerable to Improper Verification of Cryptographic Signature via jose_jwk_kty_ec:verify/4, which splits the JWS signature at byte_size(Signature) div 2 - a length derived from attacker-controlled input rather than the canonical size mandated by RFC 7518 section 3.4. Because crypto:bytes_to_integer/1 ignores leading zero octets, an attacker who holds one valid compact JWS token can prepend or strip zero octets from each half of the R || S signature to produce an unbounded family of distinct byte strings that all pass verification against the same key and payload. This breaks any control keyed on token or signature uniqueness, such as revoke-by-hash, replay caches, or deduplication logic.

PoC

The attached script (`scripts/jose/ecdsa_signature_length_is_never_validated_in_verify_4_signat_2314.exs`, embedded below under "Scripts and Logs") demonstrates the malleability through the library's public verification API.

Threat model: an outside attacker holds one valid, signed JWS compact token (`header.payload.signature`) — exactly what a server hands a client. The attacker does not know the private key and cannot forge new payloads. The only public entry point used is `JOSE.JWS.verify/2`, the trust boundary a real application relies on.

Walkthrough:
1. The victim generates a P-256 key and signs a payload with `alg = ES256`, producing a compact token. The script confirms this honest token verifies: `{true, _, _} = JOSE.JWS.verify(jwk, token)`.
2. The attacker splits the token on `.` and base64url-decodes the signature. For ES256 the signature is `R ‖ S`, each 32 bytes, so it splits at the midpoint into `r` and `s`.
3. For `k` from 1 to 5, the attacker builds a re-padded signature `zeros(k) <> r <> zeros(k) <> s` and re-assembles a token with it. Each forged signature is longer than 64 bytes but still splits evenly, and `crypto:bytes_to_integer/1` strips the injected zeros, so each half decodes back to the original `r` and `s`.
4. Every forged token is fed back through `JOSE.JWS.verify(jwk, t)`.

The script counts how many *distinct* signature byte strings verify against the same key. A strict RFC 7518 verifier accepts exactly 1 (the canonical token); `jose` accepts all five forgeries, so the distinct-verifying-token count is greater than 1 and the script prints a `VERIFIED:` line reporting the number of re-padded signatures accepted. No private key, special configuration, or forged payload is involved — only a single legitimately issued token.

Note: The PoC script demonstrates the malleability through the library's public verification API using a P-256 key and ES256 alg, but states no specific runtime version, operating system, or non-default configuration.

CVSS Base Scores

version 4.0
version 3.1