Broken Authentication in Flask
How Broken Authentication Manifests in Flask
Broken authentication in Flask applications typically emerges through several Flask-specific patterns. The most common vulnerability occurs when developers rely on Flask's default session management without proper configuration. Flask sessions are signed but not encrypted by default, meaning session data can be read if the secret key is compromised.
A classic Flask authentication flaw involves session fixation attacks. Consider this vulnerable pattern:
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
session['user_id'] = user.id
return redirect('/dashboard')
return 'Invalid credentials'This code fails to regenerate the session ID after login, allowing attackers to fixate a session before authentication and gain access once the victim logs in.
Another Flask-specific issue involves improper use of flask-login's login_user() without proper session protection:
@app.route('/login')
def login():
user = User.query.filter_by(username='admin').first()
login_user(user, remember=True)
return redirect('/admin')The remember=True parameter creates persistent cookies without adequate protection against theft. Flask's default session cookie (session) is vulnerable if transmitted over HTTP or if the secret key is weak.
Flask's before_request decorators can also introduce authentication bypass if not implemented correctly:
@app.before_request
def check_auth():
if request.endpoint != 'login':
if 'user_id' not in session:
return redirect('/login')
# Missing return for authenticated case!
This function returns None for authenticated requests, which Flask treats as continuing execution, but the logic is confusing and error-prone. A more subtle issue occurs with Flask's abort() function:
@app.route('/admin')
def admin():
if not session.get('is_admin'):
abort(403)
# Attacker can trigger abort handlers that reveal system information
Improper error handling in abort handlers can leak authentication bypass opportunities through timing differences or error messages.
Flask-Specific Detection
Detecting broken authentication in Flask requires examining both the application code and runtime behavior. For code analysis, look for these Flask-specific patterns:
Session Management Issues: Search for app.secret_key assignments with weak values or hardcoded secrets. Flask sessions should use strong, randomly generated keys of at least 24 bytes.
Missing Session Regeneration: After successful authentication, session IDs must be regenerated. Use session.regenerate() or implement session rotation manually.
from flask import session
from flask import redirect, url_for
from flask import request
@app.route('/secure-login', methods=['POST'])
def secure_login():
# ... authentication logic ...
if authenticated:
# Regenerate session to prevent fixation
session.regenerate()
session['user_id'] = user.id
return redirect(url_for('dashboard'))
CSRF Protection Gaps: Flask-WTF provides CSRF protection, but it must be explicitly enabled:
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)
# In templates: {{ form.csrf_token }}
Authentication Bypass via Route Order: Flask processes routes in the order they're defined. A catch-all route before an authenticated route can bypass protection:
@app.route('/')
def catch_all(path):
return 'Hello!'
@app.route('/admin')
def admin():
# This is never reached if catch_all matches first
return 'Admin panel'
Runtime Detection with middleBrick: middleBrick's black-box scanning can identify Flask authentication issues without access to source code. The scanner tests for common Flask vulnerabilities like session fixation by attempting to reuse session cookies across authentication boundaries. It also checks for missing CSRF tokens by submitting forms without the expected csrf_token field and observing whether the server accepts the request.
middleBrick specifically tests Flask's default session behavior by manipulating the session cookie and observing how the application responds to session tampering attempts. The scanner's authentication bypass checks include testing for predictable session token generation and insufficient session validation logic.
Flask-Specific Remediation
Securing Flask authentication requires leveraging Flask's built-in security features and following established patterns. Here's a comprehensive approach to fixing broken authentication in Flask:
Strong Session Configuration:
from flask import Flask, session
import os
app = Flask(__name__)
app.secret_key = os.environ.get('FLASK_SECRET_KEY') or os.urandom(24)
# Use secure session configuration
app.config.update(
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SECURE=True, # HTTPS only
SESSION_COOKIE_SAMESITE='Lax',
PERMANENT_SESSION_LIFETIME=timedelta(hours=1)
)
Proper Authentication Flow:
from flask import session, redirect, url_for
from flask import request
from werkzeug.security import check_password_hash
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username).first()
if user and check_password_hash(user.password_hash, password):
# Regenerate session to prevent fixation
session.regenerate()
session['user_id'] = user.id
session['authenticated'] = True
session['csrf_token'] = os.urandom(16).hex()
return redirect(url_for('dashboard'))
return 'Invalid credentials', 401
@app.route('/logout')
def logout():
session.clear()
return redirect(url_for('login'))
CSRF Protection Implementation:
from flask_wtf.csrf import CSRFProtect
from flask import request
csrf = CSRFProtect(app)
@app.route('/submit-form', methods=['POST'])
def submit_form()):
if not csrf.validate_csrf(request.form.get('csrf_token')):
return 'CSRF validation failed', 403
# Process form data
Role-Based Access Control:
from functools import wraps
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not session.get('authenticated'):
return redirect(url_for('login'))
return f(*args, **kwargs)
return decorated_function
def role_required(allowed_roles):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
user_role = session.get('user_role')
if user_role not in allowed_roles:
return 'Insufficient permissions', 403
return f(*args, **kwargs)
return decorated_function
return decorator
@app.route('/admin')
@login_required
@role_required(['admin'])
def admin():
return 'Admin panel'
Rate Limiting for Authentication:
from flask import request
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"]
)
@app.route('/login', methods=['POST'])
@limiter.limit("5/minute;20/hour")
def login():
# Authentication logic with rate limiting
Secure Password Handling:
from werkzeug.security import generate_password_hash, check_password_hash
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password_hash = db.Column(db.String(128))
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
These remediation strategies address the most common Flask authentication vulnerabilities. For comprehensive security validation, scanning your Flask API with middleBrick can identify authentication weaknesses that might be missed during manual code review.
Related CWEs: authentication
| CWE ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |