From aa9a3a8136528d4075c9b564c14b85047e5c4ecd Mon Sep 17 00:00:00 2001 From: galgolamiel Date: Mon, 9 Mar 2026 15:10:07 +0200 Subject: [PATCH] Improve GHSA-fg6f-75jq-6523 --- .../GHSA-fg6f-75jq-6523.json | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/advisories/github-reviewed/2026/01/GHSA-fg6f-75jq-6523/GHSA-fg6f-75jq-6523.json b/advisories/github-reviewed/2026/01/GHSA-fg6f-75jq-6523/GHSA-fg6f-75jq-6523.json index 1c328c2be46d7..011a1b53ef616 100644 --- a/advisories/github-reviewed/2026/01/GHSA-fg6f-75jq-6523/GHSA-fg6f-75jq-6523.json +++ b/advisories/github-reviewed/2026/01/GHSA-fg6f-75jq-6523/GHSA-fg6f-75jq-6523.json @@ -6,8 +6,8 @@ "aliases": [ "CVE-2025-68158" ], - "summary": "Authlib has 1-click Account Takeover vulnerability", - "details": "I am writing to you from the Security Labs team at Snyk to report a security issue affecting Authlib, which we identified during a recent research project.\n\nWe have identified a vulnerability that can result in a 1-click Account Takeover in applications that use the Authlib library. (5.7 CVSS v3: AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N)\n\n**Description**\n\nCache-backed state/request-token storage is not tied to the initiating user session, so CSRF is possible for any attacker that has a valid state (easily obtainable via an attacker-initiated authentication flow). When a cache is supplied to the OAuth client registry, `FrameworkIntegration.set_state_data` writes the entire state blob under `_state_{app}_{state},` and `get_state_data` ignores the caller’s session altogether. \\[1\\]\\[2\\]\n\n```py\n def _get_cache_data(self, key):\n value = self.cache.get(key)\n if not value:\n return None\n try:\n return json.loads(value)\n except (TypeError, ValueError):\n return None\n[snip]\n def get_state_data(self, session, state):\n key = f\"_state_{self.name}_{state}\"\n if self.cache:\n value = self._get_cache_data(key)\n else:\n value = session.get(key)\n if value:\n return value.get(\"data\")\n return None\n```\n\n*authlib/integrations/base\\_client/framework\\_integration.py:12-41*\n\nRetrieval in authorize\\_access\\_token therefore succeeds for whichever browser presents that opaque value, and the token exchange proceeds with the attacker’s authorization code. \\[3\\]\n\n```py\n def authorize_access_token(self, **kwargs):\n \"\"\"Fetch access token in one step.\n\n :return: A token dict.\n \"\"\"\n params = request.args.to_dict(flat=True)\n state = params.get(\"oauth_token\")\n if not state:\n raise OAuthError(description='Missing \"oauth_token\" parameter')\n\n data = self.framework.get_state_data(session, state)\n if not data:\n raise OAuthError(description='Missing \"request_token\" in temporary data')\n\n params[\"request_token\"] = data[\"request_token\"]\n params.update(kwargs)\n self.framework.clear_state_data(session, state)\n token = self.fetch_access_token(**params)\n self.token = token\n return token\n```\n\n*authlib/integrations/flask\\_client/apps.py:57-76*\n\nThis opens up the avenue for Login CSRF for apps that use the cache-backed storage. Depending on the dependent app’s implementation (whether it somehow links accounts in the case of a login CSRF), this could lead to account takeover.\n\n\\[1\\] [https://github.com/authlib/authlib/blob/260d04edee23d8470057ea659c16fb8a2c7b0dc2/authlib/integrations/flask\\_client/apps.py\\#L35](https://github.com/authlib/authlib/blob/260d04edee23d8470057ea659c16fb8a2c7b0dc2/authlib/integrations/flask_client/apps.py#L35)\n\n\\[2\\] [https://github.com/authlib/authlib/blob/260d04edee23d8470057ea659c16fb8a2c7b0dc2/authlib/integrations/base\\_client/framework\\_integration.py\\#L33](https://github.com/authlib/authlib/blob/260d04edee23d8470057ea659c16fb8a2c7b0dc2/authlib/integrations/base_client/framework_integration.py#L33)\n\n\\[3\\] [https://github.com/authlib/authlib/blob/260d04edee23d8470057ea659c16fb8a2c7b0dc2/authlib/integrations/flask\\_client/apps.py\\#L57](https://github.com/authlib/authlib/blob/260d04edee23d8470057ea659c16fb8a2c7b0dc2/authlib/integrations/flask_client/apps.py#L57)\n\n**Proof of Concept**\n\nLet’s think of an app \\- AwesomeAuthlibApp. Let’s assume that the AwesomeAuthlibApp has internal logic that, when an already logged-in user performs a `callback` request, links the newly provided SSO identity to the already existing user that made the request.\n\nThen, an attacker can get account takeover inside the app by performing the following actions:\n\n1\\. They start an SSO OAuth flow, but stop it right before making the callback call to AwesomeAuthlibApp; \n2\\. The attacker tricks a logged-in user (via phishing, a drive-by attack, etc.) to perform a GET request with the attacker's state value and grant code to the AwesomeAuthlibApp callback. Because Authlib doesn’t check whether the state token is linked to the session performing the callback, the callback is processed, the grant code is sent to the provider, and the account linking takes place.\n\nAfter the GET request is performed, the attacker's SSO account is linked with the victim's AwesomeAuthlibApp account permanently.\n\n**Suggested Fix**\n\nPer the OAuth RFC \\[4\\], the state should be tied to the user’s session to stop exactly such scenarios. One straightforward method of mitigating this issue is to keep storing the state in the session even when caching.\n\nAnother method would be to hash the session ID (or another per-user secret from the session) into the cache key. This way, the state will be stored inside the cache, but it is still linked to the session of the user that initiated the OAuth flow.\n\n[4] https://www.rfc-editor.org/rfc/rfc6749#section-10.12", + "summary": "update version range on security advisory : https://github.com/advisories/GHSA-fg6f-75jq-6523", + "details": "hey,\nplease update version range on security advisory : https://github.com/advisories/GHSA-fg6f-75jq-6523 .\ncurrently version range is <= 1.6.5 .\nThis is not affecting version 0.15.5 - due to the reasons below:\n\nin short - not relevant to this version:\n\nThe state is tied to the user's session\nAn attacker cannot supply a valid state from their own session to be used in the victim's session\nThe get_session_data method uses session.pop() which removes the state from the session, ensuring it's only used once\nmore detailed:\nState is stored in request.session, which is tied to the user's session cookie\nAn attacker cannot access the victim's session (assuming proper session security)\nIf an attacker tricks a victim into visiting the callback with the attacker's state, get_session_data will return None (the victim's session doesn't have that state), and validation fails\nThis is different from the cache-backed vulnerability where state was stored globally and not bound to a user session.\n\nThe architecture differs from the vulnerable pattern described in the CVE:\n\nVersion 0.15.5 uses a simpler state management system:\nset_session_data() and get_session_data() store data in the session directly using keys like _{name}_authlib_{key}_\nCache is only used for OAuth1 request tokens (not OAuth2 state), and even then it stores a session ID in the session and the actual token in cache (see authlib/integrations/flask_client/oauth_registry.py )\nState management:\n1.OAuth2 state is stored directly in session via set_session_data(request, 'state', state)\n2.Retrieved via get_session_data(request, 'state') which pops it from session enforces one-time use.\n3.The session key format includes the client name: _dev_authlib_state_\n4.There is NO cache-backed state storage pattern (Cache is only used for OAuth1 tokens with session-bound keys.)\n\nWhat category best describes your issue?\nFeature request", "severity": [ { "type": "CVSS_V3", @@ -15,6 +15,25 @@ } ], "affected": [ + { + "package": { + "ecosystem": "PyPI", + "name": "authlib" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0.15.6" + }, + { + "fixed": "1.6.6" + } + ] + } + ] + }, { "package": { "ecosystem": "PyPI", @@ -51,10 +70,6 @@ "type": "WEB", "url": "https://github.com/authlib/authlib/commit/2808378611dd6fb2532b189a9087877d8f0c0489" }, - { - "type": "WEB", - "url": "https://github.com/authlib/authlib/commit/7974f45e4d7492ab5f527577677f2770ce423228" - }, { "type": "PACKAGE", "url": "https://github.com/authlib/authlib"