Privilege Escalation in Django with Hmac Signatures
Privilege Escalation in Django with Hmac Signatures
In Django, HMAC signatures are often used to guarantee integrity and authenticity of data in cookies, query parameters, or API tokens. When HMAC is implemented incorrectly, it can lead to privilege escalation by allowing an attacker to forge identifiers that the application trusts. A common pattern is to embed a user identifier and a role or permission flag inside a signed value, then deserialize it without re-verifying authorization constraints on each request.
Consider a scenario where a developer creates a signed token containing a user ID and an is_admin flag, then stores it in a cookie. If the signature verification only ensures the value has not been tampered with but does not re-check server-side authorization, an attacker who discovers the signing key—or uses a weak key—can modify the is_admin flag and forge an administrative session. This is a classic privilege escalation via HMAC manipulation, often linked to insecure default serialization and insufficient validation of claims after verification.
Django’s signing module (django.core.signing) provides Signer and TimestampSigner, which are straightforward to use but can be misapplied. For example, developers may sign a serialized representation of a user’s role and later trust the deserialized object without confirming that the requesting user’s session aligns with backend policy. If the signature algorithm is weak, or if the key is exposed, an attacker can craft a valid signature for an elevated role. Additionally, if the application exposes an endpoint that interprets signed input directly (e.g., a token in a URL or POST parameter) and performs sensitive actions based on the embedded claims, the attack surface expands.
Another angle is API token handling: if tokens are derived or verified using HMAC without binding them to a specific scope or context, an attacker who obtains a low-privilege token might be able to reuse it in a different context where higher privileges are inferred. This can happen when tokens include metadata like org_id or role but lack proper server-side checks. The risk is compounded when combined with other weaknesses such as IDOR, where a predictable resource identifier is paired with a signed value that the attacker can manipulate.
To detect this during scanning, middleBrick runs checks that examine how signed values are constructed, transmitted, and validated. It looks for patterns where elevated decisions are made based on deserialized signed data without corroborating server-side permissions, and flags weak signing configurations or missing context binding. The scanner correlates these findings with the authentication and authorization checks to highlight potential privilege escalation paths that involve HMAC-based tokens.
Hmac Signatures-Specific Remediation in Django
Remediation centers on strict validation, context binding, and avoiding trust in client-influenced claims. Always verify the signature, then re-validate server-side permissions before performing any sensitive operation. Do not rely on deserialized claims alone to enforce authorization.
Use Django’s signing utilities with a strong key and algorithm, and ensure the signed payload contains minimal trusted data. Prefer server-side mapping of signed identifiers to permissions rather than embedding roles directly. For example, sign only a user ID, then fetch the user object and consult a server-side role/permission store on each request.
Below are concrete code examples that demonstrate secure handling of HMAC-signed values in Django.
import json
import hmac
import hashlib
import base64
from django.conf import settings
from django.core.signing import Signer, BadSignature
def create_hmac_token(user_id, extra=None):
"""Create a signed token with user_id and optional metadata, using HMAC-SHA256."""
payload = {'user_id': user_id}
if extra:
payload['extra'] = extra
data = json.dumps(payload, separators=(',', ':')).encode('utf-8')
signer = Signer(key=settings.SECRET_KEY, algorithm='sha256')
return signer.sign(data.decode('utf-8'))
def verify_and_get_user_hmac(token):
"""Verify the HMAC signature and return the user object, enforcing server-side permissions."""
signer = Signer(key=settings.SECRET_KEY, algorithm='sha256')
try:
unsigned = signer.unsign(token)
except BadSignature:
raise ValueError('Invalid signature')
payload = json.loads(unsigned)
user_id = payload.get('user_id')
if user_id is None:
raise ValueError('Missing user_id')
# Always re-fetch user from DB; do not trust payload roles
from django.contrib.auth import get_user_model
User = get_user_model()
user = User.objects.filter(pk=user_id).first()
if user is None:
raise ValueError('User not found')
# Server-side authorization check
if not user.has_perm('app.can_access_resource'):
raise PermissionError('Insufficient permissions')
return user
# Example usage in a view
from django.http import JsonResponse, HttpResponseBadRequest
def sensitive_action(request):
token = request.GET.get('token')
if not token:
return HttpResponseBadRequest('Missing token')
try:
user = verify_and_get_user_hmac(token)
except (ValueError, PermissionError):
return HttpResponseBadRequest('Forbidden')
# Proceed with action for the verified, authorized user
return JsonResponse({'status': 'ok', 'user_id': user.id})
For API tokens used in microservices, bind the token to a scope and validate it on each request:
import os
import hmac
import hashlib
import json
import time
from django.conf import settings
def generate_api_token(user_id, scope, expires_in=3600):
"""Generate a scoped, short-lived API token with HMAC signature."""
iat = int(time.time())
exp = iat + expires_in
payload = {'user_id': user_id, 'scope': scope, 'iat': iat, 'exp': exp}
data = json.dumps(payload, separators=(',', ':'), sort_keys=True).encode('utf-8')
key = settings.SECRET_KEY.encode('utf-8')
signature = hmac.new(key, data, hashlib.sha256).hexdigest()
token = base64.urlsafe_b64encode(data).decode('utf-8') + '.' + signature
return token
def validate_api_token(token):
"""Validate token signature, scope, and expiration; return claims if valid."""
try:
encoded, signature = token.rsplit('.', 1)
except ValueError:
return None
key = settings.SECRET_KEY.encode('utf-8')
expected = hmac.new(key, encoded.encode('utf-8'), hashlib.sha256).hexdigest()
if not hmac.compare_digest(signature, expected):
return None
import base64
try:
data = base64.urlsafe_b64decode(encoded + '==').decode('utf-8')
claims = json.loads(data)
except Exception:
return None
now = int(time.time())
if claims.get('exp', 0) < now or claims.get('iat', 0) > now:
return None
# Enforce scope-based server-side checks here
return claims
These examples emphasize that HMAC verification is only one layer; server-side permission checks and scope validation are essential to prevent privilege escalation. middleBrick’s scans can surface weak signing practices and missing authorization checks, helping you refine your implementation.