CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Dashboard
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2025-68158
5.70.02%

Authlib's Amnesia: How a Cache Lookup Flaw Led to 1-Click Account Takeover

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 23, 2026·6 min read·8 visits

PoC Available

Executive Summary (TL;DR)

Authlib < 1.6.6 failed to bind OAuth 'state' data to user sessions when using server-side caching (e.g., Redis). Attackers could generate a valid state, send the callback URL to a victim, and hijack the account via Login CSRF.

OAuth is a complex beast, and the `state` parameter is its primary defense against Cross-Site Request Forgery (CSRF). In Authlib versions prior to 1.6.6, a critical logic error occurred when developers opted for performance by storing OAuth states in a backend cache (like Redis or Memcached) instead of cookies. The library failed to cryptographically bind the cached state to the user's browser session. This decoupling allowed attackers to generate a valid OAuth flow, harvest the URL, and trick a victim into consuming it—effectively performing a Login CSRF or account linking attack with a single click.

The Hook: When Performance Kills Security

If you build Python web applications, you probably use Authlib. It is the Swiss Army knife of OAuth and OpenID Connect, powering authentication for thousands of Flask, Django, and Starlette apps. It abstracts away the misery of the OAuth 2.0 handshake, handling the redirect dances and token exchanges so you don't have to.

The cornerstone of OAuth security is the state parameter. It is a random token generated by the client (your app) to prevent CSRF. It says, "I am the one who started this request, and I am the one finishing it." Without it, anyone could force your browser to log in to their account, or worse, link their rogue Google account to your administrative profile.

Here is the twist: Authlib handled this perfectly fine in its default mode (storing state in cookies). But developers love speed. We love scaling. So, we switch the storage backend to Redis or Memcached to keep our cookies small. In Authlib versions before 1.6.6, flipping that switch was like locking your front door but leaving the keys taped to the outside of the window. The library stopped checking who owned the state, and only checked if the state existed.

The Flaw: The Valet Parking Problem

To understand this bug, imagine a valet parking service. Normally, you hand over your keys and get a ticket (the state). When you return, you show your ticket, and the valet checks two things: 1) Is this a valid ticket? and 2) Does the face matching this ticket look like the guy who dropped off the car?

Authlib's cache-backed implementation skipped step 2.

In framework_integration.py, the logic for retrieving state data looked something like this (simplified):

# The Vulnerable Logic
def get_state_data(self, session, state):
    key = f"_state_{self.name}_{state}"
    if self.cache:
        # LOOKUP ONLY BY KEY
        value = self.cache.get(key)
        return value

Do you see the issue? The get_state_data function takes a session object, but it ignores it if a cache is configured. It constructs a lookup key based entirely on the state parameter found in the URL.

This means the state was no longer a secret bound to the user's browser session. It became a global, floating token. If I, the attacker, start a login flow, Authlib writes _state_google_XYZ123 to Redis. If I then send you a link containing state=XYZ123, Authlib looks up that key in Redis, finds it, and says, "Looks legit! Proceed."

The Code: The Smoking Gun

The fix, introduced in commit 2808378611dd6fb2532b189a9087877d8f0c0489, reveals exactly how the maintainers patched the hole. They implemented a "double-binding" strategy. Even if the heavy data lives in Redis, a reference must exist in the user's session cookie.

Here is the critical diff:

  def get_state_data(self, session, state):
      key = f'_state_{self.name}_{state}'
+     # VERIFY THE SESSION HAS THIS KEY
+     if not session.get(key):
+         return None
+
      if self.cache:
          return self.cache.get(key)

Before the patch, the code blindly trusted the cache. After the patch, the code first checks session.get(key). If the user's browser doesn't have the specific cookie indicating they started this flow, the server returns None, and the attack fails. It forces the state to exist in both the shared cache (Redis) and the private session (Cookie).

The Exploit: 1-Click Account Takeover

Let's weaponize this. We are going to perform a classic Login CSRF attack. The goal is to force the victim to log in using the attacker's credentials, or to link the attacker's social profile to the victim's existing account.

The Setup:

  1. Attacker goes to target-app.com/login/google.
  2. Target App generates state=EVIL_STATE, stores it in Redis, and redirects the Attacker to Google.
  3. Attacker captures the URL but pauses. They do not complete the login yet. They now have a valid state sitting in the Target App's Redis cache.

The Trap: 4. Attacker constructs the callback URL: https://target-app.com/authorize?code=ATTACKER_CODE&state=EVIL_STATE. 5. Attacker sends this link to the Victim via email/chat.

The Execution: 6. Victim clicks the link. 7. Target App receives the request. It extracts state=EVIL_STATE. 8. Vulnerable Logic checks Redis for _state_google_EVIL_STATE. It exists! (Because the attacker put it there). 9. Target App logs the user in.

The Payoff: If the application supports "Social Account Linking," and the victim was already logged in, the attacker's Google account is now linked to the victim's profile. The attacker can now log in as the victim at any time using their own Google credentials.

The Impact: Why We Should Panic

While NVD rates this as a Medium (5.7) because it requires user interaction, the practical impact is often Critical. Identity is the perimeter. If I can compromise your identity layer, I don't need an RCE.

In scenarios where this vulnerability is used for Account Linking, the impact is persistent Account Takeover (ATO). The attacker gains a permanent backdoor into the user's account without changing the password.

In Login CSRF scenarios (where the victim is logged in as the attacker), the impact is subtle but dangerous. The victim might upload sensitive documents or credit card details, thinking they are in their own account, effectively handing that data directly to the attacker. For SaaS platforms handling sensitive data, this is a privacy nightmare.

The Fix: Binding the Ghost

The remediation is straightforward: Update Authlib to version 1.6.6 or higher.

If you cannot update immediately, you must modify your FrameworkIntegration or custom OAuth client setup. You need to ensure that whatever storage backend you use, you are cross-referencing the state with the user's current session.

> [!NOTE] > If you are using FileStorage or SQLAlchemy storage for states, check if your implementation relies on FrameworkIntegration. The vulnerability specifically affects the default logic where cache is passed to the registry.

After patching, ensure you rotate all active sessions. The patch changes how states are validated, so pending OAuth flows might fail immediately after deployment (a small price to pay for security).

Official Patches

AuthlibGitHub Commit Diff

Fix Analysis (1)

Technical Appendix

CVSS Score
5.7/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N
EPSS Probability
0.02%
Top 95% most exploited

Affected Systems

Flask applications using Authlib with CacheDjango applications using Authlib with CacheStarlette/FastAPI applications using Authlib with Cache

Affected Versions Detail

Product
Affected Versions
Fixed Version
Authlib
Authlib
< 1.6.61.6.6
AttributeDetail
Vulnerability TypeCWE-352: Cross-Site Request Forgery (CSRF)
Attack VectorNetwork (Pre-generated State)
CVSS v3.15.7 (Medium)
ImpactAccount Takeover / Account Linking
Fixed Version1.6.6
Affected Componentauthlib.integrations.base_client.framework_integration

MITRE ATT&CK Mapping

T1189Drive-by Compromise
Initial Access
T1550.001Application Access Token
Use Alternative Authentication Material
CWE-352
Cross-Site Request Forgery (CSRF)

The application does not verify that the OAuth state parameter is bound to the user's current session, allowing CSRF attacks.

Known Exploits & Detection

ManualThe advisory describes the Login CSRF flow in detail.
NucleiDetection Template Available

Vulnerability Timeline

Patch Committed (v1.6.6)
2025-12-12
CVE Published
2026-01-08

References & Sources

  • [1]GHSA Advisory
  • [2]NVD Entry

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.