Container Escape in Flask
How Container Escape Manifests in Flask
Container escape vulnerabilities in Flask applications often arise from misconfigurations that allow attackers to access the host filesystem or execute arbitrary code. The most common patterns involve file upload endpoints, template injection, and improper handling of user-supplied paths.
A classic Flask container escape scenario occurs when an application accepts file uploads without proper validation. Consider this vulnerable pattern:
from flask import Flask, request
app = Flask(__name__)
@app.route('/upload', methods=['POST'])
def upload():
file = request.files['file']
file.save('/tmp/' + file.filename) # Path traversal possible
return 'Uploaded!'
An attacker can upload a malicious file with a path like ../../etc/passwd, escaping the intended directory. More sophisticated attacks involve uploading a Python file and then using Flask's import system to execute it:
# Attacker uploads evil.py
from flask import Flask
app = Flask(__name__)
@app.route('/evil')
def evil():
import os
os.system('cat /etc/shadow') # Host data exposure
return 'Pwned!'
Template injection represents another vector. Flask's Jinja2 templates can execute arbitrary Python if improperly configured:
from flask import Flask, render_template_string
app = Flask(__name__)
@app.route('/render')
def render():
user_input = request.args.get('template')
return render_template_string(user_input) # RCE possible
An attacker could inject {{ config.items() }} to dump configuration or use {% for %} loops with subprocess calls to execute commands on the host.
Flask's debug mode presents a particularly severe container escape risk. When debug=True, Flask enables the Werkzeug debugger, which includes a full interactive console:
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0') # DANGEROUS in production
This console provides direct shell access to the container environment, allowing file system traversal, process inspection, and network access from within the compromised container.
Flask-Specific Detection
Detecting container escape vulnerabilities in Flask requires both static analysis of the codebase and dynamic runtime scanning. middleBrick's black-box scanning approach is particularly effective for Flask applications because it tests the actual running endpoints without requiring source code access.
For file upload vulnerabilities, middleBrick tests for path traversal by attempting to upload files with directory traversal sequences like ../../../ and special filenames. The scanner checks whether the application properly sanitizes filenames and restricts upload directories. It also tests for dangerous file types by attempting to upload Python files, shell scripts, and other executable content.
Template injection detection involves sending payloads that attempt to execute template expressions. middleBrick uses a comprehensive test suite that includes:
- Basic expression injection:
{{ 7 * 7 }} - Configuration dumping:
{{ config.items() }} - Object introspection:
{{ [].__class__.__base__.__subclasses__() }} - Subprocess execution attempts
For debug mode detection, middleBrick identifies Flask applications running with debugging enabled by checking for characteristic error pages, the Werkzeug debugger interface, and specific HTTP headers that indicate debug mode is active.
The scanner also tests for unsafe consumption patterns where Flask applications might execute code from external sources. This includes checking for:
# Dangerous patterns middleBrick detects:
import importlib.util
# Dynamic module loading
spec = importlib.util.spec_from_file_location('module.name', '/tmp/evil.py')
module = importlib.util.module_from_spec(spec)
importlib.util.module_from_spec(spec)
middleBrick's API security scanning specifically checks Flask's common misconfigurations: app.run(host='0.0.0.0') without proper authentication, exposed development servers, and applications running as root within containers.
The tool generates a security score with detailed findings, showing exactly which endpoints are vulnerable and providing specific remediation guidance. For Flask applications, this includes recommendations for secure file handling, template rendering practices, and proper container configuration.
Flask-Specific Remediation
Securing Flask applications against container escape requires defense-in-depth strategies. Start with proper file upload handling:
import os
from flask import Flask, request
from werkzeug.utils import secure_filename
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = '/var/app/uploads'
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB limit
# Allow only safe extensions
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return 'No file part', 400
file = request.files['file']
if file.filename == '':
return 'No selected file', 400
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return 'File uploaded successfully'
return 'File type not allowed', 400
For template security, always use template files rather than rendering user input, and enable sandbox mode:
from flask import Flask, render_template
app = Flask(__name__)
# Safe pattern - use template files, never render user input directly
@app.route('/profile/')
def profile(username):
user = get_user_from_db(username)
return render_template('profile.html', user=user)
# If you must render templates from strings, use a sandbox
from jinja2.sandbox import SandboxedEnvironment
safe_env = SandboxedEnvironment()
def safe_render(template_str, context):
return safe_env.from_string(template_str).render(context)
Debug mode must be disabled in production. Use environment-based configuration:
import os
from flask import Flask
app = Flask(__name__)
# Never run with debug=True in production
app.config['DEBUG'] = os.environ.get('FLASK_DEBUG', 'false').lower() == 'true'
if __name__ == '__main__':
# Only bind to localhost in production
host = '127.0.0.1' if app.config['DEBUG'] else '0.0.0.0'
app.run(host=host, port=5000)
Container security best practices for Flask include running as a non-root user, using read-only filesystems where possible, and implementing proper resource limits:
Finally, implement proper logging and monitoring to detect container escape attempts. Log file upload events, template rendering, and any subprocess executions. Use Flask's built-in logging with appropriate log levels and consider integrating with centralized logging solutions.