HIGH session fixationflaskdynamodb

Session Fixation in Flask with Dynamodb

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

Session fixation occurs when an application allows an attacker to force a user to use a known session identifier. In Flask, the default secure and httponly flags for session cookies help mitigate some risks, but the server-side session storage implementation determines whether fixation is still possible. When using DynamoDB as the session store, the risk shifts to how session identifiers are created, assigned, and validated.

Flask does not enforce session regeneration on authentication by default. If your application assigns the session ID before login and stores session data in DynamoDB without rotating the identifier after successful authentication, an attacker can capture a victim’s session token (e.g., via XSS or network sniffing) and reuse it after login. DynamoDB itself does not introduce the flaw, but its usage patterns can amplify the impact if session records are keyed by the user-controlled identifier without additional safeguards.

Consider a Flask route that writes user-specific data into DynamoDB under a session key derived from the cookie value. An attacker can craft a URL with a known session ID, get the victim to authenticate, and then query the DynamoDB table using that ID to access privileged data. Because the session entry is stored under the attacker-chosen key, the attacker retains access after the victim logs in. This pattern violates the principle of session ID rotation and maps directly to OWASP API Top 10 A07:2021 — Identification and Authentication Failures.

DynamoDB’s schema-less nature means you must enforce security semantics in application code. For example, storing session entries with a partition key of session_id without binding that session to a post-authentication user context can lead to enumeration and fixation attacks. Compounded with missing server-side invalidation on logout or idle timeout, the session store becomes a persistence mechanism for attacker-controlled tokens.

A realistic DynamoDB session model in Flask might use a table where the primary key is the session identifier and attributes include user_id, created_at, and last_seen. If the session_id is not regenerated after login, the link between the authenticated user_id and the pre-auth session record remains exploitable. Regular security scans with tools that include API authentication and authorization checks (such as checking whether session rotation is enforced) are important to surface such weaknesses early.

Dynamodb-Specific Remediation in Flask — concrete code fixes

To remediate session fixation when using DynamoDB as the session backend in Flask, enforce session identifier rotation on authentication and bind sessions to user context with strict server-side validation. Below is a minimal, secure pattern using the AWS SDK for Python (Boto3) with DynamoDB.

First, ensure your DynamoDB table uses a composite key design that supports secure lookup and invalidation. For example, use PK as session_id and a sort key or attribute to associate the session with a user_id and enforce TTL for automatic cleanup.

import os
import uuid
import datetime
from flask import Flask, request, make_response
import boto3
from botocore.exceptions import ClientError

app = Flask(__name__)
app.secret_key = os.environ.get('FLASK_SECRET_KEY', 'dev-secret-change-in-prod')

ddb = boto3.resource('dynamodb', region_name='us-east-1')
table_name = os.environ.get('DYNAMODB_SESSION_TABLE', 'app_sessions')
table = ddb.Table(table_name)

def create_session(user_id):
    session_id = str(uuid.uuid4())
    now = datetime.datetime.utcnow()
    ttl = int((now + datetime.timedelta(hours=1)).timestamp())
    try:
        table.put_item(
            Item={
                'session_id': session_id,
                'user_id': user_id,
                'created_at': now.isoformat(),
                'last_seen': now.isoformat(),
                'ttl': ttl
            }
        )
    except ClientError as e:
        app.logger.error(f'DynamoDB put_item failed: {e}')
        raise
    return session_id

def validate_session(session_id):
    try:
        resp = table.get_item(Key={'session_id': session_id})
        item = resp.get('Item')
        if item:
            # refresh last_seen optionally
            table.update_item(
                Key={'session_id': session_id},
                UpdateExpression='SET last_seen = :now',
                ExpressionAttributeValues={':now': datetime.datetime.utcnow().isoformat()}
            )
            return item['user_id']
        return None
    except ClientError as e:
        app.logger.error(f'DynamoDB get_item failed: {e}')
        return None

@app.route('/login', methods=['POST'])
def login():
    username = request.form.get('username')
    password = request.form.get('password')
    # perform credential validation here
    if not (username == 'alice' and password == 'secret'):
        return 'Unauthorized', 401
    # Critical: rotate session on authentication
    new_session_id = create_session(username)
    resp = make_response('OK')
    resp.set_cookie('session', new_session_id, httponly=True, secure=True, samesite='Lax')
    return resp

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

@app.route('/logout')
def logout():
    session_id = request.cookies.get('session')
    if session_id:
        try:
            table.delete_item(Key={'session_id': session_id})
        except ClientError:
            pass
    resp = make_response('Logged out')
    resp.set_cookie('session', '', expires=0)
    return resp

Key points: always generate a new UUID for the session ID after login (create_session), store it in DynamoDB with user context, and instruct Flask to set the cookie with Secure and HttpOnly flags. On logout, explicitly delete the DynamoDB item to prevent reuse. These steps ensure that even if an attacker forces a session ID, the token is replaced after authentication, neutralizing the fixation vector.

For CI/CD safety, integrate the GitHub Action to scan your API endpoints and verify that session management endpoints enforce secure cookie attributes and server-side rotation. The dashboard can help track findings over time, and the CLI allows you to script checks for insecure session handling as part of automated testing.

Frequently Asked Questions

Does using DynamoDB as a session store inherently introduce security risks in Flask?
DynamoDB does not inherently introduce risks; the risk comes from how session identifiers are created, stored, and invalidated. If session IDs are not rotated after login and are stored without proper access controls, session fixation and hijacking can occur regardless of the backend store.
How can I verify my Flask app is protected against session fixation when using DynamoDB?
Verify that your login flow generates a new session identifier after successful authentication, sets Secure and HttpOnly cookie flags, and deletes or invalidates the previous session record in DynamoDB. Automated scans, such as those from the GitHub Action or CLI, can check for missing session rotation and insecure cookie attributes.