Sql Injection in Django with Dynamodb
SQL Injection in Django with DynamoDB — how this specific combination creates or exposes the vulnerability
SQL injection is commonly associated with relational databases, but when DynamoDB is used through an ORM or query layer in Django, developers can still introduce injection-like issues through unsafe string interpolation in query construction or raw expression building. Although DynamoDB itself uses a partitioned key-value model and does not have a SQL parser, injection risks arise when developers embed user input into key conditions, filter expressions, or construct low-level API parameters dynamically.
In a Django application using DynamoDB (for example via boto3 or a lightweight wrapper), a typical pattern might build a query dictionary using unchecked request data. If the code does not validate or parameterize values, an attacker can manipulate the structure of the condition expression to affect which items are returned or cause unexpected behavior. For example, concatenating a key condition string can unintentionally expose data or bypass intended filters.
Consider a DynamoDB table where partition keys represent tenant identifiers. If the application builds the key condition expression by string concatenation using raw input, an attacker may change the logical structure of the expression. While DynamoDB does not support arbitrary SQL, unsafe composition of expression attribute names and values can lead to over-privileged queries or data exposure. The framework does not inherently protect against these logic flaws, so validation and strict parameterization are essential.
An example of a risky pattern is constructing a filter expression by directly embedding user input into a string that becomes part of the request sent to DynamoDB. This can change the semantics of the query in ways the developer did not intend, such as affecting which subset of items is scanned or returned. Even though the backend is NoSQL, the impact can be significant: exposure of other users’ data, enumeration of records, or unexpected filtering behavior.
When using DynamoDB with Django, it is important to treat query inputs as untrusted and apply the same rigor as with SQL. Use strongly typed condition objects, validate input against expected formats, and avoid dynamic construction of key condition expressions. Rely on the SDK’s built-in mechanisms for expression values rather than string-based assembly. This ensures that user-controlled data never alters the structure of the request in unsafe ways.
DynamoDB-Specific Remediation in Django — concrete code fixes
To prevent injection-like issues when using DynamoDB in Django, adopt strict parameterization and input validation. Use expression attribute names and expression attribute values correctly, and avoid string interpolation for key conditions or filter logic. Below are concrete, safe patterns using the AWS SDK for Python (boto3) within a Django project.
Safe key condition construction
Always use placeholder syntax provided by the SDK and supply attribute values separately. This keeps data out of the expression structure and prevents unintended logic changes.
import boto3
from django.conf import settings
dynamodb = boto3.resource('dynamodb', region_name=settings.AWS_REGION)
table = dynamodb.Table('MyTable')
# Safe: using ExpressionAttributeNames and ExpressionAttributeValues
def get_tenant_items(tenant_id):
response = table.query(
KeyConditionExpression='#pk = :val',
ExpressionAttributeNames={'#pk': 'tenant_id'},
ExpressionAttributeValues={':val': tenant_id}
)
return response['Items']
Validated filter expressions
For filter logic, validate and map user input to known safe values or patterns, and use placeholders for any dynamic values.
import re
def build_filter(user_status):
# Validate against an allowlist to prevent injection-like manipulation
allowed = {'active', 'inactive', 'pending'}
if user_status not in allowed:
raise ValueError('Invalid status')
return '#status = :status', {
'#status': 'status',
':status': user_status
}
def list_users(status):
table = boto3.resource('dynamodb', region_name='us-east-1').Table('Users')
condition, values = build_filter(status)
response = table.scan(
FilterExpression=condition,
ExpressionAttributeNames={'#status': 'status'},
ExpressionAttributeValues=values
)
return response['Items']
Avoiding dynamic expression names
Do not allow user input to dictate attribute names. If dynamic names are necessary, map them through a strict allowlist or dictionary lookup before use.
SAFE_NAMES = {
'username': 'username',
'email': 'email',
'created_at': 'created_at'
}
def safe_query(index_name, value):
if index_name not in SAFE_NAMES:
raise ValueError('Invalid index')
table = boto3.resource('dynamodb').Table('Items')
response = table.query(
IndexName=index_name,
KeyConditionExpression='#idx = :v',
ExpressionAttributeNames={'#idx': SAFE_NAMES[index_name]},
ExpressionAttributeValues={':v': value}
)
return response['Items']
Framework-level safeguards
In Django, centralize DynamoDB access through services or repositories that enforce validation and expression safety. Use Django form or serializer validation to ensure only permitted data reaches query-building code. Logging and monitoring of unexpected patterns can also help detect attempted manipulation early.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |