Request Smuggling in Django with Hmac Signatures
Request Smuggling in Django with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Request smuggling occurs when an application processes HTTP requests differently depending on whether they are handled by a frontend proxy or the application itself. In Django, this can arise when requests include HMAC signatures for integrity and authenticity verification but the signature is computed over a subset of the message while the proxy and Django process different effective request bodies. This mismatch allows an attacker to smuggle a second request by crafting a request where the proxy interprets one message boundary and Django interprets another.
Django’s HMAC utilities (e.g., django.core.signing.dumps and django.core.signing.loads) typically sign a payload string, often including a timestamp and a value such as a primary key. If the signature covers only the serialized payload and not the entire HTTP message—including headers and body framing—an attacker can send a request with a valid signature but with an additional, unauthenticated body appended. A misconfigured proxy that normalizes or splits requests differently may forward the first request to Django with only the signed part, while the smuggled part is interpreted as a new request by the proxy or an intermediate server.
For example, consider a Django endpoint that expects a JSON body and an X-API-Signature header. The proxy might buffer and forward requests, but if it handles chunked transfer encoding differently than Django, an attacker can craft a request where the first chunk contains a valid signed JSON object and a second chunk that is not signed. The proxy may treat the concatenated body as two requests, but Django only validates the first and ignores the remainder, allowing the smuggled request to execute under the original authentication context.
Specific vectors include mismatched content-length handling, transfer-encoding parsing differences, and reliance on framework-specific request wrappers that do not enforce strict message boundaries before verifying HMAC. Since middleBrick tests unauthenticated attack surfaces and flags inconsistencies in how APIs handle input boundaries and authorization, such issues can be surfaced through its 12 parallel security checks, including Input Validation and Property Authorization.
Hmac Signatures-Specific Remediation in Django — concrete code fixes
To mitigate request smuggling when using HMAC signatures in Django, ensure the signature covers the full request body and that the application enforces strict message parsing before verification. Use Django’s signing utilities with explicit data that includes or represents the full request body, and avoid relying on partial content for the signature.
Example: Signing the full JSON payload
Instead of signing only a subset (e.g., an ID), sign the entire payload that will be processed. Include a timestamp to prevent replay and ensure the signature is validated before any business logic executes.
import json
import time
from django.core import signing
from django.http import JsonResponse, HttpResponseBadRequest
def my_view(request):
if request.method != 'POST':
return HttpResponseBadRequest('Only POST allowed')
body = request.body.decode('utf-8')
timestamp = str(int(time.time()))
data = json.loads(body)
payload = json.dumps({'timestamp': timestamp, 'body': data})
signature = signing.dumps(payload, key='your-secret-key')
# The client must send this signature in a header, e.g., X-API-Signature
# and the original body in the request body.
# Verification example:
received_signature = request.headers.get('X-API-Signature')
if not received_signature:
return JsonResponse({'error': 'Missing signature'}, status=400)
try:
verified = signing.loads(received_signature, key='your-secret-key', max_age=30)
verified_data = json.loads(verified)
if verified_data['timestamp'] != timestamp:
return JsonResponse({'error': 'Replay detected'}, status=400)
# Process verified_data['body'] as the intended payload
return JsonResponse({'status': 'ok'})
except signing.BadSignature:
return JsonResponse({'error': 'Invalid signature'}, status=400)
Enforce Content-Length and Reject Chunked Smuggling Attempts
Configure Django and the proxy to reject ambiguous transfer encodings and to validate Content-Length consistently. In production, ensure the WSGI server and reverse proxy (e.g., Nginx, Traefik) are aligned on request buffering policies.
# Example Nginx snippet to mitigate smuggling by enforcing Content-Length
location /api/ {
proxy_pass http://django_app;
proxy_set_header Content-Length $content_length;
proxy_ignore_client_abort on;
# Reject requests with both Transfer-Encoding and Content-Length
if ($http_transfer_encoding != "") {
return 400;
}
}
Middleware to Validate Signature Scope
Add a middleware that ensures the signed payload includes the exact body bytes received, preventing partial-signature vulnerabilities.
import json
import hashlib
from django.core.signing import TimestampSigner, BadSignature
class HmacRequestVerificationMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.signer = TimestampSigner(key='your-secret-key')
def __call__(self, request):
if request.method == 'POST':
body = request.body
received_sig = request.headers.get('X-API-Signature')
if received_sig:
computed = self.signer.sign(hashlib.sha256(body).hexdigest())
if not constant_time_compare(computed, received_sig):
from django.http import HttpResponseForbidden
return HttpResponseForbidden('Invalid signature scope')
return self.get_response(request)
def constant_time_compare(val1, val2):
return hashlib.compare_digest(val1, val2)
By combining these practices—signing the full payload, rejecting ambiguous encodings, and validating scope with middleware—you reduce the risk of request smuggling while continuing to use HMAC signatures for integrity in Django.