Crlf Injection in Django with Hmac Signatures
Crlf Injection in Django with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Crlf Injection occurs when an attacker can inject CRLF sequences (\r\n) into data that later influences header lines or control flow. In Django, this risk can intersect with Hmac Signatures when a developer uses a signed value in a header or redirects to a location that includes user-controlled data. If the signed payload or a related field is reflected into a header without validation, an attacker can append new headers or perform response splitting.
Consider a scenario where a Django view builds a redirect URL using a signed token that includes a user-controlled next parameter. If the next parameter contains \r\n and the developer does not sanitize it, the signed value may be stored or logged, but the injection occurs when the application places the next value into a Location header. An example unsafe pattern is shown below:
from django.http import HttpResponseRedirect
from django.utils.http import url_has_allowed_host_and_scheme
def unsafe_redirect_view(request):
next_url = request.GET.get('next', '/')
# Intentionally unsafe: no validation beyond host check
if url_has_allowed_host_and_scheme(next_url, allowed_hosts={request.get_host()}):
return HttpResponseRedirect(next_url)
# else fallback
return HttpResponseRedirect('/')
If an attacker crafts a request with next set to https://example.com/\r\nX-Injected: header, and the application uses a signed version of next in logs or passes it through a header, the injected line break can lead to header injection. When Hmac Signatures are used to sign query parameters or headers, the signature does not inherently prevent splitting if the signed value is later concatenated into a header line without proper encoding or validation.
In API or webhook flows, a signed query parameter may be reflected into a header by a downstream service. For example, a Django view might generate a signed URL that includes an integrity token, and the consumer of that URL might place the token into a custom header. If the signed token contains unescaped line breaks, the consumer’s header construction can be abused to inject additional headers, potentially leading to response splitting or cache poisoning.
Django’s signing utilities (e.g., TimestampSigner) produce a string that includes the payload and signature. If the signed string is placed directly into a header, an attacker who can influence the payload before signing might attempt to include \r\n. However, because the signature covers the payload, modifying the payload after signing breaks the signature, so the attacker cannot forge a valid signed injection without the secret. The practical risk arises when the signed value is used in a multi-step flow where the server trusts its own signature but does not enforce strict output encoding for headers.
Key conditions that make Crlf Injection viable with Hmac Signatures in Django:
- User input is included in the data that is signed and later used in a header or redirect target.
- The application does not enforce canonicalization and strict validation of line breaks in values that reach HTTP response headers.
- Downstream components trust the signed value and place it into headers without re-encoding or validation.
Hmac Signatures-Specific Remediation in Django — concrete code fixes
Remediation focuses on preventing injected line breaks from affecting header construction and ensuring that signed values are handled in a way that preserves integrity without enabling splitting.
1) Canonicalize and reject dangerous characters before signing or using in headers
Strip or reject \r and \n from any data that will be placed in headers or redirect targets, even if the data is signed. Do not rely on the signature to prevent header injection; prevent the characters from appearing in the header context.
import re
CRLF_RE = re.compile(r'[\r\n]')
def safe_header_value(value: str) -> str:
"""Remove CRLF characters to prevent response splitting."""
return CRLF_RE.sub('', value)
Apply this function to any user-controlled string before using it in a header or redirect location.
2) Use Django’s redirect with safe URLs and avoid placing signed values directly in Location headers
Always resolve redirects to known-safe paths or validated absolute URLs. Avoid passing raw signed tokens in Location headers. If you must include a token, encode it (e.g., base64) and validate it on receipt.
from django.http import HttpResponseRedirect
from django.utils.http import url_has_allowed_host_and_scheme, urlencode
from django.core.signing import TimestampSigner
signer = TimestampSigner()
def safe_redirect_view(request):
next_url = request.GET.get('next', '/')
# Restrict to relative paths or validated absolute URLs
if not url_has_allowed_host_and_scheme(next_url, allowed_hosts={request.get_host()}):
next_url = '/'
# Optionally sign a small token for integrity, but do not inject it into Location raw
token = signer.sign('safe_redirect')
# Pass token as a separate query parameter if needed, and validate on return
return HttpResponseRedirect(f'{next_url}?token={urlencode({"token": token})}')
3) Encode signed values when used in headers
If a signed value must appear in a header (e.g., a custom integrity token), ensure it is encoded so that line breaks cannot be injected. Base64 encoding is a safe choice.
import base64 django.core.signing.dumps = lambda value: base64.urlsafe_b64encode(value.encode()).decode() # When verifying: # decoded = base64.urlsafe_b64decode(token.encode()).decode()
4) Validate and sanitize all inputs before inclusion in any header-producing context
Use Django’s built-in validators and avoid concatenating raw strings into headers. For webhook or API responses, construct headers programmatically and avoid string templates that could allow line breaks.
from django.http import HttpResponse
def build_response(headers_dict):
response = HttpResponse()
for key, value in headers_dict.items():
# Ensure key and value do not contain line breaks
safe_key = safe_header_value(key)
safe_value = safe_header_value(value)
response[safe_key] = safe_value
return response
By combining input validation, character canonicalization, and careful use of Django’s signing utilities, you mitigate Crlf Injection risks while still using Hmac Signatures for integrity checks.