Header Injection in Django with Bearer Tokens
Header Injection in Django with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Header Injection in the context of Django APIs using Bearer Tokens occurs when untrusted input is reflected into HTTP response headers without proper validation or encoding. This often happens when developers concatenate request data (e.g., a client-supplied value) directly into header construction logic. While Bearer Tokens are typically expected in the Authorization header, the vulnerability arises not from the token itself, but from how other headers are built using untrusted sources.
Consider a scenario where an API endpoint accepts a custom header such as X-Client-Name and uses it to influence downstream behavior or logging. If the application does something like response['X-Client-Name'] = request.GET.get('client_name'), an attacker can inject newline characters (e.g., %0D%0A or literal \r\n) to split the header block and inject additional headers, such as X-Injected: true or even a second Set-Cookie header. This is a classic CRLF (Carriage Return Line Feed) injection.
In Django, the WSGI layer typically buffers and parses headers before they reach the application, but manual header manipulation in views or middleware can bypass these safeguards. When Bearer Tokens are involved, developers may mistakenly treat the Authorization header as trusted and reflect it into other headers for logging or routing purposes. For example, extracting the token via request.META.get('HTTP_AUTHORIZATION') and then writing it into a diagnostic header like X-Token-Debug without validation can expose internal patterns or enable header smuggling in chained proxies.
Another related risk involves splitting user-supplied data across multiple headers without enforcing a single-value constraint. An attacker might provide a Bearer Token containing newline sequences when the token is improperly decoded or when the application attempts to parse a malformed Authorization header. Although Django’s HttpRequest normalizes headers to some degree, custom code that manually constructs headers using string formatting remains vulnerable. For instance:
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
if auth_header.startswith('Bearer '):
token = auth_header.split(' ')[1]
response['X-Bearer-Token'] = token # Unsafe if token contains \r\n
If the token supplied by a client includes encoded line breaks, the injected header could alter the response routing or expose sensitive information in logs. Although Django does not automatically parse cookies from injected headers in the response, a compromised downstream service or intermediary proxy might. This is why header injection must be treated as a boundary violation: never trust data that will leave the Django layer via headers without strict allowlisting and encoding.
Real-world attack patterns like CVE-2021-32055 (related to header smuggling via improperly handled Location headers) demonstrate the impact of such weaknesses. While Django itself does not encourage direct header injection, integrations with API gateways, load balancers, or WSGI servers can amplify the risk if responses are modified after leaving the application. The key takeaway is that Bearer Tokens and other dynamic values must be validated for format and length, and never directly embedded into headers that Django constructs.
Bearer Tokens-Specific Remediation in Django — concrete code fixes
Remediation focuses on two principles: strict input validation for any value that may influence headers, and safe construction of response headers. For Bearer Tokens, treat the Authorization header as an opaque string and avoid reflecting it into other headers. If debugging or logging is required, hash or truncate the token, and ensure no user-controlled newline characters are present.
First, validate the Authorization header format before extraction. Use a regular expression to confirm the scheme and token structure, and reject malformed inputs early:
import re
from django.http import HttpResponseBadRequest
def validate_bearer_token(auth_header):
pattern = re.compile(r'^Bearer [A-Za-z0-9\-._~+/=]+$')
return bool(pattern.match(auth_header)) if auth_header else False
def my_view(request):
auth = request.META.get('HTTP_AUTHORIZATION', '')
if not validate_bearer_token(auth):
return HttpResponseBadRequest('Invalid Authorization header')
token = auth.split(' ')[1]
# Proceed with token usage, but do not inject it into other headers
Second, when constructing response headers, use Django’s built-in header utilities which handle encoding and prevent CRLF injection. Avoid manual string concatenation. For example, use response.headers['X-Custom'] = safe_value instead of manipulating raw header strings. If you must include a transformed version of the token, hash it:
import hashlib
from django.http import JsonResponse
def my_view(request):
auth = request.META.get('HTTP_AUTHORIZATION', '')
if not validate_bearer_token(auth):
return JsonResponse({'error': 'Unauthorized'}, status=401)
token = auth.split(' ')[1]
token_hash = hashlib.sha256(token.encode()).hexdigest()
response = JsonResponse({'status': 'ok'})
response.headers['X-Token-Fingerprint'] = token_hash # Safe, non-sensitive
return response
Third, if your architecture involves middleware that processes headers, enforce a denylist of prohibited header names (e.g., Location, Set-Cookie) when modifying responses. Combine this with a strict allowlist for known-safe headers. For logging, use Django’s logging framework with sanitized fields rather than injecting raw header values.
Finally, leverage Django’s security middleware to handle transport protections. Ensure SECURE_SSL_REDIRECT is set appropriately in production and that CSRF_COOKIE_SECURE and SESSION_COOKIE_SECURE are enabled when using HTTPS. While these settings do not directly prevent header injection, they reduce the attack surface when tokens are transmitted.