Broken Function Level Authorization

Broken Function Level Auth in Flask (CVE-2021-33026) [CVE-2021-33026]

[Updated 2026-03] Updated CVE-2021-33026

Overview

CVE-2021-33026 describes a remote code execution risk stemming from the Flask-Caching extension (up to version 1.10.1) using Python's pickle for serialization. If an attacker gains write access to the cache backend (e.g., Redis, Memcached, or filesystem caches) they can inject a crafted pickle payload that, when deserialized during cache reads, may execute arbitrary Python code. This is a classic CWE-502 deserialization vulnerability and, while real-world exploitation is more likely when cache backends are exposed or poorly secured, it highlights how function-level access controls in Flask can be undermined by untrusted data flows through shared resources. The same risk applies whether or not endpoints are protected by Flask’s normal authorization checks, since a compromised cache can influence the behavior of protected functions by supplying deserialized objects or triggering side effects at runtime. The vulnerability is especially concerning in deployments where caching layers are network-accessible or shared among tenants or services. The patch and guidance below reference CVE-2021-33026 and emphasize the need to harden data serialization and storage boundaries in Flask apps (CWE-502).

Affected Versions

Flask-Caching <= 1.10.1 (the vulnerability exists up to version 1.10.1).

Code Fix Example

Flask API Security Remediation
# Vulnerable pattern
from flask import Flask, request
from flask_caching import Cache

app = Flask(__name__)
cache = Cache(app, config={
    'CACHE_TYPE': 'RedisCache',
    'CACHE_REDIS_URL': 'redis://localhost:6379/0',
    'CACHE_DEFAULT_TIMEOUT': 300
})

@app.route('/store')
def store():
    payload = request.args.get('payload', '')  # attacker-controlled data
    # Cache stores data using pickle-based serialization by default
    cache.set('payload', payload)
    return 'stored'

@app.route('/load')
def load():
    value = cache.get('payload')  # deserialization on access could execute crafted payload
    return 'payload=' + str(value)

# Fixed pattern
app = Flask(__name__)
cache = Cache(app, config={
    'CACHE_TYPE': 'RedisCache',
    'CACHE_REDIS_URL': 'redis://localhost:6379/0',
    'CACHE_DEFAULT_TIMEOUT': 300,
    'CACHE_SERIALIZER': 'json'  # avoid pickle-based deserialization
})

@app.route('/store_fixed')
def store_fixed():
    payload = request.args.get('payload', '')
    if not isinstance(payload, str):
        payload = str(payload)
    # Sanitize and validate input; avoid caching untrusted objects
    cache.set('payload', payload)
    return 'stored'

@app.route('/load_fixed')
def load_fixed():
    value = cache.get('payload')
    return 'payload=' + str(value)

if __name__ == '__main__':
    app.run(debug=True)

CVE References

Choose which optional cookies to allow. You can change this any time.