HIGH broken access controldjangohmac signatures

Broken Access Control in Django with Hmac Signatures

Broken Access Control in Django with Hmac Signatures — how this combination creates or exposes the vulnerability

Broken Access Control (BOLA/IDOR) in Django when HMAC signatures are used for request authentication can occur when signature validation is incomplete, applied at the wrong layer, or bypassed by trusted internal flows. A typical pattern is a Django view that accepts an object identifier (e.g., /documents/123) and validates an HMAC to prove the client may reference that identifier. If the signature is computed only over the object ID and a shared secret, but the server does not also confirm that the requesting user (or API key) is allowed to access that specific object, an attacker who knows or guesses another valid ID can reuse a captured signature in a BOLA/IDOR attack.

Consider a Django endpoint that generates an HMAC over an integer resource_id and a timestamp, then uses that signature to allow time-limited access without further permission checks:

import hmac
import hashlib
import time
from django.http import JsonResponse, HttpResponseBadRequest
from django.views import View

SECRET = b'super-secret-key'

def generate_hmac(resource_id: int) -> str:
    payload = f'{resource_id}:{int(time.time()) // 60}'
    return hmac.new(SECRET, payload.encode(), hashlib.sha256).hexdigest()

class DocumentView(View):
    def get(self, request, resource_id):
        provided = request.GET.get('sig')
        expected = generate_hmac(resource_id)
        if not hmac.compare_digest(provided, expected):
            return HttpResponseBadRequest('Invalid signature')
        # BOLA risk: no check whether request.user can access resource_id
        return JsonResponse({'resource_id': resource_id, 'data': 'sensitive'})

This example illustrates the core issue: the signature proves the client knew the secret and the resource_id at signing time, but does not bind the resource to an authenticated user or tenant. An attacker who intercepts a valid signature can iterate over other resource IDs and use the same signature pattern if the server does not embed user context or ownership into the signed payload. The vulnerability is a BOLA/IDOR rooted in insufficient authorization checks, not in the HMAC algorithm itself.

Another common anti-pattern is trusting HMAC validation performed by middleware or a helper before the view runs, without ensuring the downstream Django permission system (e.g., object-level permissions, row-level scopes) is consulted. If the signature is accepted and the request is allowed to proceed without re-evaluating access per-object, the attacker can traverse IDs freely. This is especially risky when signatures are used for unauthenticated endpoints or shared API keys where ownership is not encoded.

Real-world attack patterns include enumeration via sequential or guessed IDs, or leveraging leaked signatures from logs or error messages. Because the scan category BOLA/IDOR specifically tests for these authorization gaps, middleBrick flags such endpoints when signatures do not incorporate user or tenant binding and when views skip per-object permission checks.

Hmac Signatures-Specific Remediation in Django — concrete code fixes

To fix Broken Access Control when using HMAC signatures in Django, bind the signature to the current user (or API key tenant) and enforce object-level permissions before serving data. The signed payload must include a user identifier or tenant context, and the view must verify Django permissions or a row-level scope even after a valid signature.

Here is a secure example that includes user_id in the HMAC and checks object ownership in the view:

import hmac
import hashlib
import time
from django.http import JsonResponse, HttpResponseBadRequest
from django.views import View
from django.shortcuts import get_object_or_404
from myapp.models import Document

def generate_hmac(resource_id: int, user_id: int) -> str:
    payload = f'{resource_id}:{user_id}:{int(time.time()) // 60}'
    return hmac.new(SECRET, payload.encode(), hashlib.sha256).hexdigest()

class SecureDocumentView(View):
    def get(self, request, resource_id):
        provided = request.GET.get('sig')
        user_id = request.user.id if request.user.is_authenticated else None
        if user_id is None:
            return HttpResponseBadRequest('Authentication required')
        expected = generate_hmac(resource_id, user_id)
        if not hmac.compare_digest(provided, expected):
            return HttpResponseBadRequest('Invalid signature')
        # Enforce object-level ownership/Django permissions
        document = get_object_or_404(Document, pk=resource_id, owner=request.user)
        return JsonResponse({'resource_id': document.id, 'data': document.content})

Key remediation points:

  • Include user or tenant context in the HMAC payload so a signature cannot be reused across users.
  • Perform signature comparison using a constant-time function (hmac.compare_digest) to avoid timing attacks.
  • After validating the signature, enforce Django model-level permissions or explicit row ownership checks; do not rely on the signature alone for authorization.
  • Use short-lived timestamps or nonces to prevent replay, and consider binding additional request metadata (e.g., HTTP method) into the signed string.

If you use an API key or service account instead of a user, embed that key identifier in the payload and validate scopes or roles server-side before returning sensitive resources.

Frequently Asked Questions

Why does including user context in the HMAC prevent BOLA/IDOR?
Including user context ensures the signature is specific to a user-resource pair. An attacker cannot reuse a signature across different users or tenants because the embedded user_id changes the signed payload, making enumeration via intercepted signatures ineffective.
Is HMAC alone enough for authorization in Django APIs?
No. HMAC can provide integrity and authenticity for a request, but it must be combined with server-side authorization checks such as Django object permissions or row-level security to prevent Broken Access Control. Always validate ownership or scopes after signature verification.