Out Of Bounds Write in Flask
How Out Of Bounds Write Manifests in Flask
Out Of Bounds Write in Flask applications typically occurs when developers trust user input to determine array indices, list positions, or buffer sizes without proper validation. Flask's dynamic nature and common patterns create several specific vulnerability points.
One of the most common manifestations is in request data handling. Flask's request.form, request.json, and request.args dictionaries accept arbitrary keys, allowing attackers to specify indices that exceed intended bounds. For example:
@app.route('/update_profile', methods=['POST'])
def update_profile():
data = request.json
# Vulnerable: no bounds checking on index
profile = ['name', 'email', 'age']
key = data.get('field_index')
profile[key] = data.get('value') # IndexError if key is out of range
return {'status': 'success'}
This code fails when field_index is negative, too large, or non-numeric. Flask doesn't automatically validate these bounds, making it the developer's responsibility.
Another Flask-specific pattern involves SQLAlchemy ORM operations where list indices are used directly:
@app.route('/update_user_role', methods=['POST'])
def update_user_role():
user_id = request.json.get('user_id')
new_role = request.json.get('role')
user = User.query.get(user_id)
# Vulnerable: assuming roles list has at least 1 element
user.roles[len(user.roles)] = new_role # IndexError if len(user.roles) == current max index
db.session.commit()
return {'status': 'success'}
The len(user.roles)] expression attempts to write to an index that may not exist, causing an IndexError that could crash the application or leak information through error responses.
File upload handling in Flask also presents OOB write opportunities. When processing multipart uploads, developers might use user-controlled values to determine buffer sizes or array positions:
@app.route('/upload', methods=['POST'])
def upload_file():
file = request.files['file']
chunk_size = int(request.form.get('chunk_size', 1024))
buffer = bytearray(chunk_size)
file.readinto(buffer) # Vulnerable if chunk_size is negative or extremely large
# Process buffer...
return {'status': 'success'}
Negative or excessively large chunk_size values can cause buffer overflows or memory exhaustion, particularly problematic in Flask's WSGI environment where resource limits may not be immediately enforced.
Flask-Specific Detection
Detecting Out Of Bounds Write vulnerabilities in Flask requires examining both the application code and runtime behavior. Static analysis can identify risky patterns, while dynamic scanning reveals actual exploitation paths.
Code review should focus on these Flask-specific patterns:
# High-risk patterns to flag:
# 1. Direct list indexing with request data
index = request.json.get('index')
my_list[index] = value
# 2. Array operations without bounds checking
items.append(request.json.get('item')) # if items has fixed size
# 3. Negative indexing vulnerabilities
items[-request.json.get('offset')] = value
# 4. Type confusion in indexing
index = request.json.get('index')
items[str(index)] = value # May cause unexpected behavior
Dynamic detection with middleBrick specifically targets Flask's common vulnerability patterns. The scanner tests for:
- Negative indices in list operations
- Excessively large indices that would cause memory issues
- Non-numeric values where numeric indices are expected
- Boundary conditions around list lengths
middleBrick's black-box scanning approach is particularly effective for Flask applications because it tests the actual runtime behavior without requiring source code access. The scanner sends crafted requests to identify OOB write conditions:
# Scan a Flask API endpoint
middlebrick scan https://your-flask-app.com/api/update
# Output includes:
# - OOB Write detection results
# - Severity assessment
# - Specific parameter names that are vulnerable
# - Recommended remediation steps
For Flask applications using SQLAlchemy, middleBrick also checks for ORM-specific OOB patterns where list operations on relationship attributes could cause issues. The scanner's 12 security checks include input validation testing that specifically targets array bounds vulnerabilities common in Python web applications.
Flask-Specific Remediation
Remediating Out Of Bounds Write vulnerabilities in Flask requires defensive programming practices and proper input validation. Flask provides several native mechanisms to prevent these issues.
The most fundamental defense is explicit bounds checking before any array operation:
@app.route('/update_profile', methods=['POST'])
def update_profile():
data = request.json
index = data.get('field_index')
value = data.get('value')
# Validate index is within bounds
if not isinstance(index, int) or index < 0 or index >= len(profile_fields):
return {'error': 'Invalid field index'}, 400
profile_fields[index] = value
return {'status': 'success'}
Flask's request validation can be enhanced with schemas using libraries like Marshmallow or Pydantic:
from pydantic import BaseModel, Field
from flask import Flask, request, jsonify
class UpdateRequest(BaseModel):
field_index: int = Field(ge=0, description="Index must be non-negative")
value: str
@app.route('/update_profile', methods=['POST'])
def update_profile():
try:
req = UpdateRequest(**request.json)
# Additional bounds check against actual data structure
if req.field_index >= len(profile_fields):
return {'error': 'Index out of bounds'}, 400
profile_fields[req.field_index] = req.value
return {'status': 'success'}
except ValueError as e:
return {'error': str(e)}, 400
For SQLAlchemy operations, use safe collection methods instead of direct indexing:
@app.route('/update_user_role', methods=['POST'])
def update_user_role():
user_id = request.json.get('user_id')
new_role = request.json.get('role')
user = User.query.get_or_404(user_id)
# Safe approach using append instead of direct indexing
if new_role not in user.roles:
user.roles.append(new_role)
db.session.commit()
return {'status': 'success'}
File upload handling should include strict size limits and validation:
@app.route('/upload', methods=['POST'])
def upload_file():
file = request.files['file']
# Enforce maximum file size (e.g., 10MB)
file.seek(0, 2)
file_length = file.tell()
file.seek(0, 0)
if file_length > 10 * 1024 * 1024:
return {'error': 'File too large'}, 400
# Process file safely without user-controlled buffer sizes
buffer = file.read()
# Process buffer...
return {'status': 'success'}
Flask's built-in error handling can also be configured to prevent information leakage from OOB errors:
@app.errorhandler(IndexError)
def handle_index_error(e):
app.logger.error(f'IndexError: {e}')
return {'error': 'Invalid operation'}, 400