GHSA-97r5-pg8x-p63p
GHSA-97r5-pg8x-p63p is a Improper Authentication vulnerability in flask-security-too. O3 Security confirms whether GHSA-97r5-pg8x-p63p is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.
Flask-Security-Too OAuth reauthentication freshness bypass via cross- user OAuth identity acceptance
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
Exploitation and automatability from CISA’s SSVC triage for GHSA-97r5-pg8x-p63p.
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
Real-World Exposure
flask-security-tooReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.
Description
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-214flask_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:
[email protected] - OAuth verified user:
[email protected] - 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 [email protected]. 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 [email protected].
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
[email protected].
6. Confirm the session is still for [email protected], 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": "[email protected]",
"oauth_verify_response_status": 302,
"post_bypass_change_username_status": 200,
"final_email": "[email protected]",
"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"].
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐍PyPI | flask-security-too | ≥ 5.8.0&&< 5.8.1 | 5.8.1 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for flask-security-too. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update flask-security-too to 5.8.1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-97r5-pg8x-p63p is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-97r5-pg8x-p63p is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-97r5-pg8x-p63p. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-97r5-pg8x-p63p in your dependencies?
O3 detects GHSA-97r5-pg8x-p63p across PyPI dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.