HIGH use after freedjangobearer tokens

Use After Free in Django with Bearer Tokens

Use After Free in Django with Bearer Tokens — how this specific combination creates or exposes the vulnerability

Use After Free (UAF) is a memory safety class of vulnerability where code continues to use a reference after the underlying resource has been freed. While commonly discussed in systems languages, application-layer frameworks can exhibit analogous behavior when object lifetimes are mismanaged. In Django, this can occur when cached or session-bound representations of decoded Bearer Token payloads are reused after invalidation or after the token has been logically retired.

Bearer Tokens in Django are typically validated using libraries such as PyJWT or django-rest-framework-simplejwt. When a token is verified, its claims (e.g., user ID, scopes, roles) are decoded and often materialized into Django models or cached structures. If a token is revoked—say, via a denylist or a changed signing secret—but an in-memory object or cache entry still holds a reference to the decoded claims, the application may incorrectly treat that stale data as authoritative. This is the application-layer analogue of UAF: the token material is no longer valid, yet the code path continues to act as though it is.

Consider a scenario where a decoded token payload is stored in the request cache (e.g., per-request caching or middleware attributes). If token invalidation is handled by adding the token’s JTI (JWT ID) to a denylist but the cached payload is not cleared, an attacker who can predict or reuse an old token identifier might trigger code paths that operate on the stale payload. For example, the cached user ID from the token may be used to perform a horizontal privilege escalation (BOLA/IDOR) by accessing or modifying another user’s resources because the authorization checks were based on outdated claims.

Token replay is another concern. If a Bearer Token is intercepted and later reused, and the server-side cache or session store has not properly invalidated the corresponding user context, the reused token may be accepted as valid. This can lead to actions being executed on behalf of a user without their current consent. In frameworks that rely heavily on cached deserialization, the boundary between token validation and authorization can blur, increasing the risk of acting on stale data.

Middleware and decorators that perform authentication and permission checks must ensure that any cached token-derived objects are invalidated or re-validated on each request. Relying on a cached decoding result without confirming the token’s current validity (e.g., by consulting a denylist or checking an expiry window shorter than the token lifetime) creates conditions where stale data drives security decisions. This mirrors UAF in that a reference is used after the resource’s intended lifecycle has ended.

To detect this class of issue, scans should examine how token payloads are stored, cached, and invalidated across requests. MiddleBrick tests whether authorization checks correctly re-validate token state on each call and whether denylisted tokens are still accepted due to improper cache handling. Findings will highlight places where token-derived objects are used without confirming their current validity, providing remediation guidance to align token lifecycle with authorization checks.

Bearer Tokens-Specific Remediation in Django — concrete code fixes

Remediation focuses on ensuring token validation is performed on every request and that cached representations are tied directly to a verified, current token state. Below are concrete Django patterns that mitigate Use After Free-like issues with Bearer Tokens.

1. Stateless validation per request with PyJWT and denylist checks

import jwt
from django.conf import settings
from django.http import HttpResponseForbidden
from django.utils.deprecation import MiddlewareMixin

JWT_ALGORITHM = 'HS256'

def verify_token_not_revoked(token_jti, token_payload):
    # Replace with your cache/database denylist check
    from myapp.cache import is_jti_revoked
    return is_jti_revoked(token_jti)

class JWTBearerTokenMiddleware(MiddlewareMixin):
    def process_request(self, request):
        auth = request.META.get('HTTP_AUTHORIZATION', '')
        if auth.startswith('Bearer '):
            token = auth[7:]
            try:
                payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[JWT_ALGORITHM])
                if verify_token_not_revoked(payload.get('jti'), payload):
                    request.user = None
                    raise Exception('Token revoked')
                # Attach minimal, re-validated data to request
                request.token_payload = payload
            except jwt.ExpiredSignatureError:
                request.user = None
                raise HttpResponseForbidden('Token expired')
            except jwt.InvalidTokenError:
                request.user = None
                raise HttpResponseForbidden('Invalid token')
        else:
            request.user = None

This pattern decodes and verifies the token on every request and checks a denylist (e.g., a cache keyed by JTI) before trusting the payload. No long-lived decoded object is stored beyond the request scope.

2. Short-lived tokens with refresh rotation

Issue short-lived access tokens (e.g., 5–15 minutes) and use refresh tokens with rotation. On refresh, revoke the previous access token’s JTI. This limits the window in which a token can be reused and reduces the impact of any stale reference.

3. Avoid caching full decoded payloads for authorization

Instead of caching user roles or permissions derived from the token, fetch them on each request from the authoritative source (e.g., database or feature-flag service). If caching is necessary, tie cache keys to the token’s JTI and expiry, and invalidate on logout or denylist addition.

4. Middleware to clear token context on response

class TokenCleanupMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        # Clear any request attributes that may hold token-derived data
        if hasattr(request, 'token_payload'):
            del request.token_payload
        return response

This ensures that no residual token data leaks into downstream request processing or logging.

5. Validate scopes and claims on each sensitive operation

Even when using token-based authentication, re-check scopes and user status for critical endpoints. Do not assume that a token decoded earlier still satisfies fine-grained permissions.

By coupling strict per-request validation, denylist checks, short token lifetimes, and cleanup of request-scoped data, Django applications can avoid patterns that resemble Use After Free with Bearer Tokens.

Frequently Asked Questions

How does middleBrick detect token-related authorization issues?
middleBrick runs parallel security checks including BOLA/IDOR and Property Authorization while cross-referencing OpenAPI/Swagger specs with runtime behavior. It flags cases where authorization appears to rely on cached or stale token-derived data, providing findings with severity and remediation guidance.
Can the free plan scan Bearer Token-protected APIs?
Yes. The free plan allows 3 scans per month and supports authenticated endpoints. You can submit a URL that requires Bearer Token authentication; scans will test the unauthenticated attack surface and highlight authorization and token validation findings.