chore(deps): update dependency authlib to v1.6.9 [security]#299
Open
chore(deps): update dependency authlib to v1.6.9 [security]#299
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
1.6.8->1.6.9GitHub Vulnerability Alerts
CVE-2026-27962
Description
Summary
A JWK Header Injection vulnerability in
authlib's JWS implementation allows an unauthenticatedattacker to forge arbitrary JWT tokens that pass signature verification. When
key=Noneis passedto any JWS deserialization function, the library extracts and uses the cryptographic key embedded
in the attacker-controlled JWT
jwkheader field. An attacker can sign a token with their ownprivate 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.pyVulnerable method:
JsonWebSignature._prepare_algorithm_key()Lines: 272–273
When
key=Noneis passed tojws.deserialize_compact(),jws.deserialize_json(), orjws.deserialize(), the library checks the JWT header for ajwkfield. If present, it extractsthat value — which is fully attacker-controlled — and uses it as the verification key.
RFC 7515 violations:
jwkheader parameter is "NOT RECOMMENDED" because keysembedded by the token submitter cannot be trusted as a verification anchor.
context, not from the token itself. There is no step in the RFC that permits falling back to
the
jwkheader 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:
When an attacker submits a token with an unknown
kid, the callable legitimately returnsNone.The library then silently falls through to
key = header["jwk"], trusting the attacker's embeddedkey. The developer never wrote
key=None— the library's fallback logic introduced it. The resultlooks like a verified token with no exception raised, making the substitution invisible.
Attack steps:
{"role": "admin"}).jwkheader field.kidto cause the key resolver to returnNone.header["jwk"]for verification — signature passes.PoC
Tested against authlib 1.6.6 (HEAD
a9e4cfee, Python 3.11).Requirements:
Exploit script:
Expected output:
Docker (self-contained reproduction):
Impact
This is an authentication and authorization bypass vulnerability. Any application using authlib's
JWS deserialization is affected when:
key=Noneis passed directly, orNonefor 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
jwkheader 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:Recommended safe replacement — raise explicitly when no key is resolved:
CVE-2026-28490
1. Executive Summary
A cryptographic padding oracle vulnerability was identified in the Authlib Python library
concerning the implementation of the JSON Web Encryption (JWE)
RSA1_5key managementalgorithm. Authlib registers
RSA1_5in its default algorithm registry without requiringexplicit opt-in, and actively destroys the constant-time Bleichenbacher mitigation that
the underlying
cryptographylibrary implements correctly.When
cryptographyencounters an invalid PKCS#1 v1.5 padding, it returns a randomizedbyte string instead of raising an exception — the correct behavior per RFC 3218 §2.3.2.
Authlib ignores this contract and raises
ValueError('Invalid "cek" length')immediatelyafter decryption, before reaching AES-GCM tag validation. This creates a clean, reliable
Exception Oracle:
cryptographyreturns random bytes → Authlib length check fails→
ValueError: Invalid "cek" lengthfails →
InvalidTagThis oracle is active by default in every Authlib installation without any special
configuration by the developer or the attacker. The three most widely used Python web
frameworks — Flask, Django, and FastAPI — all expose distinguishable HTTP responses for
these two exception classes in their default configurations, requiring no additional
setup to exploit.
Empirically confirmed on authlib 1.6.8 + cryptography 46.0.5:
2. Technical Details & Root Cause
2.1 Vulnerable Code
File:
authlib/jose/rfc7518/jwe_algs.py2.2 Root Cause — Active Mitigation Destruction
cryptography46.0.5 implements the Bleichenbacher mitigation correctly at the librarylevel. When PKCS#1 v1.5 padding validation fails, it does not raise an exception.
Instead it returns a randomized byte string (empirically observed: 84 bytes for a
2048-bit RSA key). The caller is expected to pass this fake key to the symmetric
decryptor, where MAC/tag validation will fail in constant time — producing an error
indistinguishable from a MAC failure on a valid padding.
Authlib does not honor this contract. The length check on the following line detects
that 84 bytes != 16 bytes (128-bit CEK for A128GCM) and raises
ValueError('Invalid "cek" length')immediately. This exception propagates before AES-GCM is ever reached,creating two execution paths with observable differences:
The single line
raise ValueError('Invalid "cek" length')is the complete root cause.Removing the raise and replacing it with a silent random CEK fallback eliminates both
the exception oracle and any residual timing difference.
2.3 Empirical Confirmation
All results obtained on authlib 1.6.8 / cryptography 46.0.5 / Linux x86_64
running the attached PoC (
poc_bleichenbacher.py):Note on timing: The 0.287ms delta is within the noise margin (stdev ~1ms across
50 iterations) and is not claimed as a reliable standalone timing oracle. The exception
oracle is the primary exploitable vector and does not require timing measurement.
3. Default Framework Behavior — Why This Is Exploitable Out of the Box
A potential objection to this report is that middleware or custom error handlers could
normalize exceptions to a single HTTP response, eliminating the observable discrepancy.
This section addresses that objection directly.
The oracle is active in default configurations of all major Python web frameworks.
No special server misconfiguration is required. The following demonstrates the default
behavior for Flask, Django, and FastAPI — the three most widely deployed Python web
frameworks — when an unhandled exception propagates from a route handler:
Flask (default configuration)
Flask's default error handler returns the exception message in the response body for
debug mode, and an empty 500 for production. However, even in production, the response
body content differs between
ValueError(which has a message) andInvalidTag(which has no message), leaking the oracle through response body length.
FastAPI (default configuration)
FastAPI normalizes both to HTTP 500 in production. However, FastAPI's default
RequestValidationErrorandHTTPExceptionhandlers do not catch arbitrary exceptions,so the distinguishable stack trace is logged — and in many deployments, error monitoring
tools (Sentry, Datadog, etc.) expose the exception class to operators, enabling oracle
exploitation by an insider or via log exfiltration.
Django REST Framework (default configuration)
Summary: Even in cases where HTTP status codes are normalized, the oracle persists
through response body differences, response timing, or error monitoring infrastructure.
The RFC 3218 §2.3.2 requirement exists precisely because any observable difference —
regardless of channel — is sufficient for a Bleichenbacher attack. The library is
responsible for eliminating the discrepancy at the source, not delegating that
responsibility to application developers.
This is a library-level vulnerability. Requiring every application developer to
implement custom exception normalization to compensate for a cryptographic flaw in
the library violates the principle of secure defaults. The fix must be in Authlib.
4. Specification Violations
RFC 3218 — Preventing the Million Message Attack on CMS
Section 2.3.2 (Mitigation):
This is an absolute requirement with no exceptions for "application-level mitigations."
Authlib violates this by raising a different exception class for padding failures than
for MAC failures. The
cryptographylibrary already implements the correct mitigationfor this exact scenario — Authlib destroys it with a single length check.
RFC 7516 — JSON Web Encryption
Section 9 (Security Considerations):
Authlib enables exactly this scenario. Two structurally different encrypted keys
(one with invalid padding, one with valid padding but wrong CEK) produce two different
exception classes. This is the exact condition RFC 7516 §9 warns against.
5. Attack Scenario
The attacker identifies an Authlib-powered endpoint that decrypts JWE tokens.
Because
RSA1_5is in the default registry, no special server configurationis required.
The attacker obtains the server RSA public key — typically available via the
JWKS endpoint (
/.well-known/jwks.json), which is standard in OIDC deployments.The attacker crafts JWE tokens with the
RSA1_5algorithm and submits a streamof requests to the endpoint, manipulating the
ekcomponent per Bleichenbacher'salgorithm.
The server responds with observable differences between the two paths:
ValueErrorpath → distinguishable response (exception message, timing, orerror monitoring artifact)
InvalidTagpath → different distinguishable responseBy observing these oracle responses across thousands of requests, the attacker
geometrically narrows the PKCS#1 v1.5 plaintext boundaries until the CEK is
fully recovered.
With the CEK recovered:
Prerequisites:
RSA1_5(active by default)(present by default in Flask, Django, FastAPI without custom error handling)
duration but does not prevent it)
6. Remediation
6.1 Immediate — Remove RSA1_5 from Default Registry
Remove
RSA1_5from the defaultJWE_ALG_ALGORITHMSregistry. Users requiringlegacy RSA1_5 support should explicitly opt-in with a documented security warning.
This eliminates the attack surface for all users not requiring this algorithm.
6.2 Code Fix — Restore Constant-Time Behavior
The
unwrapmethod must never raise an exception that distinguishes padding failurefrom MAC failure. The length check must be replaced with a silent random CEK fallback,
preserving the mitigation that
cryptographyimplements.Suggested Patch (
authlib/jose/rfc7518/jwe_algs.py):Result: Both paths return a CEK of the correct length. AES-GCM tag validation
fails for both, producing
InvalidTagin both cases. The exception oracle iseliminated. Empirically validated via TEST 5 of the attached PoC.
7. Proof of Concept
Setup:
See attached
poc_bleichenbacher.py. All 5 tests run against the real installedauthlib module without mocks.
Confirmed Output (authlib 1.6.8 / cryptography 46.0.5 / Linux x86_64):
Code
Output
CVE-2026-28498
1. Executive Summary
A critical library-level vulnerability was identified in the Authlib Python library concerning the validation of OpenID Connect (OIDC) ID Tokens. Specifically, the internal hash verification logic (
_verify_hash) responsible for validating theat_hash(Access Token Hash) andc_hash(Authorization Code Hash) claims exhibits a fail-open behavior when encountering an unsupported or unknown cryptographic algorithm.This flaw allows an attacker to bypass mandatory integrity protections by supplying a forged ID Token with a deliberately unrecognized
algheader parameter. The library intercepts the unsupported state and silently returnsTrue(validation passed), inherently violating fundamental cryptographic design principles and direct OIDC specifications.2. Technical Details & Root Cause
The vulnerability resides within the
_verify_hash(signature, s, alg)function inauthlib/oidc/core/claims.py:When an unsupported algorithm string (e.g.,
"XX999") is processed by the helper functioncreate_half_hashinauthlib/oidc/core/util.py, the internalgetattr(hashlib, hash_type, None)call fails, and the function correctly returnsNone.However, instead of triggering a
Fail-Closedcryptographic state (raising an exception or returningFalse), the_verify_hashfunction misinterprets theNonereturn value and explicitly returnsTrue.Because developers rely on the standard
.validate()method provided by Authlib'sIDTokenclass—which internally calls this flawed function—there is no mechanism for the implementing developer to prevent this bypass. It is a strict library-level liability.3. Attack Scenario
This vulnerability exposes applications utilizing Hybrid or Implicit OIDC flows to Token Substitution Attacks.
access_token) or Authorization Code (code) with a malicious or mismatched one.algparameter to an arbitrary, unsupported value (e.g.,{"alg": "CUSTOM_ALG"}).at_hashorc_hashclaims."CUSTOM_ALG"is unsupported byhashlib,create_half_hashreturnsNone._verify_hashreceivesNoneand silently returnsTrue.4. Specification & Standards Violations
This explicit fail-open behavior violates multiple foundational RFCs and Core Specifications. A secure cryptographic library MUST fail and reject material when encountering unsupported cryptographic parameters.
OpenID Connect Core 1.0
at_hashClaim, the Client MUST verify that the hash value of the Access Token matches the value of theat_hashClaim." Silencing the validation check natively contradicts this absolute requirement.c_hashClaim.IETF JSON Web Token (JWT) Best Current Practices (BCP)
IETF JSON Web Signature (JWS)
Truefor anUnsupportedAlgorithmstate, Authlib violates robust application security logic.5. Remediation Recommendation
The
_verify_hashfunction must be patched to enforce aFail-Closedposture. If an algorithm is unsupported and cannot produce a hash for comparison, the validation must fail immediately.Suggested Patch (
authlib/oidc/core/claims.py):6. Proof of Concept (PoC)
The following standalone script mathematically demonstrates the vulnerability across the Root Cause, Implicit Flow (
at_hash), Hybrid Flow (c_hash), and the entire attack surface. It utilizes Authlib's own validation logic to prove the Fail-Open behavior.```bashOutput
Release Notes
authlib/authlib (authlib)
v1.6.9Compare Source
Full Changelog: authlib/authlib@v1.6.8...v1.6.9
Changes in
josemodulejwkautomaticallyES256Kinto default jwt algorithmscekwhenceklength doesn't matchConfiguration
📅 Schedule: Branch creation - "" in timezone UTC, Automerge - At any time (no schedule defined).
🚦 Automerge: Enabled.
♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR has been generated by Renovate Bot.