Flask-Security-Too 5.8.0's OAuth reauthentication flow can mark a session as fresh after verifying an OAuth account that belongs to a different user.
If an attacker can operate an already-authenticated but stale victim
session, they can complete OAuth verification using their own OAuth
identity. The victim session is then treated as recently
reauthenticated, allowing freshness-protected account actions to
proceed. This was reproduced against the built-in /change-username
route.
### Details
The issue is in the OAuth verification callback.
_oauth_response_common() resolves the OAuth provider identity to a
Flask-Security user:
flask_security/oauth_glue.py:101-108
oauth_verify_response() then accepts any resolved user and updates
the current session freshness timestamp:
flask_security/oauth_glue.py:182-214
flask_security/oauth_glue.py:201-204
The missing check is that the OAuth-resolved user must match the current authenticated session user. In the failing case:
current session user: victim@example.com
attacker@example.comsession marked fresh: yes
So the attacker is not logging in as the victim, but they are satisfying the victim session's reauthentication requirement with a different account.
Tested version:
Flask-Security-Too 5.8.0
5.8.0commit 08288dff6907e413d848a16aaf43fc2c2b2a3b72
Used a minimal Flask app with:
```python SECURITYOAUTHENABLE = True SECURITYOAUTHBUILTINPROVIDERS = ["github"] SECURITYFRESHNESS = timedelta(seconds=1) SECURITYFRESHNESSGRACEPERIOD = timedelta(seconds=0) SECURITYUSERNAMEENABLE = True SECURITYCHANGE_USERNAME = True
The OAuth provider was replaced with a localhost mock provider returning attacker@example.com. This avoids hitting a live third-party provider while still exercising Flask-Security-Too's real OAuth verification handler.
Reproduction steps:
The victim user's username is changed successfully.
Observed result:
{ "prebypassstatus": 401, "prebypassreauthrequired": true, "attackeridentity": "attacker@example.com", "oauthverifyresponsestatus": 302, "postbypasschangeusernamestatus": 200, "finalemail": "victim@example.com", "finalusername": "victimowned1777878574", "directimpact_verified": true }
Note: CSRF was disabled in the local harness only to keep the test focused on the reauthentication check. This is not a CSRF bypass report.
This bypasses Flask-Security-Too's freshness/reauthentication boundary.
Applications using OAuth verification together with freshness- protected account operations may allow a stale victim session to be refreshed using a different user's OAuth account. In my test, this allowed the victim account's username to be changed through Flask- Security-Too's built-in /change-username route.
A likely fix is to reject OAuth verification unless the resolved OAuth user matches currentuser before updating session["fspaa"].
{
"github_reviewed_at": "2026-05-22T17:48:54Z",
"github_reviewed": true,
"cwe_ids": [
"CWE-287"
],
"nvd_published_at": null,
"severity": "MODERATE"
}