Header Injection in Flask with Dynamodb
Header Injection in Flask with Dynamodb — how this specific combination creates or exposes the vulnerability
Header Injection occurs when untrusted input is reflected into HTTP response headers without validation or encoding. In a Flask application that interacts with DynamoDB, this typically happens when data retrieved from or stored in DynamoDB is used to construct response headers. Because HTTP headers are structured as name/value pairs and control critical runtime behavior (e.g., redirection, cookies, content type), injecting newline characters (\r\n) enables attackers to split headers and inject malicious directives.
Flask does not inherently sanitize values placed into headers via Response.headers or make_response. If your code retrieves an item from DynamoDB and directly assigns a field such as user_supplied_value to a header, an attacker can supply a string like example.com\r\nSet-Cookie: stolen=cookie. This results in header injection, which can lead to session fixation, HTTP response splitting, or cross-site scripting in some clients.
DynamoDB itself is a database and does not introduce header injection; the risk arises at the application layer when DynamoDB data is used unsafely. Common patterns include:
- Using a DynamoDB attribute as a redirect target (e.g.,
Locationheader) without validation. - Echoing DynamoDB-stored user data into headers such as
X-User-IDorX-Request-Idwithout sanitization. - Deserializing DynamoDB items (e.g., via
json.loadson a JSON string attribute) and then using values in headers.
Because middleBrick scans test unauthenticated attack surfaces and include checks such as Input Validation and Data Exposure, it can identify places where DynamoDB-derived input reaches response headers without encoding. Findings typically map to the OWASP API Top 10 category API1:2023 – Broken Object Level Authorization when access patterns expose sensitive data, and to header manipulation risks under general input validation failures.
In practice, an attacker might send a request that causes Flask to retrieve a DynamoDB item containing a malicious header fragment and then observe whether the injected header appears in the response. Tools like curl or browser dev tools can reveal split responses or unintended Set-Cookie headers, demonstrating the impact of insufficient sanitization.
Dynamodb-Specific Remediation in Flask — concrete code fixes
To prevent Header Injection when using DynamoDB data in Flask, always treat data from DynamoDB as untrusted. Validate, sanitize, and avoid direct interpolation into headers. Below are concrete, safe patterns and code examples.
1. Validate and sanitize header values
Never allow raw newlines in header values. Use a strict allowlist validation and replace or reject control characters.
import re
from flask import Flask, jsonify, make_response
def is_safe_header(value: str) -> bool:
# Allow alphanumerics and a limited set of safe punctuation
return bool(re.fullmatch(r'[a-zA-Z0-9_\-\.\s]{1,256}', value))
2. Use a safe dictionary for response headers
Construct headers explicitly and avoid adding user-controlled strings directly. If you must include DynamoDB data, map it through a controlled set of keys.
from flask import Flask, Response
import boto3
import json
app = Flask(__name__)
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
table = dynamodb.Table('Items')
@app.route('/item/<item_id>')
def get_item_headers(item_id: str):
resp = table.get_item(Key={'item_id': item_id})
item = resp.get('Item')
if not item:
return jsonify({'error': 'not found'}), 404
# Safe: explicitly map DynamoDB fields to known headers
headers = {
'X-Item-Id': str(item.get('item_id', '')),
'X-Created-At': str(item.get('created_at', '')),
}
# Ensure no newline in header values
safe_headers = {k: v.replace('\r', '').replace('\n', '') for k, v in headers.items()}
response = make_response(jsonify(dict(item)))
for k, v in safe_headers.items():
response.headers[k] = v
return response
3. Avoid using DynamoDB data in redirects or Set-Cookie
Do not use raw DynamoDB fields for Location headers or Set-Cookie. If redirection is required, use a predefined allowlist of paths or IDs.
from flask import redirect, url_for
@app.route('/redirect')
def safe_redirect():
# BAD: redirect(unsafe_location) # location from DynamoDB
# GOOD: use a validated internal route name
return url_for('dashboard') # or a hard-coded safe URL
4. Encode output where necessary
For headers that must include dynamic values, apply percent-encoding for non-safe characters or avoid them entirely. Flask's werkzeug.datastructures.Headers does not automatically encode newlines, so manual sanitization is required.
from urllib.parse import quote
user_label = item.get('label', '')
# Encode to avoid injection via colon, newline, or spaces
safe_label = quote(user_label, safe='') # removes problematic chars
response.headers['X-User-Label'] = safe_label
5. Use structured logging and monitoring instead of headers
Prefer embedding DynamoDB-derived metadata in the response body or server-side logs rather than headers, which are more strictly interpreted by clients and intermediaries.