Sql Injection in Django with Hmac Signatures
Sql Injection in Django with Hmac Signatures — how this specific combination creates or exposes the vulnerability
SQL injection remains a critical risk in Django when dynamic query building interacts with signature-based routing or parameter passing. HMAC signatures are commonly used to ensure integrity and authenticity of query parameters, for example to prevent tampering with record IDs in URLs. When developers include user-controlled data in SQL expressions while also validating an HMAC, they may mistakenly assume signature verification alone prevents injection. However, the signature does not sanitize or parameterize inputs; if the application reconstructs SQL using string formatting or unsanitized ORM inputs after verifying the signature, injection remains possible.
Consider a pattern where a signed query parameter (e.g., a serialized record identifier) is verified via HMAC and then used directly in a filter. If the developer concatenates values into raw SQL or constructs dynamic Q objects using unescaped data, the signature check passes but the ORM or raw cursor executes malicious payloads. For instance, an attacker could tamper with parameter values (if the signature key is leaked or algorithm is weak) or exploit weak composition of signed data that later participates in raw SQL. Even when using Django’s ORM, unsafe use of extra, raw, or cursor.execute with user-influenced fields can lead to injection despite valid HMAC verification.
Additionally, logging or error handling that exposes signature validation outcomes may leak information useful for cryptanalysis, weakening the HMAC’s protective role. Because HMAC ensures integrity but not input safety, developers must treat signed data as untrusted for SQL construction. The combination does not inherently introduce new injection vectors, but it can create a false sense of security that leads to unsafe query assembly patterns, especially when integrating legacy raw SQL or complex joins that fall outside Django’s parameterized query guarantees.
Hmac Signatures-Specific Remediation in Django — concrete code fixes
To safely use HMAC signatures in Django while preventing SQL injection, always rely on Django’s parameterized queries and strict input validation after signature verification. Do not reconstruct SQL via string interpolation or concatenation even if the signature is valid. Below are concrete, secure patterns with working code examples.
1. Signed integer ID used in a safe ORM query
import hmac
import hashlib
import base64
from django.http import Http404
from django.core.signing import TimestampSigner, BadSignature, SignatureExpired
from myapp.models import Product
def get_product(request, signed_id):
signer = TimestampSigner(key="your-secret-key", salt="product_lookup")
try:
unsigned = signer.unsign(signed_id, max_age=300) # raises BadSignature or SignatureExpired
pk = int(unsigned) # enforce integer type
except (BadSignature, SignatureExpired, ValueError):
raise Http404("Invalid or expired signature")
# Safe: ORM uses parameterized SQL under the hood
product = Product.objects.filter(pk=pk).first()
if product is None:
raise Http404("Product not found")
return product
2. Signed JSON payload with strict schema validation before raw cursor use
import json
import hmac
from django.http import JsonResponse
from django.db import connection
def execute_signed_query(request, signed_payload):
expected = "my-secret"
if not hmac.compare_digest(hmac.new(expected.encode(), signed_payload.encode(), hashlib.sha256).hexdigest(), request.GET.get("sig", "")):
return JsonResponse({"error": "Invalid signature"}, status=403)
try:
data = json.loads(signed_payload)
except json.JSONDecodeError:
return JsonResponse({"error": "Invalid JSON"}, status=400)
# Validate schema strictly
if not isinstance(data.get("table"), str) or not isinstance(data.get("column"), str) or not isinstance(data.get("value"), str):
return JsonResponse({"error": "Invalid payload schema"}, status=400)
# Safe: parameterized query with placeholders; table/column cannot be parameterized, so whitelist check is required
allowed_tables = {"products", "orders"}
allowed_columns = {"name", "sku", "status"}
if data["table"] not in allowed_tables or data["column"] not in allowed_columns:
return JsonResponse({"error": "Unauthorized resource"}, status=403)
with connection.cursor() as cursor:
cursor.execute(
f"SELECT * FROM {data['table']} WHERE {data['column']} = %s",
[data["value"]]
)
rows = cursor.fetchall()
return JsonResponse({"rows": [dict(r) for r in rows]})
3. Using Django’s signing module for safe redirect targets with HMAC
from django.core.signing import JSONSigner, BadSignature
from django.shortcuts import redirect
def safe_redirect_view(request, signed_target):
signer = JSONSigner(key="redirect-secret")
try:
target = signer.loads(signed_target, salt="redirect")
except BadSignature:
return redirect("/safe-default/")
# Enforce same-host rule to open redirect
if not target.startswith(request.build_absolute_uri("/").split("?")[0]):
return redirect("/safe-default/")
return redirect(target)
4. Defense-in-depth: combine HMAC with Django form validation and model clean
from django import forms
from django.core.exceptions import ValidationError
import hmac
class SecureDataForm(forms.Form):
record_id = forms.IntegerField()
action = forms.ChoiceField(choices=["view", "edit"])
sig = forms.CharField(max_length=64)
def clean(self):
cleaned_data = super().clean()
payload = f"{cleaned_data['record_id']}:{cleaned_data['action']}"
expected = hmac.new(b"form-secret", payload.encode(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, cleaned_data.get("sig", "")):
raise ValidationError("Invalid signature")
# Additional business logic checks can go here
return cleaned_data
Key remediation principles
- Verify HMAC before using data; re-validate type and constraints after verification.
- Use parameterized queries (ORM or cursor with placeholders) for all data values; never interpolate identifiers or table/column names.
- Whitelist allowed tables/columns when dynamic SQL is unavoidable; avoid reflective or string-built queries.
- Treat signed data as integrity-protected but not safe; apply the same input validation rules as unsigned data.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |