Cloudanix VulnDB

CVE-2026-45363

High · 7.4

ruby-jwt: Empty-key HMAC bypass; cross-language sibling of CVE-2026-44351

CVSS

7.4

EPSS

0.4%

Published

May 18, 2026

CISA due date

—

Cloudanix analysis

Next patch cycle SSVC: Track

Some applications may accept fake login tokens if their token-signing secret is missing or accidentally set to an empty value.

Exploitation
Remote Unauthenticated
Attacker needs
None
Complexity
Moderate
Impact
Full Compromise
Component
Library
Class
Authentication Bypass
Remediation
Upgrade
Effort
Medium
Patch risk
Low

What it actually does

This is an authentication bypass in JWT HMAC verification paths for HS256, HS384, and HS512. If an application verifies a JWT using an empty string key, or if a key-finder returns an empty string, nil value that is coerced to an empty string, or an array containing nil, an attacker can create a valid-looking HMAC-signed token using an empty secret. The verification path accepts the attacker-generated signature because HMAC with an empty key is still deterministic, and the affected implementation does not consistently reject empty HMAC keys before verification. Applications using a non-empty static signing key, or key-finder logic that rejects or raises on unknown keys, are not affected by this specific condition.

Why it matters to the business

An exposed application may trust attacker-created login or authorization tokens, allowing account impersonation, privilege escalation, unauthorized access to APIs, and bypass of normal authentication controls. The risk is highest for services that use dynamic JWT key lookup, key IDs, environment-variable fallbacks, cache lookups, database-backed secrets, or defaults that can resolve to an empty value. This issue is not currently listed in CISA KEV, is not known to be ransomware-associated, and has a low EPSS score, but authentication bypass flaws can become high-priority quickly when simple exploit conditions are identified in internet-facing applications.

How an attack unfolds

An attacker first determines that the target accepts HMAC-signed JWTs, typically HS256, HS384, or HS512. They then craft a JWT header and claims body suitable for the application, such as a chosen subject, role, tenant, scope, or administrative claim. Instead of needing the real signing secret, the attacker signs the JWT using HMAC with an empty key. If the application passes an empty verification key directly, or its key-finder returns an empty value for an unknown or attacker-chosen key ID, the verification routine computes the same empty-key HMAC and accepts the forged token. The attacker then presents the token to protected endpoints and may be treated as the claimed user or role. Exploitation is application-dependent and most likely where missing secrets, cache misses, environment fallbacks, default database values, or permissive hash defaults resolve to an empty string rather than failing closed.

How to fix it

Upgrade the affected JWT library to a fixed release as soon as a patched version is available from the maintainer. No fixed versions were provided in the input, so teams should track the package advisory and pin to the first release that explicitly rejects empty HMAC verification keys. In the meantime, enforce fail-closed key handling in application code: never pass an empty string as a JWT HMAC secret, never coerce missing keys with to_s, do not use ENV fallbacks such as ENV['SECRET'] || '', do not use database defaults of an empty string for signing secrets, and do not use Hash.new('') for key lookup. Key-finder callbacks should return a valid non-empty secret for known key IDs and raise an error or return a rejected nil on misses, rather than falling back to ''. Enable any available HMAC key-length enforcement option, such as enforce_hmac_key_length, and require secrets with adequate entropy and length for HS256, HS384, and HS512. Consider restricting accepted algorithms to the exact expected algorithm and migrating high-risk deployments to asymmetric JWT signing where public-key verification removes shared-secret lookup ambiguity.

How to confirm the fix worked

Review dependency advisories and lockfiles to confirm the JWT package is at a patched version once available. Search application code for JWT.decode, JWT::EncodedToken#verify_signature!, key_finder, keyfinder blocks, empty-string secret defaults, and secret lookup paths that can return ''. Add unit tests that attempt to verify an HS256, HS384, and HS512 token signed with an empty key and assert that verification fails. Test key-finder behavior for unknown kid values and confirm the verifier rejects the token rather than using an empty fallback. Confirm configuration validation fails application startup if JWT secrets are unset, empty, whitespace-only, or below the required length. In runtime validation, send a controlled forged token signed with an empty key to a non-production environment and verify it is rejected with an authentication failure.

Typical attack path

  1. Identify an application endpoint that accepts bearer JWTs for authentication or authorization.
  2. Determine or infer that the application accepts HS256, HS384, or HS512 HMAC-signed tokens.
  3. Create a JWT header, optionally including a kid value likely to miss in the application key lookup path.
  4. Create attacker-controlled claims, such as a privileged role, user identifier, scope, or long expiration time.
  5. Sign the token using the selected HMAC algorithm with an empty key.
  6. Submit the forged token in an Authorization bearer header, cookie, or other JWT-bearing request field.
  7. If the verifier resolves the signing key to an empty value, the signature is accepted and the attacker gains the privileges represented by the forged claims.

How to detect it

Detection is difficult because successfully forged tokens may look like normal authenticated requests unless the application logs JWT metadata and authorization decisions. Start by identifying applications that accept HMAC JWTs and reviewing access logs, authentication middleware logs, API gateway logs, and application security logs for successful requests with bearer tokens that contain HS256, HS384, or HS512 headers but do not correspond to normal issuer activity. Pay particular attention to tokens with unknown or unusual kid values, missing kid values where a kid is normally present, privileged claims not issued by the identity provider, abnormal subject-to-role combinations, unusually long exp values, or requests where the user appears authenticated without a matching login, refresh, or token-issuance event. If tokens were logged or can be recovered from request captures, decode the header and claims without trusting them, then recompute the signature using an empty key for HS256, HS384, or HS512; a match indicates likely exploitation. Also perform source and configuration searches for exposure patterns that could have allowed exploitation, including empty-string secrets, permissive key-finder defaults, cache misses converted with to_s, and environment-variable fallbacks. Where exploitation is suspected, correlate the first suspicious token use with subsequent data access, privilege changes, administrative actions, API key creation, user impersonation, and session persistence activity.

  • JWT.decode(token, "", true, algorithm: 'HS256')
  • JWT.decode(token, "", true, algorithm: 'HS384')
  • JWT.decode(token, "", true, algorithm: 'HS512')
  • JWT.decode(token, nil, true, algorithms: ['HS256'])
  • JWT.decode(token, nil, true, algorithms: ['HS384'])
  • JWT.decode(token, nil, true, algorithms: ['HS512'])
  • JWT::EncodedToken#verify_signature!(key_finder:)
  • redis.get("kid:#{kid}").to_s
  • ENV['SECRET'] || ''
  • Hash.new('')
  • enforce_hmac_key_length: false
  • "alg":"HS256"
  • "alg":"HS384"
  • "alg":"HS512"

What your security team should do

  • Prioritize inventory of applications and services that verify JWTs with HS256, HS384, or HS512, especially Ruby-based services and services using dynamic key lookup by kid.
  • Search source code and configuration for empty-string JWT secrets, ENV fallbacks, cache lookups using to_s, database defaults of '', Hash.new(''), and key-finder callbacks that return empty values.
  • Enable strict HMAC key validation and key-length enforcement immediately where supported.
  • Add regression tests that prove empty-key-signed JWTs are rejected for all accepted HMAC algorithms.
  • Patch the JWT dependency as soon as a fixed version is released and ensure dependency pinning prevents reintroduction of vulnerable versions.
  • Review authentication logs for suspicious successful requests using JWTs with unknown kid values, unusual algorithms, unexpected privileged claims, or tokens issued outside normal identity-provider flows.
  • Rotate JWT signing secrets if there is evidence that forged tokens may have been accepted, and invalidate active sessions where practical.
  • Consider adding centralized JWT verification middleware that rejects empty, missing, weak, or dynamically unresolved keys before calling the JWT library.

The record

`JWT.decode(token, '', true, algorithm: 'HS256')` accepts an attacker-forged token. `OpenSSL::HMAC.digest('SHA256', '', payload)` returns a valid digest under an empty key, and no `raise InvalidKeyError if key.empty?` precondition exists in the HMAC algorithm. ``` JWT.decode(token, "", true, algorithm: 'HS256') -> JWA::Hmac.verify(verification_key: "", ...) -> OpenSSL::HMAC.digest('SHA256', "", signing_input) == signature ``` The same path is reached when a keyfinder block or key_finder: argument returns "", nil, or an array containing nil for an unknown key. JWT::Decode#find_key only rejects literal nil and empty arrays, and JWT::JWA::Hmac silently coerces nil to "" (signing_key ||= '') before signing. ``` JWT.decode(token, nil, true, algorithms: ['HS256']) { |_h| "" } -> find_key returns "" # "" && !Array("").empty? == true -> JWA::Hmac.verify(verification_key: "", ...) -> verifies ``` Common application patterns that produce the unsafe value: `redis.get("kid:#{kid}").to_s`, ORM string columns with `default: ''`, `ENV['SECRET'] || '', Hash.new('')` lookups, [primary, fallback] where fallback may be nil. Applications passing a non-empty static key:, or whose keyfinder returns nil / raises on miss, are not affected. The existing `enforce_hmac_key_length` option would block this but defaults to false. On OpenSSL ≥ 3.5 the empty-key HMAC.digest call no longer raises, so the OpenSSL-3.0 rescue in JWA::Hmac#sign does not fire. Affects HS256/HS384/HS512 via both JWT.decode (positional key and block keyfinder) and `JWT::EncodedToken#verify_signature!(key_finder:)`

Affected packages

Ecosystem Package Introduced Fixed

Related vulnerabilities