Integer Overflow in Django with Hmac Signatures
Integer Overflow in Django with Hmac Signatures — how this specific combination creates or exposes the vulnerability
An integer overflow in the context of Django HMAC signatures occurs when a length or size value used in signature computation exceeds the native integer range of the platform (e.g., 32-bit vs 64-bit), wrapping to a lower value and producing an incorrect signature. In Django, developers commonly use HMAC to sign data such as timestamps, tokens, or serialized payloads, often with utilities like django.core.signing or manual hmac.compare_digest checks. If a user-controlled integer (for example, a timestamp, an object ID, or a size parameter) is included in the data that is hashed, and that integer can grow beyond expected bounds, the resulting hash can collide or truncate in unexpected ways, bypassing intended integrity checks.
Consider a scenario where an unsigned 32-bit integer is used to represent a creation timestamp or an incremental counter. If an attacker can manipulate that integer to wrap around (for instance, setting it near the maximum value then incrementing once), the HMAC input changes in a way that may still produce a valid-looking signature if the application does not validate the integer range before signing. In Django, this can manifest when using signed cookies or serialized tokens that embed numeric fields without range validation. The framework’s signing module uses TimestampSigner and JSONSerializer, which serialize Python objects into a canonical form before signing. If the serialized representation of an integer is not bounded, an overflowed integer can lead to signature collisions, potentially enabling an attacker to forge a valid signature for a different logical value that hashes to the same truncated representation.
Additionally, if the HMAC key derivation or the payload construction involves arithmetic on integers (e.g., computing an offset or a size), and those integers are not validated, an overflow can cause the computed signature to differ from what the server expects. While Django’s signing module uses SHA1 by default, the integrity guarantee relies on both the secrecy of the key and the correctness of the data before signing. An unchecked integer overflow can cause the data used in the HMAC to diverge from the application’s logical intent, leading to a situation where a tampered payload appears authentic. This is especially risky in security-sensitive flows such as password reset tokens or one-time download links, where the signed value includes an integer identifier.
Real-world parallels can be drawn to issues where numeric parameters in signed tokens were manipulated to cause collisions or privilege escalation. Although not a direct CVE in Django itself, similar classes of flaws appear in libraries and protocols that combine HMAC with unbounded numeric inputs. The OWASP API Security Top 10 category ‘2: Broken Authentication’ and related implementation concerns highlight the importance of validating and bounding all inputs before cryptographic operations. In practice, this means ensuring that integers used in HMAC payloads are constrained, serialized consistently, and verified server-side before the signature is computed or verified.
Hmac Signatures-Specific Remediation in Django — concrete code fixes
Remediation focuses on validating and bounding integer values before they are included in HMAC input, using constant-time comparison, and ensuring serialization is deterministic. In Django, avoid relying on raw integer concatenation for signed payloads; instead, serialize data with a well-defined format and enforce range checks.
Example 1: Safe signed timestamp using TimestampSigner
import time
from django.core.signing import TimestampSigner, BadSignature, SignatureExpired
signer = TimestampSigner()
value = 12345 # integer that must be bounded
if not (0 <= value <= 2**32 - 1):
raise ValueError('value out of range')
token = signer.sign(value)
# Later, verify with expiration control
try:
result = signer.unsign(token, max_age=3600)
verified_value = int(result)
except (BadSignature, SignatureExpired):
# Handle invalid or expired token safely
verified_value = None
Example 2: HMAC with explicit payload construction and integer validation
import hmac
import hashlib
import json
from django.conf import settings
def create_signed_payload(user_id, timestamp):
# Validate integer bounds explicitly
if not (0 <= user_id < 2**31):
raise ValueError('user_id out of allowed range')
if not (0 <= timestamp <= 2**31):
raise ValueError('timestamp out of allowed range')
payload = json.dumps({'user_id': user_id, 'timestamp': timestamp}, sort_keys=True, separators=(',', ':'))
key = settings.SECRET_KEY.encode()
signature = hmac.new(key, payload.encode(), hashlib.sha256).hexdigest()
return {'payload': payload, 'signature': signature}
def verify_signed_payload(payload_str, signature, key=None):
expected = hmac.new(key or settings.SECRET_KEY.encode(), payload_str.encode(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
raise ValueError('invalid signature')
data = json.loads(payload_str)
# Re-validate integer ranges on verification side
if not (0 <= data['user_id'] < 2**31):
raise ValueError('user_id out of allowed range')
return data
Example 3: Using Django’s signing module with salted key to isolate domains
from django.core.signing import Signer
# Use a salt to ensure signatures are scoped to a specific context
signer = Signer(key=settings.SECRET_KEY, salt='user_invite_token')
user_id = 999
if not (0 <= user_id < 1000000):
raise ValueError('invalid user identifier')
token = signer.sign(str(user_id))
# Verification will raise BadSignature if invalid
try:
original = signer.unsign(token)
verified_id = int(original)
except Exception:
verified_id = None
Best practices summary
- Validate integer ranges explicitly before including them in HMAC input.
- Use deterministic serialization (e.g., JSON with sort_keys) to avoid signature variability.
- Use
hmac.compare_digestfor signature comparison to avoid timing attacks. - Apply salting in Django Signer to isolate signing contexts and reduce cross-application forgery risk.
- Treat signed integers as untrusted until re-validated on the server side, even if a signature is valid.