Security Misconfiguration

Security Misconfiguration in Flask: CVE-2021-21241 [CVE-2021-21241]

[Updated March 2026] Updated CVE-2021-21241

Overview

The CVE-2021-21241 vulnerability affects Flask-Security-Too, an independently maintained fork of Flask-Security used to add authentication features to Flask apps. In versions 3.3.0 through 3.4.4, the /login and /change endpoints could return the authenticated user’s token in a response to a GET request. Because GET requests are typically not protected by CSRF tokens, a malicious third party could coax a victim’s browser to make that GET request and harvest the token from the response, enabling token theft and potential impersonation. This token leakage is a security misconfiguration in practice: token data is revealed in GET responses instead of being restricted to POST flows and properly protected endpoints. The official patches fix this by ensuring tokens are not exposed via GET and by tightening token handling in the library (patched in 3.4.5 and 4.0.0). As a workaround, if you aren’t using authentication tokens, you can set SECURITY_TOKEN_MAX_AGE to 0 to invalidate tokens immediately.

Affected Versions

3.3.0 through 3.4.4

Code Fix Example

Flask API Security Remediation
VULNERABLE PATTERN (GET exposes token):
from flask import Flask, jsonify
import os

app = Flask(__name__)

# Simulated in-app token for demonstration purposes
TOKENS = {"user1": "token-abc123"}

@app.route('/login', methods=['GET'])
def login_get():
    # Vulnerable: returns token in GET response which is not CSRF-protected
    user_id = 'user1'
    token = TOKENS.get(user_id)
    return jsonify({"token": token, "message": "Use POST to login"})

if __name__ == '__main__':
    app.run(debug=True)

FIXED PATTERN (no token on GET; login uses POST):
from flask import Flask, jsonify, request, abort

app = Flask(__name__)

# Simulated in-app token for demonstration purposes
TOKENS = {"user1": "token-abc123"}

@app.route('/login', methods=['POST'])
def login_post():
    # Proper login flow via POST; token exposed only after successful authentication
    data = request.get_json(silent=True) or {}
    username = data.get('username')
    password = data.get('password')
    if username == 'user1' and password == 's3cr3t':
        token = TOKENS.get('user1')
        return jsonify({"token": token})
    abort(401)

# Optionally keep a GET handler that does not leak tokens
@app.route('/login', methods=['GET'])
def login_get_safe():
    abort(405)  # disallow token exposure via GET

if __name__ == '__main__':
    app.run(debug=True)

CVE References

Choose which optional cookies to allow. You can change this any time.