Email Injection in Django with Hmac Signatures
Email Injection in Django with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Email Injection is a class of injection vulnerability where attacker-controlled data is inserted into email headers or the message body, enabling header injection, mail relay abuse, or phishing. In Django, this commonly occurs when user input is directly interpolated into email headers such as To, Cc, Subject, or From without strict validation or sanitization. Even when HMAC signatures are used to authenticate requests or verify the integrity of a payload, a developer can mistakenly trust the signature as proof of safety and pass user-supplied data into email routines, inadvertently creating an injection path.
A typical pattern: a confirmation link in an email includes an HMAC-signed token (e.g., user ID, timestamp, action) to prevent tampering. The view verifies the signature, extracts the payload, and then uses one of the extracted fields—such as a user-supplied display name or email address—in an email header. Because the signature only guarantees the payload has not been altered by an attacker, it does not guarantee the data is safe for email headers. Newline characters (\n, \r) in the display name or email address can break header structure and enable injection of additional headers like Cc: or Reply-To:. In this way, the HMAC signature protects integrity but does not protect against injection; without output encoding or header-safe validation, the combination creates a bypass where trusted signature logic coexists with unsafe email composition.
Attackers can exploit this to conduct mail relay abuse, spoof headers, or perform phishing by injecting malicious recipients or redirecting replies. Since the signature validates the token but not the semantic safety of the data within headers, the vulnerability is not detected by integrity checks alone. This underscores the need for context-aware output handling: HMAC verification and email-safe handling must be applied as separate, independent controls rather than assuming one negates the need for the other.
Hmac Signatures-Specific Remediation in Django — concrete code fixes
Remediation centers on strict input validation, safe header construction, and using Django utilities designed for email safety. Never trust data merely because it is covered by an HMAC signature; treat it as untrusted for email contexts. Validate email addresses with Django’s validators, ensure display names do not contain control characters, and use EmailMessage or send_mail which handle header encoding properly.
Below are concrete examples demonstrating secure practices.
Example 1: Safe email header construction with user input
import logging
from django.core.mail import EmailMessage
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
from django.utils.encoding import force_str
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.utils.crypto import constant_time_compare
from django.core.signing import TimestampSigner, BadSignature, SignatureExpired
logger = logging.getLogger(__name__)
signer = TimestampSigner()
def build_safe_email(recipient_email, display_name):
# Validate and normalize email
try:
validate_email(recipient_email)
except ValidationError:
logger.warning('Invalid recipient email: %s', recipient_email)
raise
# Restrict display name to safe characters; strip or reject newlines
if display_name is None:
display_name = ''
display_name = force_str(display_name)
if '\n' in display_name or '\r' in display_name:
logger.warning('Display name contains newline: %s', display_name)
raise ValueError('Display name contains invalid characters')
# Encode header using Django's Header to ensure safe encoding
from email.header import Header
from email.message import EmailMessage
safe_name = Header(display_name, 'utf-8').encode()
msg = EmailMessage(
subject='Your subject here',
body='Email body.',
from_='[email protected]',
to=[recipient_email],
)
if safe_name:
msg['To'] = f'{safe_name} <{recipient_email}>'
else:
msg['To'] = recipient_email
return msg
# Example usage with HMAC-signed token verification
def confirmation_view(request, token):
try:
data = signer.unsign(token, max_age=3600)
except (BadSignature, SignatureExpired):
logger.warning('Invalid or expired token')
return HttpResponseBadRequest('Invalid link')
# data is a dict-like string or JSON; parse safely
# Assume data was serialized as JSON and contains user_email and display_name
import json
try:
payload = json.loads(data)
user_email = payload['email']
display_name = payload.get('name', '')
except (json.JSONDecodeError, KeyError):
logger.warning('Invalid payload in token')
return HttpResponseBadRequest('Invalid data')
# Build and send email safely
msg = build_safe_email(user_email, display_name)
msg.send()
return HttpResponse('Confirmation email sent')
Example 2: Using Django’s send_mail with encoded headers
from django.core.mail import send_mail
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
from email.header import Header
def send_verification_email(user_email, raw_display_name):
try:
validate_email(user_email)
except ValidationError:
logger.warning('Invalid email: %s', user_email)
raise
display_name = raw_display_name or ''
if '\n' in display_name or '\r' in display_name:
raise ValueError('Display name contains newline')
# Header.encode ensures non-ASCII and special chars are encoded safely
safe_to = f'{Header(display_name, "utf-8").encode()} <{user_email}>'
send_mail(
subject='Welcome',
message='Welcome to our service.',
from_email='[email protected]',
recipient_list=[user_email],
html_message=None,
fail_silently=False,
)
Mapping to OWASP API Top 10 and practical guidance
- A01:2023 Broken Object Level Authorization (BOLA)/IDOR: Ensure HMAC tokens include a per-request nonce or timestamp and are validated strictly to prevent replay. Do not use tokens to imply data safety for headers.
- A07:2021 Identification and Authentication Failures: Protect the signing key and rotate keys periodically. Short-lived tokens reduce the impact of leakage.
- A05:2021 Security Misconfiguration: Disable debug mode in production and avoid exposing internal paths in error messages returned during email construction failures.
By separating token verification from email composition, validating each input for its target context, and using Django’s header encoding utilities, you maintain the integrity benefits of HMAC while preventing Email Injection.