Broken Access Control in Django with Dynamodb
Broken Access Control in Django with Dynamodb — how this specific combination creates or exposes the vulnerability
Broken Access Control occurs when authorization checks are missing or bypassed, allowing one user to act on another user's resources. In a Django application using Amazon DynamoDB as the persistence layer, the risk arises from a mismatch between Django's high-level permission abstractions and the low, direct key-based access patterns to DynamoDB.
DynamoDB stores items with partition and sort keys that often embed tenant identifiers such as user_id or org_id. If Django views or model managers do not consistently enforce that every query includes the correct tenant identifier derived from the authenticated user, an attacker can manipulate input (e.g., changing a URL parameter like customer_id) to request data belonging to another tenant. Because DynamoDB does not enforce row-level policies, the database will return data if the exact key is supplied, even if the application layer should have forbidden the request.
This becomes more pronounced when developers use the DynamoDB DocumentClient in Django data access utilities that construct requests from user-supplied parameters. For example, a view that accepts customer_id from the URL and passes it directly into a get_item or query call without verifying that the customer_id belongs to the requesting user can lead to Insecure Direct Object References (IDOR), a classic form of Broken Access Control. The issue is compounded when DynamoDB responses are cached or paginated without re-applying authorization checks on each page, allowing lateral movement across resources.
In practice, this maps to the OWASP API Top 10 category A1: Broken Access Control and commonly surfaces as a finding in unauthenticated or low-privilege scans where endpoints expose identifiers that should be constrained. Unlike traditional relational databases, DynamoDB does not provide native row-level security, so the burden is entirely on the application to enforce tenant boundaries, making rigorous authorization checks in Django views and managers essential.
Dynamodb-Specific Remediation in Django — concrete code fixes
To remediate Broken Access Control when using DynamoDB in Django, enforce tenant isolation at every data access point and avoid passing raw user input directly into DynamoDB key expressions. Below are concrete, DynamoDB-aware patterns that integrate safely with Django.
1. Always include tenant context in the key condition
Ensure that every DynamoDB operation includes the authenticated user's scope (e.g., user_id or org_id) as part of the key. Do not rely on the client-supplied identifier alone.
import boto3
from django.conf import settings
dynamodb = boto3.resource('dynamodb', region_name=settings.AWS_REGION)
table = dynamodb.Table(settings.DYNAMODB_TABLE)
def get_customer_for_user(user_id, customer_id):
# Enforce that customer_id belongs to user_id by including both in the key
response = table.get_item(
Key={
'user_id': user_id,
'customer_id': customer_id
}
)
return response.get('Item')
2. Use a dedicated data access layer that validates ownership
Create a manager or service that wraps query construction, automatically appending the tenant filter and rejecting ambiguous requests.
from django.core.exceptions import PermissionDenied
def query_orders_for_user(user_id, status=None):
filter_expression = 'user_id = :uid'
expression_attr_values = {':uid': user_id}
if status:
filter_expression += ' AND order_status = :status'
expression_attr_values[':status'] = status
response = table.scan(
FilterExpression=filter_expression,
ExpressionAttributeValues=expression_attr_values
)
return response.get('Items', [])3. Avoid client-supplied keys in sensitive operations
For operations that should act on the authenticated user only (e.g., profile data), derive the key from the request context and ignore any user-provided key parameters.
def get_user_profile(user_id):
# Do not accept a profile_id from the client; derive from user_id
response = table.get_item(
Key={
'entity': 'profile',
'owner_id': user_id
}
)
return response.get('Item')
4. Apply authorization before returning data
Even when using DynamoDB queries, re-check ownership in Python if your data model requires it, and raise PermissionDenied to ensure no accidental leakage occurs.
By combining these patterns, Django applications can enforce strict access boundaries on DynamoDB, reducing the attack surface for IDOR and privilege escalation while remaining compatible with the DynamoDB API surface.