Session Fixation in Flask with Firestore
Session Fixation in Flask with Firestore — how this specific combination creates or exposes the vulnerability
Session fixation occurs when an application assigns a user a session identifier before authentication and does not regenerate that identifier afterward. In a Flask app using Firestore as the session store, the risk arises from how session identifiers are created, stored, and validated. Flask’s default cookie-based sessions can rely on SESSION_COOKIE_NAME and SESSION_COOKIE_HTTPONLY settings, but if the server does not issue a new session ID after login, an attacker can set a known session ID on the victim’s browser and later hijack the authenticated session.
When Firestore is used to persist session data (for example via a custom session interface that reads and writes session documents by session ID), the session ID becomes a key into a user-specific document in a collection such as sessions. If the session ID is predictable or not rotated on authentication, an attacker who knows the ID can read or modify the corresponding Firestore document, assuming the security rules permit it. Even with restrictive Firestore rules, fixation undermines identity boundaries: the server treats the pre-auth session ID as equivalent to a post-auth session ID because it does not recreate the document or reissue the ID after login.
Consider a naive implementation where session data is stored in Firestore under a sessions collection, keyed by session ID, with fields like user_id, created_at, and expires. If Flask creates the session cookie once and never calls session.regenerate(), the same document is referenced before and after authentication. An attacker can craft a URL with a known session ID, trick a user into authenticating, and then use the now-authenticated Firestore document to impersonate the user. This maps directly to the OWASP API Top 10 session management issues and parallels classic web session fixation, but with the added complexity of server-side document lookups in Firestore.
Additionally, if the API endpoints that read or update session data in Firestore do not enforce strict input validation on the session ID, an attacker may leverage path traversal or malformed IDs to access documents outside the intended scope. Because middleBrick tests input validation and BOLA/IDOR across 12 checks in parallel, such a misconfiguration would likely be flagged as a high-severity finding under Authentication and Property Authorization categories, with remediation guidance to rotate session identifiers post-authentication and enforce strict document ownership checks.
Firestore-Specific Remediation in Flask — concrete code fixes
To remediate session fixation when using Firestore in Flask, regenerate the session identifier immediately after successful authentication and ensure session documents in Firestore are created or updated under the new identifier. Below is a concrete, syntactically correct example that integrates Flask with Firestore using the official Google Cloud client library.
from flask import Flask, session, request, redirect, url_for
from google.cloud import firestore
import os
import secrets
app = Flask(__name__)
app.secret_key = os.environ.get('FLASK_SECRET_KEY')
# Initialize Firestore client
db = firestore.Client()
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
password = request.form.get('password')
# Validate credentials against your user store (e.g., Firestore 'users' collection)
user_doc = db.collection('users').where('username', '==', username).limit(1).get()
user = None
for doc in user_doc:
# In production, verify password with a secure hash check
if doc.to_dict().get('password') == password: # placeholder; use hashing in practice
user = doc
break
if user is None:
return 'Invalid credentials', 401
# Critical remediation: regenerate session to prevent fixation
session.regenerate()
# Write a new session document keyed by the new session ID
session_id = session.get('_id') # Flask assigns this after regeneration
db.collection('sessions').document(session_id).set({
'user_id': user.id,
'created_at': firestore.SERVER_TIMESTAMP,
'expires': firestore.SERVER_TIMESTAMP # set TTL logic separately or via TTL policy
})
# Ensure cookie settings are secure
session['user_id'] = user.id
return redirect(url_for('dashboard'))
@app.route('/profile')
def profile():
session_id = session.get('_id')
if not session_id:
return redirect(url_for('login'))
sess_doc = db.collection('sessions').document(session_id).get()
if not sess_doc.exists:
return redirect(url_for('login'))
# Enforce ownership: verify that the session’s user_id matches the resource being accessed
user_id = sess_doc.to_dict().get('user_id')
# Fetch user-specific data, ensuring user_id aligns with the session
user_data = db.collection('users').document(user_id).get()
return f'Profile for {user_data.get("email")}'
Key points in this remediation:
session.regenerate()creates a new session identifier and discards the old one, preventing an attacker’s pre-set ID from remaining valid.- A new Firestore document is created under the regenerated session ID, establishing a clear one-to-one mapping between the authenticated session and the server-side document.
- Firestore document reads and writes should always validate ownership by comparing the session’s
user_idwith the target resource’s user identifier, mitigating BOLA/IDOR risks. - Use secure, HttpOnly cookies and consider short session TTLs managed either in Firestore TTL policies or application logic to reduce the window of exposure.
For continuous assurance, middleBrick’s scans include checks for Authentication, BOLA/IDOR, and Input Validation, which can detect missing session regeneration or overly permissive Firestore security rules. If you maintain a dashboard or use the CLI (middlebrick scan <url>) as part of your workflow, these findings will be surfaced with severity and prioritized remediation guidance.