HIGH sql injectiondjangohmac signatures

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 IDNameSeverity
CWE-20Improper Input Validation HIGH
CWE-22Path Traversal HIGH
CWE-74Injection CRITICAL
CWE-77Command Injection CRITICAL
CWE-78OS Command Injection CRITICAL
CWE-79Cross-site Scripting (XSS) HIGH
CWE-89SQL Injection CRITICAL
CWE-90LDAP Injection HIGH
CWE-91XML Injection HIGH
CWE-94Code Injection CRITICAL

Frequently Asked Questions

Does a valid HMAC prevent SQL injection in Django?
No. HMAC ensures data integrity and authenticity but does not sanitize input. Unsafe query construction after verification can still lead to SQL injection; always use parameterized queries and strict validation.
What should I do if I must use raw SQL with signed parameters?
Use Django’s parameterized cursor with placeholders for all data values; whitelist and validate any identifiers (tables/columns) against an allowlist; never concatenate user-influenced values into SQL strings.