HIGH use after freeflaskbearer tokens

Use After Free in Flask with Bearer Tokens

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

Use After Free (UAF) in a Flask API that uses Bearer Tokens occurs when token-related resources are released or invalidated on the server but references to that memory persist in logic or data structures, and later requests reuse those stale references. Although Python’s runtime includes automatic garbage collection, UAF-like conditions can still manifest through patterns such as caching token state in mutable globals, storing decoded payloads in request-bound structures that are reused across invocations, or failing to fully clear token material after logout or revocation.

When Bearer Tokens are involved, the typical flow is: client sends an Authorization header, Flask decodes or validates the token (often via a library such as PyJWT), attaches claims to g or another request-scoped holder, and uses those claims for authorization. If the implementation caches decoded token data in a global dictionary keyed by token identifier (e.g., jti) and does not reliably remove entries on logout or token expiry, a later request with a different token that reuses the same identifier can observe the previous token’s claims.

In a black-box scan, middleBrick tests unauthenticated attack surface paths and checks for inconsistent authorization boundaries. It can detect scenarios where authorization checks rely on stale token metadata, or where token revocation is not reflected in runtime decisions. For example, if an endpoint reads g.user_permissions that were populated from an earlier token, and the token has since been revoked, the API may erroneously allow an action that should be denied. This maps to authorization flaws such as BOLA/IDOR when the confused identity is derived from token handling rather than direct resource ownership.

Consider a Flask route that caches decoded payloads in a module-level dictionary without cleanup:

from flask import Flask, request, g
import jwt

app = Flask(__name__)
CACHE = {}

@app.before_request
def load_token_state():
    auth = request.headers.get('Authorization', '')
    if auth.startswith('Bearer '):
        token = auth.split(' ')[1]
        # Decode without verifying signature in a misconfigured example
        payload = jwt.decode(token, options={'verify_signature': False})
        # Store by jti — if not cleaned on logout, UAF-like reuse can occur
        if 'jti' in payload:
            if payload['jti'] not in CACHE:
                CACHE[payload['jti']] = payload
        g.token_payload = CACHE.get(payload['jti'])
    else:
        g.token_payload = None

@app.route('/admin')
def admin():
    if g.token_payload and g.token_payload.get('role') == 'admin':
        return 'admin access granted'
    return 'forbidden', 403

If the application later removes entries from CACHE inconsistently (e.g., on logout for one token family but not another), or if the jti space is small and collisions occur, a request with a different token can obtain the cached claims of a previously freed token. middleBrick’s checks for Property Authorization and BOLA/IDOR would flag such routes when authorization depends on mutable global state rather than current request validation.

Another scenario involves token introspection endpoints storing transient state that is not properly cleared. Suppose Flask caches introspection results to avoid repeated validation, and the cache eviction policy is weak or keyed only by token string. If a token is rotated or revoked, stale entries may be served, causing authorization decisions based on outdated permissions — a UAF-like condition in the logic layer.

These issues are not about memory safety in the C sense, but about logical reuse of authorization state. middleBrick’s unauthenticated scan surface testing can surface these by checking whether endpoints behave differently when tokens are revoked, expired, or replaced while cached references remain. Remediation focuses on ensuring token-bound data is tied strictly to the request lifecycle and is purged on logout, expiry, or revocation, and by avoiding global caches for authorization state.

Bearer Tokens-Specific Remediation in Flask — concrete code fixes

Remediation centers on strict request-scoped validation, avoiding persistent caches for authorization claims, and ensuring token revocation is respected before any authorization decision. The following patterns demonstrate secure handling of Bearer Tokens in Flask.

1. Validate on each request and avoid global caching

Decode and verify the token on every request, and do not store decoded payloads in mutable globals. Use flask.g only for the current request:

from flask import Flask, request, g
import jwt
from jwt.exceptions import InvalidTokenError

app = Flask(__name__)
SECRET_KEY = 'your-secure-key'

def verify_token(token: str) -> dict:
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
        return payload
    except InvalidTokenError:
        return None

@app.before_request
def load_token_state():
    auth = request.headers.get('Authorization', '')
    if auth.startswith('Bearer '):
        token = auth.split(' ')[1]
        payload = verify_token(token)
        g.token_payload = payload
    else:
        g.token_payload = None

@app.route('/resource')
def access_resource():
    if not g.token_payload:
        return 'unauthorized', 401
    # Use claims directly from g.token_payload, no stale cache
    return 'ok', 200

2. Enforce revocation on logout

If you maintain a denylist (e.g., for logged-out tokens until expiry), store only token identifiers with an expiration aligned to the token TTL, and clean them deterministically. A simple in-memory set with TTL can be implemented via a cache library, but ensure it is request-bound for reads and safely invalidated on logout:

from flask import Flask, request, g
import jwt
import time
from collections import OrderedDict

app = Flask(__name__)
SECRET_KEY = 'your-secure-key'

# Simple ordered denylist with TTL housekeeping
DENYLIST = OrderedDict()
DENYLIST_TTL = 3600  # seconds, should match token expiry policy

def add_to_denylist(jti: str, exp: int):
    DENYLIST[jti] = exp
    # Trim oldest entries to bound size
    while len(DENYLIST) > 10_000:
        DENYLIST.popitem(last=False)
    # Expire old entries
    now = time.time()
    expired = [k for k, v in DENYLIST.items() if v <= now]
    for k in expired:
        DENYLIST.pop(k, None)

def is_denied(jti: str) -> bool:
    return jti in DENYLIST

@app.route('/logout', methods=['POST'])
def logout():
    auth = request.headers.get('Authorization', '')
    if auth.startswith('Bearer '):
        token = auth.split(' ')[1]
        payload = verify_token(token)
        if payload and 'jti' in payload:
            # Store with expiry to bound memory
            add_to_denylist(payload['jti'], payload.get('exp', int(time.time()) + 3600))
    return 'logged out', 200

def verify_token_with_denylist(token: str) -> dict:
    payload = verify_token(token)
    if payload and 'jti' in payload and is_denied(payload['jti']):
        return None
    return payload

@app.before_request
def load_token_state_safe():
    auth = request.headers.get('Authorization', '')
    if auth.startswith('Bearer '):
        token = auth.split(' ')[1]
        g.token_payload = verify_token_with_denylist(token)
    else:
        g.token_payload = None

3. Use robust libraries and avoid custom crypto

Always use maintained libraries for decoding and verification. Do not implement your own signature verification or manipulate token headers manually. Configure audience and issuer validation to prevent token confusion across services.

These patterns ensure token state is not reused across requests and that revocation is respected. middleBrick’s GitHub Action can be added to CI/CD pipelines to fail builds if risk scores exceed your threshold, while the Web Dashboard and MCP Server help track and investigate findings interactively.

Frequently Asked Questions

Can Use After Free in Flask with Bearer Tokens lead to privilege escalation?
Yes. If authorization decisions rely on stale token claims cached in global state, an attacker may leverage a freed token’s permissions to access resources intended for a different identity, effectively escalating privileges.
How does middleBrick detect token-related authorization inconsistencies?
middleBrick runs unauthenticated checks across the API surface, validating that authorization does not depend on mutable or cached token state. It flags routes where claims from previous tokens could be reused, indicating potential Use After Free or BOLA/IDOR risks.