HIGH container escapeflask

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:

# Dockerfile example FROM python:3.9-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . RUN addgroup --system appgroup && adduser --system --group appgroup appuser USER appuser EXPOSE 5000 CMD ["python", "app.py"]

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.

Frequently Asked Questions

How can I test my Flask application for container escape vulnerabilities?
Use middleBrick's self-service scanner by submitting your Flask application's URL. The scanner tests for path traversal in file uploads, template injection vulnerabilities, debug mode exposure, and unsafe consumption patterns. It provides a security score (A-F) with specific findings and remediation guidance tailored to Flask applications.
What's the difference between a container escape and a regular Flask vulnerability?
A container escape specifically allows an attacker to break out of the container's isolation and access the host system, while a regular Flask vulnerability might only compromise the application itself. Container escapes are more severe because they can lead to host compromise, data exfiltration from other containers, and lateral movement within your infrastructure.