HIGH session fixationdjangohmac signatures

Session Fixation in Django with Hmac Signatures

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

Session fixation occurs when an attacker sets or knows a user’s session identifier and tricks the user into authenticating with that known value. In Django, the session framework supports signing cookie values using HMAC signatures to ensure integrity. While HMAC prevents tampering with the session data itself, it does not prevent fixation if the session key is assigned before authentication and not rotated upon login.

When you configure Django to use signed cookies (e.g., SESSION_COOKIE_NAME with a signature based on a secret key), the session ID stored in the cookie is HMAC-signed. On each request, Django verifies the signature. If the signature is invalid, the session is treated as empty. This protects confidentiality and integrity of session data stored client-side, but it does not stop an attacker from forcing a known session ID onto a victim’s browser.

For example, if an application sets request.session.session_key early in the request (for instance, by accessing the session or explicitly assigning a key) before the user authenticates, the attacker can supply that key to the victim. Because the session cookie is HMAC-signed, Django will accept it on subsequent authenticated requests as long as the signature matches and the secret key is unchanged. The vulnerability is not in the HMAC construction; it is in the session lifecycle management: a predictable or attacker-supplied session key persisted before authentication and not invalidated or rotated at login.

Consider a scenario where session keys are not regenerated on authentication, and the session store does not enforce re-keying. An attacker crafts a link such as https://example.com/login?next=/dashboard, where the session cookie is already set to a known value. When the victim logs in, the session remains tied to that known key. The attacker can then use the same session key (and the valid HMAC signature will be presented automatically by the browser) to hijack the authenticated session. This is a classic session fixation issue, even though the cookie value is HMAC-signed.

Django’s built-in session backends (e.g., cached_db, db, file) store session data server-side, while signed cookie sessions keep data client-side but signed. In both cases, fixation risk centers on how and when the session key is assigned. HMAC signatures do not mitigate fixation; they only ensure that an attacker cannot alter the contents of a valid session cookie without detection. Therefore, developers must explicitly rotate the session key upon successful authentication and avoid creating sessions before necessary.

Hmac Signatures-Specific Remediation in Django — concrete code fixes

To mitigate session fixation when using HMAC-signed sessions in Django, rotate the session key immediately after authentication and avoid exposing or reusing session keys before login. Below are concrete, realistic code examples that demonstrate the remediation.

First, ensure your Django settings use signed cookie sessions and a strong secret key. In settings.py:

SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
SESSION_COOKIE_SECURE = True  # Serve over HTTPS only
SESSION_COOKIE_HTTPONLY = True
SESSION_EXPIRE_AT_BROWSER_CLOSE = True

Second, in your login view, authenticate the user and then rotate the session key. Do not rely on any pre-existing session key. A common pattern is to call request.session.cycle_key() after confirming the user’s credentials:

from django.contrib.auth import authenticate, login as auth_login
from django.shortcuts import redirect, render

def login_view(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        user = authenticate(request, username=username, password=password)
        if user is not None:
            auth_login(request, user)
            # Rotate session key after successful authentication to prevent fixation
            request.session.cycle_key()
            return redirect('dashboard')
        else:
            # Handle invalid login
            return render(request, 'login.html', {'error': 'Invalid credentials'})
    return render(request, 'login.html')

The call to request.session.cycle_key() creates a new random session key while preserving session data. This ensures that even if the client supplied a known key before login, the key is replaced once authentication succeeds. If you use session data before authentication (e.g., for a multi-step form), avoid committing a session key to the client until it is necessary, or explicitly regenerate the key before storing sensitive data.

For additional safety, you can manually create a new session key and clear old data when appropriate:

# Example: manually create a new session key and clear old data
request.session.flush()  # clears the session and issues a new session key
# Now safe to store post-authentication data
request.session['user_id'] = user.id
request.session['authenticated'] = True

Note that flush() deletes all session data and generates a new key, which is more aggressive than cycle_key(). Use flush() when you want to start fresh, and cycle_key() when you want to preserve existing session data after authentication.

Finally, validate that your session configuration does not inadvertently expose keys before login. Avoid accessing request.session or writing to it prior to authentication unless necessary. By combining HMAC integrity with key rotation upon login, you address session fixation while retaining the benefits of signed cookies.

Frequently Asked Questions

Does HMAC signing prevent session fixation in Django?
No. HMAC signing ensures the integrity of the session cookie but does not prevent fixation. You must rotate the session key upon authentication to prevent an attacker from forcing a known key.
What is the difference between cycle_key() and flush() in Django sessions?
cycle_key() creates a new random session key while preserving existing session data; flush() clears all session data and generates a new key. Use cycle_key() to rotate without losing data, and flush() to start completely fresh.