HIGH time of check time of usedjangohmac signatures

Time Of Check Time Of Use in Django with Hmac Signatures

Time Of Check Time Of Use in Django with Hmac Signatures — how this specific combination creates or exposes the vulnerability

Time Of Check Time Of Use (TOCTOU) occurs when the outcome of a security decision depends on the state of something that can change between the check and the use. In Django, combining Hmac Signatures for request authentication with a non-atomic verification-and-consumption flow creates a classic TOCTOU window. Consider a workflow where a client sends an HTTP request that includes an HMAC signature computed over a canonical representation of the payload and a timestamp. The server first verifies the signature and the timestamp freshness, then later uses the request data to perform an action such as updating a resource or initiating a payment.

If the state that the signature binds to can be altered between verification and use, an attacker can exploit the gap. For example, an attacker could present a valid signature over a benign payload during verification, but before the server acts, a malicious modification occurs in the underlying data (e.g., a database row updated by another request or a cached object mutated). Because the server proceeds based on the earlier check rather than re-evaluating integrity at the moment of use, the request may execute with unintended parameters or on a stale object, leading to privilege escalation, double-spend, or unauthorized state changes.

Django does not provide built-in atomic signature workflows; it leaves the sequencing to developer code. If you verify an Hmac signature in a middleware or view and then rely on request attributes or related model state later without re-validating in the same atomic operation, you expose a TOCTOU path. This is especially risky when the signature covers identifiers that map to mutable resources, or when caching, background tasks, or querysets introduce asynchrony between check and use. An attacker who can influence timing or concurrency can race the verification step with mutations, effectively bypassing the intended integrity guarantees despite the presence of Hmac Signatures.

Hmac Signatures-Specific Remediation in Django — concrete code fixes

To mitigate TOCTOU with Hmac Signatures in Django, design the verification and the state change to be as close to atomic as possible and bind the signature to the exact version of the data used at execution time. Prefer verifying the signature immediately before the operation, and include a version or revision in the signed string so that any mutation invalidates the signature.

Example 1: Atomic verify-and-consume in a view using a timestamp, a nonce, and object version. The signature covers the payload, the object’s current version (or ETag), and the timestamp. Before performing the update, the view recomputes the signature over the same components and checks both signature validity and that the object’s version has not changed.

import hmac
import hashlib
import time
import json
from django.http import JsonResponse, HttpResponseBadRequest
from django.views.decorators.http import require_POST
from django.core.exceptions import SuspiciousOperation
from myapp.models import MyResource

def constant_time_compare(val1, val2):
    return hmac.compare_digest(val1, val2)

def verify_hmac_signature(data, provided_signature, secret_key):
    computed = hmac.new(
        secret_key.encode(),
        msg=json.dumps(data, sort_keys=True).encode(),
        digestmod=hashlib.sha256
    ).hexdigest()
    return constant_time_compare(computed, provided_signature)

@require_POST
def update_resource(request, resource_id):
    body = json.loads(request.body)
    received_signature = request.META.get('HTTP_X_SIGNATURE')
    if not received_signature:
        return HttpResponseBadRequest('Missing signature')

    resource = MyResource.objects.select_for_update().get(pk=resource_id)
    payload = {
        'resource_id': str(resource.id),
        'version': resource.version,  # assume an IntegerField version used as ETag
        'action': body.get('action'),
        'timestamp': int(time.time()),
    }
    # Ensure the timestamp is within a short window (e.g., 60s)
    now = int(time.time())
    if abs(now - payload['timestamp']) > 60:
        raise SuspiciousOperation('Timestamp out of acceptable window')

    if not verify_hmac_signature(payload, received_signature, settings.SECRET_KEY_FALLBACK):
        raise SuspiciousOperation('Invalid signature')

    # Perform the state change only after successful, fresh verification
    resource.action_log.append(payload['action'])
    resource.version += 1  # mutate version to prevent replay
    resource.save()
    return JsonResponse({'status': 'ok', 'new_version': resource.version})

Example 2: Using a library-friendly canonicalization approach for query parameters and body. The signature covers selected headers, the path, and a sorted JSON body; the view recomputes and compares before executing the business logic, ensuring no mutable dependency is used between check and use.

import hmac
import hashlib
import json
from django.http import JsonResponse
from django.views.decorators.http import require_POST
from django.utils.encoding import force_bytes

def make_canonical_body(body):
    # Deterministic JSON with sorted keys
    return json.dumps(body, sort_keys=True, separators=(',', ':'))

@require_POST
def payment_intent(request):
    body = json.loads(request.body)
    method = request.method
    path = request.path
    timestamp = request.META.get('HTTP_X_REQUEST_TIMESTAMP')
    signature_header = request.META.get('HTTP_X_SIGNATURE')

    if not timestamp or not signature_header:
        return JsonResponse({'error': 'Missing timestamp or signature'}, status=400)

    # Bind to the version of the data we will actually use
    canonical_body = make_canonical_body(body)
    to_sign = {
        'method': method,
        'path': path,
        'body': canonical_body,
        'timestamp': timestamp,
    }
    expected = hmac.new(
        settings.SECRET_KEY.encode(),
        msg=json.dumps(to_sign, sort_keys=True).encode(),
        digestmod=hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected, signature_header):
        return JsonResponse({'error': 'Invalid signature'}, status=403)

    # At this point, the data used for signing matches what we use to process
    # No mutable external state was consulted between verification and usage
    # Proceed with idempotent payment logic
    return JsonResponse({'status': 'processed'})

Key practices to prevent TOCTOU with Hmac Signatures: include a version or revision in the signed scope, acquire row-level locks (select_for_update) before verification when the signature depends on mutable state, keep the time window tight, and avoid deferring the use of verified data across asynchronous or cached boundaries. These steps ensure that the integrity check remains tightly coupled with the operation, closing the gap an attacker could otherwise exploit.

Frequently Asked Questions

Why does including a version or ETag in the Hmac payload help prevent TOCTOU?
Including a version or ETag binds the signature to a specific state of the resource. If the resource changes, the version changes and the signature no longer matches, causing verification to fail before any use, which closes the window between check and use.
Can middleware that verifies Hmac signatures still be vulnerable to TOCTOU?
Yes, if the middleware only verifies the signature and then passes control to views or background tasks that read mutable state later. Verification and the state-changing operation must be as close together as possible and re-validate the binding immediately before the action.