Unrestricted Resource Consumption

Unrestricted Resource Consumption - Flask [CVE-2021-21241]

[Updated month year] Updated CVE-2021-21241

Overview

Unrestricted Resource Consumption (URC) in Flask arises when endpoints allow requests to trigger unbounded or excessively heavy work, consuming CPU, memory, or I/O resources and thereby degrading or taking the service offline. In real Flask APIs, this may happen if input directly drives loops, expensive computations, or large payload processing with no upper bounds, timeouts, or rate controls. When attackers can craft requests that force such workloads, a single or small set of endpoints can be driven to exhaustion under concurrent load, leading to Denial of Service (DoS) conditions or degraded performance for legitimate users. The provided CVE note (CVE-2021-21241) concerns Flask-Security-Too behavior where token exposure occurred in GET responses for certain endpoints; while not a URC vulnerability per se, it highlights the risk of unprotected endpoints leaking sensitive data, which can indirectly exacerbate resource abuse if tokens are misused to trigger additional requests. This guides us to harden endpoints, enforce rate limits, and upgrade components to patched versions, which aligns with URC mitigation in Flask applications.

Affected Versions

Flask-Security-Too: 3.3.0 <= version < 3.4.5 (i.e., 3.3.0-3.4.4); patched in 3.4.5 and 4.0.0

Code Fix Example

Flask API Security Remediation
# VULNERABLE
from flask import Flask, request, jsonify

app = Flask(__name__)

def heavy_work(n: int) -> int:
    s = 0
    for i in range(n):
        s += (i * i) % 97
    return s

@app.route('/vulnerable/process')
def vulnerable_process():
    # Unbounded workload driven by user input
    n = int(request.args.get('n', '100000'))  # attacker can choose a huge n
    result = heavy_work(n)
    return jsonify({"result": result})

# FIX
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

app.config["MAX_CONTENT_LENGTH"] = 1 * 1024 * 1024  # 1 MB payload limit
limiter = Limiter(app, key_func=get_remote_address)

@app.route('/fixed/process')
@limiter.limit("200 per hour")
def fixed_process():
    n = int(request.args.get('n', '1000'))
    if n > 10000:
        return jsonify({"error": "n too large"}), 400
    result = heavy_work(n)
    return jsonify({"result": result})

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

CVE References

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