Log Injection in Django with Dynamodb
Log Injection in Django with Dynamodb — how this specific combination creates or exposes the vulnerability
Log injection occurs when untrusted input is written directly into log entries without validation, sanitization, or structured formatting. In a Django application that uses DynamoDB as a persistence or logging backend, this becomes a cross-component risk: user-controlled data can traverse Django logging configuration and be stored in DynamoDB tables via custom appenders or handlers. If log events are written using the low-level DynamoDB put_item or batch write operations without proper escaping, newline characters or crafted payloads can break the logical structure of log streams. This can lead to log forging, where an attacker injects additional entries or modifies timestamps, and to log-based side channels that may expose sensitive data or confuse automated monitoring. Because DynamoDB stores log records as item attributes, injected newlines or special characters can alter how logs are indexed and queried, making it harder to detect related events. In a Django context, this often happens when developers pass request data—such as usernames, search terms, or session identifiers—directly into log messages that are then emitted to a DynamoDB sink via a custom logging handler. Without input validation or structured logging, these values become part of the item’s attribute values, effectively turning application logs into an attacker-controlled datastore.
Dynamodb-Specific Remediation in Django — concrete code fixes
To mitigate log injection when using DynamoDB in Django, treat log data with the same rigor as database records: validate, structure, and escape. Avoid building log items by string concatenation; instead use structured logging with dictionaries and let the DynamoDB client marshal values safely. Below are concrete examples that show a vulnerable pattern and a hardened approach.
Vulnerable pattern: direct string formatting into DynamoDB item
import boto3
from django.conf import settings
dynamodb = boto3.resource('dynamodb', region_name=settings.AWS_REGION)
table = dynamodb.Table('app-logs')
def log_event(user_input):
# UNSAFE: user_input may contain newlines or special characters
table.put_item(Item={
'timestamp': '2024-01-01T00:00:00Z',
'level': 'INFO',
'message': 'User action: ' + user_input
})
Remediation 1: structured logging with type-safe attribute values
Use a dictionary and ensure scalar values for DynamoDB attribute types. Escape or omit newlines from user-controlled fields, and reserve separate attributes for metadata so log parsers can reliably split fields.
import boto3
from django.conf import settings
import json
dynamodb = boto3.resource('dynamodb', region_name=settings.AWS_REGION)
table = dynamodb.Table('app-logs')
def log_event(user_id: str, action: str, details: str):
# Ensure values are safe for log analysis
safe_action = action.replace('\n', ' ').replace('\r', ' ').strip()
safe_details = details.replace('\n', ' ').replace('\r', ' ').strip()
table.put_item(Item={
'timestamp': {'S': '2024-01-01T12:34:56Z'},
'level': {'S': 'INFO'},
'user_id': {'S': user_id},
'action': {'S': safe_action},
'details': {'S': safe_details},
'message': {'S': json.dumps({
'action': safe_action,
'details': safe_details
})}
})
Remediation 2: use a dedicated logging handler with schema validation
Instead of low-level put_item, use a handler that enforces schema and escapes control characters. For example, a custom handler can validate fields before insertion and reject or transform problematic input.
import boto3
from logging import Handler
class DynamoDBLogHandler(Handler):
def __init__(self, table_name):
super().__init__()
self.table = boto3.resource('dynamodb').Table(table_name)
def emit(self, record):
try:
msg = self.format(record)
# Replace newlines to avoid breaking log stream parsing
safe_msg = msg.replace('\n', ' ').replace('\r', ' ')
self.table.put_item(Item={
'timestamp': {'S': record.created_iso},
'level': {'S': record.levelname},
'message': {'S': safe_msg}
})
except Exception:
self.handleError(record)
# In Django settings:
# LOGGING = {
# 'handlers': {
# 'dynamodb': {
# 'class': 'myapp.logging.DynamoDBLogHandler',
# 'table_name': 'app-logs',
# },
# },
# 'loggers': {
# 'django': {
# 'handlers': ['dynamodb'],
# 'level': 'INFO',
# },
# },
# }
Additional hardening tips
- Validate and normalize input in Django forms and serializers before it reaches logging code.
- Use structured JSON messages (via
json.dumps) so log parsers can distinguish fields reliably. - Limit attribute sizes in DynamoDB and enforce server-side validation where possible.
- Monitor CloudWatch metric filters or DynamoDB streams for anomalies that indicate log forgery attempts.