Header Injection in Django
How Header Injection Manifests in Django
Header injection in Django occurs when user-controlled data flows into HTTP response headers without proper validation. Django's architecture creates several unique attack vectors for this vulnerability.
The most common pattern involves Django's JsonResponse and StreamingHttpResponse objects. When developers dynamically set headers using user input, they often overlook the security implications. For example:
def vulnerable_view(request):
user_agent = request.headers.get('User-Agent')
response = JsonResponse({'status': 'ok'})
response['X-Client-Type'] = user_agent # Vulnerable
return responseThis allows an attacker to inject CRLF sequences (%0D%0A) into the User-Agent header, potentially adding arbitrary response headers or even splitting responses.
Django's middleware system creates another attack surface. Custom middleware that processes request headers and sets response headers without sanitization can be exploited. Consider this middleware:
class HeaderProcessingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
if referer:
response['X-Referer-Source'] = referer # Vulnerable
return responseTemplate rendering with custom headers presents a third vector. Developers sometimes use response.set_cookie() or response['Set-Cookie'] with user-controlled values:
def profile_view(request):
username = request.GET.get('username', 'default')
response = render(request, 'profile.html')
response.set_cookie('username', username) # Vulnerable if username contains CRLF
return responseDjango's StreamingHttpResponse is particularly dangerous because it allows incremental header modification. An attacker could exploit timing issues to inject headers mid-response in long-running streams.
The Django REST Framework (DRF) adds additional complexity. DRF's Response class and renderer system can inadvertently expose header injection vulnerabilities when custom renderers process user input for header values.
Django-Specific Detection
Detecting header injection in Django applications requires both manual code review and automated scanning. middleBrick's black-box scanning approach is particularly effective for Django applications because it tests the actual running API without requiring source code access.
middleBrick scans for header injection by sending requests with crafted header values containing CRLF sequences and observing the response. For Django applications, it specifically tests:
- Common Django response patterns including JsonResponse, StreamingHttpResponse, and HttpResponse
- Middleware header processing logic by sending headers through the full request pipeline
- DRF renderers and response objects
- Cookie handling in Django's session and authentication middleware
The scanner sends payloads like:
GET /api/data HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
X-Injected-Header: injected-valueIf the response contains the injected header or shows signs of header splitting, middleBrick flags this as a vulnerability with severity assessment based on the potential impact.
For development teams, middleBrick's GitHub Action integration allows continuous scanning of Django APIs in CI/CD pipelines. You can configure it to fail builds when header injection vulnerabilities are detected:
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run middleBrick scan
run: middlebrick scan https://staging.example.com/api/ --fail-on-severity=mediummiddleBrick's OpenAPI analysis also helps identify potential header injection points by cross-referencing your Django REST Framework's API schema with runtime behavior, highlighting endpoints where user input might flow into headers.
Django-Specific Remediation
Remediating header injection in Django requires a defense-in-depth approach using Django's built-in security features and proper input validation.
The first line of defense is Django's django.utils.http.strip_unsafe_headers() utility, which removes potentially dangerous headers. Use it when processing user input for headers:
from django.utils.http import strip_unsafe_headers
def safe_header_view(request):
user_agent = request.headers.get('User-Agent', '')
clean_agent = strip_unsafe_headers(user_agent)
response = JsonResponse({'status': 'ok'})
response['X-Client-Type'] = clean_agent
return responseFor more comprehensive protection, create a custom middleware that sanitizes all headers before they're set in responses:
import re
from django.utils.http import headers
class HeaderSanitizationMiddleware:
CRLF_PATTERN = re.compile(r'[
]+')
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
for header in list(response.items()):
if self.CRLF_PATTERN.search(header[1]):
del response[header[0]]
return responseDjango's SafeString and template auto-escaping features help prevent header injection in template contexts. When setting headers from template variables, use Django's safe string utilities:
from django.utils.safestring import SafeString
def template_header_view(request):
username = request.GET.get('username', 'guest')
safe_username = SafeString(username.replace('
', '').replace('
', ''))
response = render(request, 'template.html', {'username': safe_username})
response['X-Username'] = username
return responseFor Django REST Framework applications, use DRF's serializer validation to sanitize header values before they're used:
from rest_framework import serializers
class HeaderSerializer(serializers.Serializer):
custom_header = serializers.CharField(max_length=100, required=False)
def validate_custom_header(self, value):
if '
' in value or '
' in value:
raise serializers.ValidationError('Header injection detected')
return valuemiddleBrick's continuous monitoring plan can help verify that these remediations remain effective over time, automatically scanning your Django APIs on a configurable schedule and alerting your team if new header injection vulnerabilities are detected.