Request Smuggling in Flask
How Request Smuggling Manifests in Flask
Request smuggling in Flask applications occurs when the framework misinterprets HTTP request boundaries, allowing attackers to hide malicious payloads in requests that appear legitimate to Flask but exploit downstream services. Unlike generic HTTP request smuggling that affects reverse proxies or load balancers, Flask-specific smuggling vulnerabilities arise from how the framework parses multipart form data and handles chunked transfer encoding.
The most common Flask smuggling pattern involves multipart form data with nested boundaries. Consider this vulnerable endpoint:
from flask import Flask, request
app = Flask(__name__)
@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return 'No file part', 400
file = request.files['file']
filename = secure_filename(file.filename)
file.save(os.path.join('/uploads', filename))
return 'File uploaded successfully'
An attacker can craft a request with malformed multipart boundaries:
POST /upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 1234
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
...malicious content...
------WebKitFormBoundary7MA4YWxkTrZu0gW--
POST /admin/delete HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
id=123&confirm=true
------WebKitFormBoundary7MA4YWxkTrZu0gW--
Flask's Werkzeug parser may stop reading at the first boundary termination, leaving the second POST request unprocessed by the initial parser but delivered to the backend service. This creates a scenario where Flask processes the file upload while the backend service receives and executes the admin deletion request.
Another Flask-specific vulnerability arises from improper handling of chunked transfer encoding combined with Flask's request context. The framework's request parsing can be confused when chunk sizes are manipulated:
POST /api/data HTTP/1.1
Transfer-Encoding: chunked
Host: example.com
4
user
5
=admin
0
POST /admin HTTP/1.1
Content-Type: application/json
Content-Length: 25
{"action":"delete","id":123}
Werkzeug may process the first request's chunks but leave the second request in the connection buffer, which downstream services might interpret as a valid request.
Flask-Specific Detection
Detecting request smuggling in Flask requires both static code analysis and dynamic testing. Static analysis should focus on how your application handles multipart form data and chunked encoding. Look for patterns where request parsing occurs without proper validation of boundary markers or chunk sizes.
middleBrick's scanner specifically tests Flask applications for request smuggling by sending crafted payloads that probe for boundary confusion and chunk size manipulation. The scanner sends multiple requests with overlapping boundaries and malformed chunk sizes, then analyzes the server's responses for inconsistencies that indicate smuggling vulnerabilities.
For manual testing, use curl to send malformed multipart requests:
curl -X POST http://localhost:5000/upload \
-H "Content-Type: multipart/form-data; boundary=----test" \
-H "Content-Length: 500" \
--data-binary "@malformed_multipart.txt"
Where malformed_multipart.txt contains crafted boundary sequences designed to confuse the parser.
Monitor your application logs for unusual patterns. Request smuggling often manifests as:
- Unexpected 400 Bad Request errors that occur intermittently
- Database operations that appear to execute without corresponding API calls
- Authentication bypass attempts that succeed without valid credentials
- Race conditions where multiple requests are processed as one
middleBrick's API security scanner runs 12 security checks including input validation and data exposure analysis that can identify smuggling-related vulnerabilities. The scanner tests your Flask endpoints with boundary manipulation payloads and analyzes response patterns to detect potential smuggling.
Flask-Specific Remediation
Remediating request smuggling in Flask requires both framework-level configuration and application-level validation. Start by ensuring your Flask application runs behind a robust reverse proxy that properly handles HTTP parsing. Nginx and Apache have built-in protections against malformed requests, but you should still implement additional safeguards in your Flask code.
For multipart form data handling, validate all boundary markers before processing:
from flask import Flask, request
import re
app = Flask(__name__)
def validate_multipart_boundary(content_type):
# Extract boundary and validate it's not empty or malformed
match = re.search(r'boundary=([^;]+)', content_type)
if not match:
return False
boundary = match.group(1)
if len(boundary) < 10 or len(boundary) > 70:
return False
# Check for suspicious boundary patterns
if re.search(r'[^A-Za-z0-9\-_]', boundary):
return False
return True
@app.route('/upload', methods=['POST'])
def upload_file():
if not validate_multipart_boundary(request.content_type):
return 'Invalid request format', 400
if 'file' not in request.files:
return 'No file part', 400
file = request.files['file']
filename = secure_filename(file.filename)
file.save(os.path.join('/uploads', filename))
return 'File uploaded successfully'
For chunked transfer encoding, disable it entirely if not needed, or implement strict validation:
from flask import Flask, request
app = Flask(__name__)
@app.before_request
def validate_transfer_encoding():
if 'Transfer-Encoding' in request.headers:
encoding = request.headers['Transfer-Encoding'].lower()
if encoding != 'chunked':
return 'Unsupported transfer encoding', 400
# For chunked encoding, validate chunk sizes are reasonable
# This requires custom WSGI middleware or Werkzeug configuration
Implement request size limits and timeouts to prevent large, malformed requests from consuming resources:
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB limit
app.config['DEFAULT_REQUEST_TIMEOUT'] = 30 # 30 second timeout
For applications using Flask-RESTful or similar extensions, ensure the underlying Werkzeug parser is configured with strict validation:
from werkzeug.middleware.proxy_fix import ProxyFix
from werkzeug.exceptions import BadRequest
class StrictRequestParser(ProxyFix):
def __call__(self, environ, start_response):
try:
# Enforce strict boundary validation
content_type = environ.get('CONTENT_TYPE', '')
if 'multipart' in content_type:
if not validate_multipart_boundary(content_type):
raise BadRequest('Malformed multipart boundary')
return super().__call__(environ, start_response)
except BadRequest:
start_response('400 Bad Request', []);
return [b'Malformed request']
Finally, implement comprehensive logging and monitoring to detect smuggling attempts:
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@app.before_request
def log_request_metadata():
logger.info(f"Request: {request.method} {request.path} "
f"Content-Length: {request.content_length}")