PT-2026-42857 · Pypi · Flask-Security-Too

Published

2026-05-22

·

Updated

2026-05-22

·

CVE-2026-46715

None

No severity ratings or metrics are available. When they are, we'll update the corresponding info on the page.

Summary

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
  • OAuth verified user: attacker@example.com
  • session 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.

PoC

Tested version:
  • Flask-Security-Too 5.8.0
  • tag 5.8.0
  • commit 08288dff6907e413d848a16aaf43fc2c2b2a3b72
Used a minimal Flask app with:
SECURITY OAUTH ENABLE = True
SECURITY OAUTH BUILTIN PROVIDERS = ["github"]
SECURITY FRESHNESS = timedelta(seconds=1)
SECURITY FRESHNESS GRACE PERIOD = timedelta(seconds=0)
SECURITY USERNAME ENABLE = True
SECURITY CHANGE 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:

1. Log in as victim@example.com.
2. Wait until the session is no longer fresh.
3. Confirm POST /change-username is blocked with 401 and
  reauth required=true.
4. Start OAuth verification with POST /login/oauth-verify-start/
  github.
5. Complete the callback with an OAuth identity for
  attacker@example.com.
6. Confirm the session is still for victim@example.com, but fs paa has
  been updated.
7. Retry POST /change-username.
8. The victim user's username is changed successfully.

Observed result:

{
 "pre bypass status": 401,
 "pre bypass reauth required": true,
 "attacker identity": "attacker@example.com",
 "oauth verify response status": 302,
 "post bypass change username status": 200,
 "final email": "victim@example.com",
 "final username": "victimowned1777878574",
 "direct impact 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 current user before updating session["fs paa"].

Improper Authentication

Weakness Enumeration

Related Identifiers

CVE-2026-46715
GHSA-97R5-PG8X-P63P

Affected Products

Flask-Security-Too