Broken Function Level Authorization

Broken Function Level Authorization in Flask [Mar 2026] [CVE-2021-21241]

[Updated Mar 2026] Updated CVE-2021-21241

Overview

This guide covers a real Broken Function Level Authorization scenario observed in Flask apps using Flask-Security-Too, specifically CVE-2021-21241. In affected versions, the /login and /change endpoints could return the authenticated user's token in responses to GET requests. Since GET requests typically bypass CSRF protections, a malicious third-party site could induce a user’s browser to fetch those endpoints and potentially exfiltrate the token. Tokens then could be used by an attacker to impersonate the user or access protected resources, depending on how the token is consumed by the app. The exposure is worst when tokens are delivered in plaintext responses or stored insecurely, enabling cross-site token theft. The vulnerability was fixed in Flask-Security-Too in versions 3.4.5 and 4.0.0, with a workaround recommended to disable tokens via SECURITY_TOKEN_MAX_AGE=0 if upgrading isn’t immediately possible. This aligns with CWE-352 and demonstrates a Broken Function Level Authorization scenario where function-level access controls were not properly protecting sensitive token leakage on GET paths.

Affected Versions

Flask-Security-Too 3.3.0 <= version < 3.4.5 (i.e., 3.3.0 through 3.4.4) are affected; the issue is patched in 3.4.5 and 4.0.0.

Code Fix Example

Flask API Security Remediation
from flask import Flask, request, jsonify

# Vulnerable pattern: GET returns authentication token
app = Flask(__name__)

def token_for(user_id):
    return f"tok-{user_id}"

@app.route('/login', methods=['GET'])
def login_vuln():
    user_id = request.args.get('user_id')
    if not user_id:
        return jsonify({'error': 'missing user_id'}), 400
    token = token_for(user_id)
    return jsonify({'token': token})

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

# Fixed: POST with CSRF protection; no token leakage on GET
from flask import Flask, request, jsonify
from flask_wtf.csrf import CSRFProtect
import os

app = Flask(__name__)
app.secret_key = os.urandom(24)
csrf = CSRFProtect(app)

def token_for(user_id):
    return f"tok-{user_id}"

@app.route('/login', methods=['POST'])
def login_fix():
    user_id = request.form.get('user_id')
    if not user_id:
        return jsonify({'error': 'missing user_id'}), 400
    token = token_for(user_id)
    return jsonify({'token': token})

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

CVE References

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