HIGH insecure direct object referencedjangohmac signatures

Insecure Direct Object Reference in Django with Hmac Signatures

Insecure Direct Object Reference in Django with Hmac Signatures — how this specific combination creates or exposes the vulnerability

Insecure Direct Object Reference (BOLA/IDOR) occurs when an API exposes a reference to an internal object—such as a numeric primary key or a UUID—and allows an authenticated subject to access or modify that object without verifying that the subject is authorized for that specific instance. Django applications commonly parameterize URLs with identifiers like pk or user_id and then perform a direct lookup, for example MyModel.objects.get(pk=request.GET["pk"]), without confirming that the authenticated user owns or is permitted to access that specific record. When Hmac Signatures are introduced to provide tamper-proof identifiers, the risk shifts rather than disappears: if the Hmac does not tightly bind authorization context, an attacker can manipulate or replay signed tokens to traverse object references they should not reach.

Consider a Django endpoint that accepts a signed identifier for a document: /documents/eyJwcm9kdWN0X2lkIjogMTIzfQ.signature. If the server verifies only the Hmac integrity of the payload and then loads Document.objects.get(pk=payload["document_id"]) without checking that the authenticated user has permission on that document, the signed token becomes a portable reference that bypasses ownership checks. The signature ensures the value has not been altered in transit, but it does not ensure the subject is allowed to access that particular object. This is a classic BOLA/IDOR: the reference is predictable or enumerable, and authorization is evaluated incompletely. An attacker who can obtain one valid signed reference—perhaps from their own resources—may increment the numeric key or substitute another UUID present in the same namespaced set, and the Hmac verification will pass if the server reconstructs the payload with the attacker-controlled value without re-evaluating scope.

Django URL designs that embed signed objects in paths or query parameters can unintentionally encourage this pattern. For example, a view that decodes a base64 Hmac payload to reveal an internal primary key and then queries directly by that key does not automatically inherit the permissions that would apply to an ORM-level relationship check. Even if the signature prevents tampering, the view must still enforce that the resolved object belongs to or is governed by the requesting user or tenant. Without such checks, the Hmac layer provides confidentiality and integrity for the identifier but not authorization, leaving the endpoint vulnerable to horizontal or vertical privilege escalation depending on the scope of access. The root cause is treating the Hmac as a sufficient authorization mechanism rather than as one component that must be paired with explicit object-level permissions.

Hmac Signatures-Specific Remediation in Django — concrete code fixes

To secure Django endpoints that use Hmac Signatures, treat the signature as a tamper-proof token carrier and still enforce object-level permissions against the database. Decode the Hmac payload, validate its structure, and then apply the same instance-level authorization checks you would use for any direct object reference. Below is a concrete, idiomatic approach that combines Hmac verification with Django model permission checks.

Example: Signed payload with strict ownership validation

Assume you sign a JSON payload containing a model identifier and a scoped context such as tenant or user. The view decodes the Hmac, ensures the payload is well-formed, retrieves the object, and then confirms the requesting user has the right to access it.

import base64
import json
import hmac
import hashlib
from django.http import HttpResponseBadRequest, HttpResponseForbidden, JsonResponse
from django.views import View
from .models import Document
from django.core.exceptions import PermissionDenied

def verify_hmac_signature(data: bytes, signature: str, secret: str) -> bool:
    computed = hmac.new(
        secret.encode("utf-8"),
        msg=data,
        digestmod=hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(computed, signature)

class DocumentDetailView(View):
    http_method_names = ["get"]
    HMAC_SECRET = "your-secure-secret"

    def get(self, request, signed_token: str):
        try:
            encoded_payload, received_sig = signed_token.rsplit(".", 1)
        except ValueError:
            return HttpResponseBadRequest({"error": "invalid_token_format"})

        if not verify_hmac_signature(encoded_payload.encode("utf-8"), received_sig, self.HMAC_SECRET):
            return HttpResponseForbidden({"error": "invalid_signature"})

        try:
            payload = json.loads(base64.urlsafe_b64decode(encoded_payload + "==").decode("utf-8"))
        except Exception:
            return HttpResponseBadRequest({"error": "invalid_token_payload"})

        document_id = payload.get("document_id")
        if document_id is None:
            return HttpResponseBadRequest({"error": "missing_document_id"})

        try:
            document = Document.objects.get(pk=document_id)
        except Document.DoesNotExist:
            return HttpResponseBadRequest({"error": "document_not_found"})

        # Critical: enforce ownership or tenant scope, even with a valid Hmac
        if not self.user_can_access(request.user, document):
            raise PermissionDenied()

        return JsonResponse({"document_id": document.id, "title": document.title})

    def user_can_access(self, user, document) -> bool:
        # Replace with your actual permission logic: ownership, team membership, ACL, etc.
        return document.owner == user or document.shared_with.filter(id=user.id).exists()

Remediation checklist

  • Always verify Hmac integrity before decoding, and use constant-time comparison to avoid timing attacks.
  • Never skip model-level permission checks after decoding a signed identifier; treat the signature as integrity only, not as authorization.
  • Scope identifiers when feasible (e.g., include tenant_id in the signed payload and re-check it against the object).
  • Prefer opaque references (such as UUIDs mapped via a join table) over sequential primary keys to reduce enumeration risk, and ensure that mappings are also guarded by permissions.
  • Log suspicious token reuse or missing permission checks for monitoring, but do not expose internal details to the client.

By combining Hmac integrity with explicit instance-level authorization, you retain the benefits of tamper-proof tokens while closing the BOLA/IDOR gap that an attacker could exploit through predictable or enumerable references.

Related CWEs: bolaAuthorization

CWE IDNameSeverity
CWE-250Execution with Unnecessary Privileges HIGH
CWE-639Insecure Direct Object Reference CRITICAL
CWE-732Incorrect Permission Assignment HIGH

Frequently Asked Questions

Does a valid Hmac Signature guarantee that the referenced object can be accessed?
No. Hmac Signatures ensure integrity and authenticity of the token contents, but they do not enforce authorization. You must still perform object-level permission checks to confirm the requesting subject is allowed to access that specific resource.
How can I reduce the risk of IDOR when using numeric primary keys with Hmac?
Include additional scope in the signed payload (e.g., owner_id or tenant_id), and re-validate that scope against the retrieved object. Consider using opaque identifiers mapped through a permissions-aware join table rather than exposing raw primary keys.