Time Of Check Time Of Use in Flask
How Time Of Check Time Of Use Manifests in Flask
Time Of Check Time Of Use (TOCTOU) is a race condition vulnerability where a security check on a resource is invalidated by a subsequent change to that resource before it is used. In Flask APIs, this commonly occurs when serving files based on user input without atomic validation, particularly with send_file(). The window between validating a file path and actually reading the file allows an attacker to replace or symlink the file, bypassing access controls.
A classic example is CVE-2021-22959, a TOCTOU in Flask's send_file when used with an unsanitized user-supplied filename. Consider this vulnerable Flask route:
from flask import Flask, request, send_file
app = Flask(__name__)
@app.route('/download')
def download():
filename = request.args.get('file') # User-controlled input
# "Check": Validate filename is in allowed directory
if not filename.startswith('/safe/dir/'):
return 'Access denied', 403
# "Use": File is served after a delay (e.g., I/O, threads)
return send_file(filename)Here, the check verifies the path starts with /safe/dir/, but between this check and the actual file open operation, an attacker can replace filename with a symlink to /etc/passwd (e.g., via a concurrent request that creates the symlink). The send_file call then follows the symlink, exposing sensitive files. This is a Broken Object Level Authorization (BOLA/IDOR) failure per OWASP API Top 10 A01:2021, as the object (file) is not consistently authorized at the moment of use.
Flask's default behavior with send_file does not resolve symlinks or re-validate paths after the initial check. The vulnerability is exacerbated by:
- Concurrency: Flask's multi-threaded or multi-process handling allows simultaneous requests to manipulate filesystem state.
- User-controlled paths: Directly using
request.argsorrequest.formvalues insend_filewithout canonicalization. - Delayed I/O: The time between path validation and file stream creation provides the race window.
- Identifying an endpoint that serves files based on user input.
- Submitting a legitimate request (e.g.,
/download?file=/safe/dir/report.pdf) to pass the initial check. - Rapidly sending a second request that creates a symlink from
/safe/dir/report.pdfto/etc/passwd(using a separate file-upload or symlink-creation endpoint if available, or via other race-condition techniques). - The first request's
send_filethen reads/etc/passwdinstead.
send_file; any Flask view that performs a time-delayed operation on user-controlled resources (e.g., database queries after a permission check, temporary file processing) is susceptible if the resource state can change concurrently.Flask-Specific Detection
Detecting TOCTOU in Flask APIs requires testing for race conditions and inconsistent authorization. Since TOCTOU is a timing-dependent flaw, static code analysis alone is insufficient; dynamic black-box testing that simulates concurrent access is essential. middleBrick's BOLA/IDOR check actively probes for these issues by:
- Path Traversal Payloads: Sending requests with sequences like
../../../etc/passwdto endpoints that accept file parameters, monitoring for unauthorized file disclosure. - Concurrent Request Simulation: Issuing rapid, overlapping requests to the same endpoint with varying inputs to exploit potential race windows (though true TOCTOU may require precise timing that automated scanners can only probabilistically detect).
- Symlink Detection: Attempting to upload or create symlinks (if the API allows file operations) and then accessing them through vulnerable download endpoints.
For example, scanning a Flask endpoint GET /download?file= with middleBrick will test:
middlebrick scan https://api.example.com/download?file=../../../etc/passwdIf the response contains root:x:0:0 (from /etc/passwd), it indicates a BOLA/IDOR flaw that could be TOCTOU-related if the application performs an initial check. middleBrick's 12 parallel security checks include dedicated BOLA/IDOR testing that maps to OWASP API Top 10 A01:2021 and PCI-DSS requirements for access control. The scanner returns a per-category risk score (0–100) and prioritized findings with remediation guidance, such as "File path not canonicalized after validation" or "Symlink following enabled."
Additionally, middleBrick analyzes OpenAPI/Swagger specs to identify parameters marked as path or query that might be used in file operations. If the spec defines a /download endpoint with a file query parameter, the runtime scan will specifically target it with TOCTOU-relevant payloads. This correlation between specification and runtime behavior helps pinpoint vulnerable code paths in Flask applications.
Flask-Specific Remediation
Fixing TOCTOU in Flask requires ensuring that the resource used is identical to the one checked, without any intermediate window for alteration. The core principle is atomic validation: resolve and validate the final, canonical path immediately before use, and never trust user-controlled paths directly.
1. Use safe_join with a fixed base directory
Werkzeug's safe_join safely joins a base directory to a user-provided path, preventing directory traversal. However, it must be used correctly:
from flask import Flask, request, abort, send_file
from werkzeug.utils import safe_join
import os
app = Flask(__name__)
BASE_DIR = '/safe/dir'
@app.route('/download')
def download():
user_input = request.args.get('file', '')
# Resolve the path within BASE_DIR
safe_path = safe_join(BASE_DIR, user_input)
if safe_path is None:
abort(403) # Path traversal attempt
# Re-check that the resolved path is still within BASE_DIR
if not os.path.realpath(safe_path).startswith(os.path.realpath(BASE_DIR) + os.sep):
abort(403)
# Atomic check-and-use: verify existence and type at point of use
if not os.path.isfile(safe_path):
abort(404)
return send_file(safe_path)Key points:
safe_joinnormalizes the path and ensures it stays underBASE_DIR.os.path.realpathresolves symlinks, so an attacker cannot replace the file with a symlink outsideBASE_DIR.- The existence check (
os.path.isfile) happens immediately beforesend_file, minimizing the race window.
2. Avoid user-controlled paths entirely A more robust approach is to use resource identifiers (e.g., database IDs) instead of filenames:
from flask import Flask, send_file
app = Flask(__name__)
# Database mapping: id -> safe absolute path
FILE_MAP = {
1: '/safe/dir/report1.pdf',
2: '/safe/dir/report2.pdf'
}
@app.route('/download/')
def download(file_id):
filepath = FILE_MAP.get(file_id)
if filepath is None:
abort(404)
return send_file(filepath) This eliminates path manipulation entirely. The ID is validated against a server-controlled mapping, so no TOCTOU exists because the path is never derived from user input at request time.
3. Use Flask's after_request for final validation
In complex applications, you can add a final check in an after_request hook to re-validate the response file path (if accessible via Flask's response object). However, this is less straightforward and the previous methods are preferred.
4. Configure the server to not follow symlinks If using a production WSGI server (e.g., gunicorn), ensure it does not follow symlinks when serving static files, though this is a defense-in-depth measure. The application-level fix above is primary.
After applying these fixes, rescan with middleBrick's BOLA/IDOR check to verify that path traversal payloads no longer succeed and that the risk score improves. The remediation guidance in middleBrick reports will suggest "Implement canonical path validation with os.path.realpath" or "Use resource identifiers instead of user-controlled filenames," aligning with these Flask-specific solutions.
Understanding Risk Scores and Continuous Monitoring
middleBrick assigns a letter grade (A–F) based on a 0–100 scale, where BOLA/IDOR flaws like TOCTOU significantly impact the score. A critical TOCTOU vulnerability (e.g., unrestricted file download via symlink) would typically result in a 'D' or 'F' due to high exploitability and impact. The per-category breakdown shows the BOLA/IDOR score separately, helping prioritize fixes.
For Flask applications in active development, integrating middleBrick into CI/CD via the GitHub Action ensures that new code does not introduce TOCTOU flaws. For example, a workflow can fail a pull request if the BOLA/IDOR score drops below a threshold:
name: API Security Scan
on: [pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: middlebrick/github-action@v1
with:
api_url: ${{ secrets.API_URL }}
fail_below_score: 80This automates detection before deployment. For production Flask APIs, the Pro plan's continuous monitoring scans on a schedule (e.g., daily) and alerts via Slack if new TOCTOU issues appear, such as after a configuration change that re-enables symlink following.
Remember: middleBrick detects and reports; it does not patch. Remediation requires developer action using the guidance provided. The fixes outlined here—canonical path validation, avoiding user-controlled paths—are standard Flask security practices that directly mitigate TOCTOU by eliminating the time-of-check/time-of-use gap.
Frequently Asked Questions
What is a Time Of Check Time Of Use vulnerability in a Flask API?
send_file with a user-supplied filename after a simple startswith check, an attacker can symlink the filename to a sensitive file like /etc/passwd during the delay, causing unauthorized data exposure.How does middleBrick detect TOCTOU issues in Flask applications?
../../../etc/passwd) to endpoints that accept file parameters and monitoring for unauthorized file disclosure. It also tests for symlink vulnerabilities by attempting to access files through traversal sequences. The scanner correlates these runtime findings with OpenAPI specs to identify vulnerable parameters, then reports them with severity and remediation guidance specific to Flask's send_file patterns.