-
Notifications
You must be signed in to change notification settings - Fork 62
Description
Hi,
I am writing to you from the Security Labs team at Snyk to report a security issue affecting FastAPI SSO, which we identified during a recent research project.
We have identified a vulnerability that can result in a 1-click Account Takeover in applications that use the FastAPI SSO library. (5.7 CVSS v3: AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N)
Description
OAuth state is generated but never validated, so login CSRF becomes trivial. get_login_url simply forwards whichever state string is passed (or auto-fills _generated_state) into the authorization request without persisting it anywhere, server/client-side. [1]
if self.requires_state and not state:
if self._generated_state is None:
warnings.warn(
f"{self.__class__.__name__!r} requires state in the request but none was provided nor "
"generated automatically. Use SSO as a context manager. The login process will most probably fail."
)
state = self._generated_state
request_uri = self.oauth_client.prepare_request_uri(
await self.authorization_endpoint,
redirect_uri=redirect_uri,
state=state,
scope=self._scope,
code_challenge=self._pkce_code_challenge,
code_challenge_method=self._pkce_challenge_method,
**params,
)
fastapi_sso/sso/base.py:303-319
During the callback, the library copies the attacker-supplied state query parameter into _state and proceeds. The random value produced in generate_random_state is never compared to anything. [2]
self._state = request.query_params.get("state")
pkce_code_verifier: Optional[str] = None
if self.uses_pkce:
pkce_code_verifier = request.cookies.get("pkce_code_verifier")
if pkce_code_verifier is None:
warnings.warn(
"PKCE code verifier was not found in the request Cookie. This will probably lead to a login error."
)
return await self.process_login(
code,
request,
params=params,
additional_headers=headers,
redirect_uri=redirect_uri,
pkce_code_verifier=pkce_code_verifier,
convert_response=convert_response,
)
fastapi_sso/sso/base.py:404-420
Also, in the documentation [3], users are advised to use transparent, non-random states, which is in contradiction to the official RFC [4].
[1]
fastapi-sso/fastapi_sso/sso/base.py
Line 274 in da63c19
| async def get_login_url( |
[2]
fastapi-sso/fastapi_sso/sso/base.py
Line 369 in da63c19
| async def verify_and_process( |
[3]
| context the authentication was initiated. It is mostly used to store the url you want your user to be redirected |
[4]
https://www.rfc-editor.org/rfc/rfc6749#section-10.12
Proof of Concept
Let’s think of an app - Awesome_FastAPI_SSO_App. Let’s assume that the Awesome_FastAPI_SSO_App 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.
Then, an attacker can get account takeover inside the app by performing the following actions:
- They start an SSO OAuth flow, but stop it right before making the callback call to Awesome_FastAPI_SSO_App;
- 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 Awesome_FastAPI_SSO_App callback. Because the library doesn’t perform any check of the state token, the callback is processed, the grant code is sent to the provider, and the account linking takes place.
After the GET request is performed, the attacker's SSO account is linked with the victim's Awesome_FastAPI_SSO_App account permanently.
Suggested Fix
Make the state a value tied to the session of the user that initiated the OAuth flow.
I also attached a suggested patch to fix this vulnerability.
We aim to follow an industry-standard 90-day disclosure process. We hope that you are able to align with this and release a fix for this vulnerability, where applicable, in this time frame.
If you have any further questions or would like assistance in mitigating or retesting these vulnerabilities, please let me know.
Thanks,
David