Prototype Pollution in Django with Hmac Signatures
Prototype Pollution in Django with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Prototype pollution in Django when HMAC signatures are used incorrectly arises when an attacker can influence the key or data that is signed without detection. If a Django application builds the material to be signed from user-controlled input, an attacker may inject properties into objects or dictionaries that affect how the signature is computed or validated. Because HMAC relies on exact byte-for-byte input to produce a valid signature, subtle changes in object structure can change the signed payload in unexpected ways, leading to security-relevant effects.
Consider a scenario where a Django view deserializes JSON, applies user-supplied values into a Python dictionary, and then signs the dictionary using HMAC before storing or transmitting it. If the dictionary is mutated after signing or if the signing process does not canonicalize keys and types, an attacker can add or modify keys that the server logic treats as important, such as is_admin, amount, or user_id. Because HMAC does not inherently prevent prototype-style mutation of the resulting object on the server side, the application may trust the tampered data if validation is performed after signature verification rather than before or during construction.
Django does not protect against prototype pollution by default; pollution occurs at the Python level when dictionaries or objects are updated with attacker-controlled keys. When HMAC signatures are used to bind data to a session, a token, or a serialized object, failing to validate and sanitize keys before signing means the attacker can introduce unexpected keys that change behavior after verification. For example, if a signature is computed over a JSON object that is later merged into a Django model or context, injected keys can bypass intended authorization checks or overwrite configuration values. This is particularly dangerous if the application relies on signature integrity to enforce security properties without independently verifying each semantic constraint.
Real-world patterns include using HMAC to secure serialized form data, querystring parameters, or API tokens where the server trusts the structure of the signed payload. If the application does not enforce strict schema validation and canonical representation before signing, an attacker can exploit prototype pollution to alter the meaning of the signed data. For instance, adding keys that change pricing, impersonate users, or escalate privileges may be possible when the server applies signature checks but does not validate the logical integrity of each field. The risk is compounded when the same HMAC key is reused across multiple data structures or when debugging endpoints expose signature generation details that aid an attacker in crafting valid polluted payloads.
To mitigate this specific risk, treat HMAC signatures as integrity checks rather than authorization mechanisms. Always validate and sanitize all user input before incorporating it into the data that is signed, enforce strict schemas, and avoid mutating signed dictionaries or objects after verification. Combining HMAC with explicit field-level validation and server-side checks significantly reduces the chance that prototype pollution can affect security-critical properties.
Hmac Signatures-Specific Remediation in Django — concrete code fixes
Defensive usage of HMAC in Django requires canonicalizing data before signing, validating all keys independently of the signature, and never relying on signature integrity alone to enforce security properties. Below are concrete, safe patterns that avoid prototype pollution risks tied to HMAC handling.
First, normalize and validate input data before building the signed payload. Use a schema library such as marshmallow or pydantic to ensure only expected keys are present and correctly typed. Do not directly incorporate user-supplied dictionaries into the signed material without strict filtering.
import json
import hmac
import hashlib
import secrets
from typing import Dict, Any
def canonical_clean(data: Dict[str, Any]) -> Dict[str, Any]:
# Keep only known safe keys and enforce string keys and deterministic ordering
allowed = {"user_id", "role", "timestamp"}
result = {k: data[k] for k in sorted(data.keys()) if k in allowed}
return result
def build_signed_payload(user_data: Dict[str, Any], secret_key: bytes) -> str:
clean = canonical_clean(user_data)
payload = json.dumps(clean, separators=(",", ":"), sort_keys=True)
signature = hmac.new(secret_key, payload.encode("utf-8"), hashlib.sha256).hexdigest()
return json.dumps({"payload": payload, "signature": signature})
Second, verify data after reconstruction rather than trusting merged structures. Deserialize the payload, recompute the HMAC, and compare using a constant-time function before applying any logic that depends on the content.
import time
def verify_signed_payload(message_bundle: str, secret_key: bytes) -> Dict[str, Any] | None:
try:
bundle = json.loads(message_bundle)
received = bundle["signature"]
payload = bundle["payload"]
expected = hmac.new(secret_key, payload.encode("utf-8"), hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, received):
return None
data = json.loads(payload)
# Re-validate schema after signature verification
if set(data.keys()) != {"user_id", "role", "timestamp"}:
return None
return data
except (KeyError, json.JSONDecodeError, TypeError):
return None
Third, avoid key reuse and bind context into the signature. Include purpose-specific labels and, where relevant, scope identifiers so that a polluted key in one context cannot be reused in another.
def scoped_hmac(data: Dict[str, Any], secret_key: bytes, scope: str) -> str:
canonical = canonical_clean(data)
canonical["_scope"] = scope
payload = json.dumps(canonical, separators=(",", ":"), sort_keys=True)
return hmac.new(secret_key, payload.encode("utf-8"), hashlib.sha256).hexdigest()
These patterns ensure that HMAC signatures are computed over a controlled, canonical representation and that keys are validated independently. By combining schema enforcement, canonical serialization, and context binding, you reduce the risk that prototype pollution can affect the integrity of signed data in Django applications.