Pii Leakage in Django with Hmac Signatures
Pii Leakage in Django with Hmac Signatures — how this specific combination creates or exposes the vulnerability
HMAC signatures are widely used in Django to verify integrity and authenticity of data, for example in password reset tokens, email confirmation links, and API query parameters. When HMAC is implemented incorrectly, it can inadvertently expose personally identifiable information (PII) through timing side channels or insecure handling of signed values.
Consider a typical pattern where a signed token includes a user identifier (such as a user ID or email) that is necessary to locate the PII-bearing record. If the application verifies the signature in a non-constant-time manner or logs the token or its embedded data, an attacker may infer the presence or absence of specific user identifiers via response timing or logs. For example, using Django’s django.core.signing.dumps and django.core.signing.loads without specifying a secure signer can lead to inconsistent behavior depending on whether the signed value exists and matches, potentially leaking information through error messages or timing differences.
Another risk occurs when the signed payload includes sensitive PII directly, such as an email address or a user ID, and the signed value is transmitted in URLs or query parameters. Even if the signature prevents tampering, the PII is still present in logs, browser history, and network traces. If the signature algorithm or key management is weak, an attacker might forge a token containing a different user’s PII, leading to unauthorized access to that data. This is especially relevant when the secret key is hardcoded, stored insecurely, or rotated infrequently, increasing the likelihood of signature compromise and PII exposure.
In API endpoints that accept signed tokens as identifiers, improper error handling can also disclose whether a given identifier exists. For instance, returning a detailed error when signature verification fails versus a generic failure can allow an attacker to enumerate valid users. Additionally, if the application uses the same key for multiple purposes or fails to include a timestamp or nonce, replay attacks become feasible, which may result in the replayed PII being processed multiple times.
To detect such issues, scanning tools evaluate whether signed tokens are constructed with a per-request salt or nonce, whether they avoid embedding PII in the signed payload, and whether verification is performed in a consistent time window. They also check whether the signing key is managed securely and whether tokens are transmitted over encrypted channels. The scan correlates these implementation details with the observed behavior of the endpoint to identify whether PII leakage is plausible through signature misuse.
Hmac Signatures-Specific Remediation in Django — concrete code fixes
Remediation focuses on preventing PII leakage by ensuring signatures do not expose sensitive data and by hardening the signing and verification process. Avoid placing PII inside the signed payload; instead, include only a non-sensitive identifier such as a user primary key, and retrieve the PII from a protected data store after verification.
Use Django’s django.core.signing.TimestampSigner to include a timestamp, which helps prevent replay attacks and allows token expiration. Always use a per-deployment secret key stored in environment variables, and rotate keys periodically. Ensure that signature verification errors result in uniform responses and do not leak information about the nature of the failure.
The following example demonstrates a secure pattern for generating and verifying signed tokens that avoid embedding PII:
import os
from django.core import signing
from django.conf import settings
# Ensure SECRET_KEY is loaded from environment, not hardcoded
signer = signing.TimestampSigner(key=os.getenv('DJANGO_SIGNING_KEY'))
# Generate a signed token with a non-sensitive user identifier
def create_signed_user_token(user_id: int) -> str:
# user_id is not PII; PII is fetched after verification
return signer.sign_object({'user_id': user_id}, salt='user-token')
# Verify the token safely and uniformly
def verify_signed_user_token(token: str):
try:
data = signer.unsign_object(token, salt='user-token', max_age=86400) # 24 hours
user_id = data.get('user_id')
if user_id is None:
return None
# Fetch PII from the database using user_id after authorization checks
# Example: User.objects.get(pk=user_id)
return user_id
except signing.BadSignature:
# Return a generic result to avoid information disclosure
return None
except signing.SignatureExpired:
# Treat expired tokens as invalid without detailed feedback
return None
When transmitting tokens, always use HTTPS to protect the signed value in transit, and avoid logging tokens or any part of the signed payload. If you need to include contextual data for authorization, store it server-side keyed by the signed identifier rather than in the token itself.
For API-based workflows, combine signed tokens with rate limiting and monitoring to detect abnormal request patterns that may indicate probing or enumeration. The middleBrick CLI can be used to scan your Django endpoints to verify that PII is not embedded in signed payloads and that signature verification behaves uniformly under different inputs.
Related CWEs: dataExposure
| CWE ID | Name | Severity |
|---|---|---|
| CWE-200 | Exposure of Sensitive Information | HIGH |
| CWE-209 | Error Information Disclosure | MEDIUM |
| CWE-213 | Exposure of Sensitive Information Due to Incompatible Policies | HIGH |
| CWE-215 | Insertion of Sensitive Information Into Debugging Code | MEDIUM |
| CWE-312 | Cleartext Storage of Sensitive Information | HIGH |
| CWE-359 | Exposure of Private Personal Information (PII) | HIGH |
| CWE-522 | Insufficiently Protected Credentials | CRITICAL |
| CWE-532 | Insertion of Sensitive Information into Log File | MEDIUM |
| CWE-538 | Insertion of Sensitive Information into Externally-Accessible File | HIGH |
| CWE-540 | Inclusion of Sensitive Information in Source Code | HIGH |