HIGH session fixationflaskhmac signatures

Session Fixation in Flask with Hmac Signatures

Session Fixation in Flask with Hmac Signatures — how this specific combination creates or exposes the vulnerability

Session fixation occurs when an application forces a user to use a session identifier (session ID) that the attacker knows or can set. In Flask, developers sometimes use signed cookies to store session identifiers and rely on HMAC signatures to ensure integrity. While HMAC prevents tampering with the cookie value, it does not prevent the server from accepting an attacker-supplied session ID. If the application does not issue a new signed session cookie after authentication, the user’s session ID remains the one originally provided — for example, set by an attacker via a link or injected script.

Consider a Flask app that creates a signed cookie using HMAC but does not rotate the session identifier on login:

from flask import Flask, request, make_response
import hmac
import hashlib

app = Flask(__name__)
SECRET_KEY = b'super-secret-key'

def sign_session(session_id):
    signature = hmac.new(SECRET_KEY, session_id.encode(), hashlib.sha256).hexdigest()
    return f'{session_id}.{signature}'

def verify_session(cookie_value):
    if '.' not in cookie_value:
        return None
    session_id, received_sig = cookie_value.rsplit('.', 1)
    expected_sig = hmac.new(SECRET_KEY, session_id.encode(), hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected_sig, received_sig):
        return None
    return session_id

@app.route('/login', methods=['POST'])
def login():
    username = request.form.get('username')
    password = request.form.get('password')
    # naive credential check
    if username == 'admin' and password == 'secret':
        session_id = request.cookies.get('session', 'fixed-by-attacker')
        response = make_response('Logged in')
        response.set_cookie('session', sign_session(session_id))
        return response
    return 'Invalid credentials', 401

@app.route('/profile')
def profile():
    session_id = verify_session(request.cookies.get('session', ''))
    if session_id is None:
        return 'Unauthorized', 401
    return f'Hello {session_id}'

In this example, the session ID is taken directly from the incoming cookie before authentication. Because the app signs whatever session ID it receives and sends it back, an attacker can craft a link with a known session ID (e.g., http://example.com/login?session=ATTACKER_SESSION), trick a victim into logging in, and then reuse the signed session ID to impersonate the victim. HMAC ensures the cookie cannot be altered undetected, but the fixation stems from failing to issue a fresh signed session cookie after successful authentication.

The vulnerability is compounded when the application exposes the session via URLs or relies on unverified origins, increasing the likelihood that an attacker can predict or deliver a specific session ID. Because HMAC only guarantees integrity, not freshness or uniqueness per authenticated session, developers must explicitly rotate the session identifier upon authentication to mitigate fixation.

Hmac Signatures-Specific Remediation in Flask — concrete code fixes

To remediate session fixation with HMAC-signed cookies in Flask, ensure that a new signed session cookie is issued immediately after successful authentication. This invalidates any pre-authentication session ID and binds the session to the authenticated user.

Below is a secure implementation that generates a cryptographically random session ID, signs it with HMAC, and sets a new cookie after login:

from flask import Flask, request, make_response
import hmac
import hashlib
import os

app = Flask(__name__)
SECRET_KEY = b'super-secret-key'

def sign_session(session_id):
    signature = hmac.new(SECRET_KEY, session_id.encode(), hashlib.sha256).hexdigest()
    return f'{session_id}.{signature}'

def verify_session(cookie_value):
    if '.' not in cookie_value:
        return None
    session_id, received_sig = cookie_value.rsplit('.', 1)
    expected_sig = hmac.new(SECRET_KEY, session_id.encode(), hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected_sig, received_sig):
        return None
    return session_id

@app.route('/login', methods=['POST'])
def login():
    username = request.form.get('username')
    password = request.form.get('password')
    if username == 'admin' and password == 'secret':
        # Generate a fresh session ID to prevent fixation
        fresh_session_id = os.urandom(16).hex()
        response = make_response('Logged in')
        response.set_cookie('session', sign_session(fresh_session_id), httponly=True, secure=True, samesite='Lax')
        return response
    return 'Invalid credentials', 401

@app.route('/profile')
def profile():
    session_id = verify_session(request.cookies.get('session', ''))
    if session_id is None:
        return 'Unauthorized', 401
    return f'Hello {session_id}'

Key remediation steps included:

  • Generate a new random session ID using os.urandom after authentication instead of reusing the incoming cookie value.
  • Always set the httponly, secure, and samesite flags on the cookie to reduce exposure to client-side script access and cross-site request forgery.
  • Verify the HMAC signature before using the session ID, ensuring the value has not been altered.

For applications using an OpenAPI spec, you can document the session handling expectations and validate that authentication endpoints explicitly rotate session identifiers. The CLI tool (middlebrick scan <url>) can detect whether authentication flows appear to retain pre-login session identifiers across requests, which is a useful indicator during security assessments.

Frequently Asked Questions

Does using HMAC for session cookies fully prevent session fixation in Flask?
No. HMAC ensures cookie integrity but does not prevent the server from accepting an attacker-supplied session ID. You must rotate the session identifier after authentication to prevent fixation.
What additional protections should be applied to HMAC-signed session cookies in Flask?
Set httponly, secure, and samesite cookie attributes; use a strong secret key; and always issue a new signed session ID upon successful authentication rather than reusing the pre-auth value.