Container Escape in Flask with Bearer Tokens
Container Escape in Flask with Bearer Tokens — how this specific combination creates or exposes the vulnerability
A container escape in a Flask application that uses Bearer tokens occurs when a compromised or misconfigured API endpoint allows an attacker to move from the application’s runtime or network namespace into the host or other containers. This risk is heightened when Bearer tokens are handled with weak validation, broad routing, or excessive permissions, enabling an authenticated context to be abused for lateral movement or host-level access.
Consider a Flask route that accepts a Bearer token but does not enforce strict scope or binding checks. An attacker who obtains a low-privilege token might exploit overly permissive CORS or host header handling to reach internal metadata services. For example, a route like /api/proxy that forwards requests based on a token-derived tenant ID can be abused to redirect traffic to http://169.254.169.254 (the AWS instance metadata service) if the container host network is reachable. This is a common path in SSRF-related container escapes, where an authenticated API call becomes a tunnel to the host.
Insecure deserialization or unsafe consumption of user-supplied data can compound the issue. If a Flask endpoint uses pickle or YAML loading on token-associated payloads, an attacker may chain token possession with code execution inside the container. From there, if the container has mounted sensitive host paths or runs with elevated capabilities, the attacker can break out to the host filesystem or probe other containers via mounted Docker sockets (/var/run/docker.sock). This illustrates how authentication via Bearer tokens does not equate to authorization at the infrastructure boundary.
OpenAPI specifications that do not tightly constrain parameters or security schemes can also contribute. When path parameters or headers are not validated against the expected token context, an attacker can leverage malformed requests to bypass intended tenant isolation. The interplay between an unauthenticated attack surface (used by middleBrick to detect such misconfigurations) and weak token binding increases the likelihood of container escape through the API layer.
Additionally, missing network segmentation within container orchestration can expose Flask services to host networking. If a Bearer token is accepted without verifying the request’s origin or enforcing mTLS where appropriate, an attacker who reaches the API port can attempt to interact with the Docker runtime API. Tools like middleBrick’s scans can highlight these authentication and network exposure findings, providing remediation guidance to reduce the container escape risk surface.
Bearer Tokens-Specific Remediation in Flask — concrete code fixes
To remediate container escape risks tied to Bearer tokens in Flask, enforce strict token validation, isolate network paths, and avoid over-privileged container runtimes. Below are concrete code examples that demonstrate secure handling.
1. Validate token scope and binding
Do not accept a Bearer token without verifying its scope and associating it to a specific tenant or namespace. Use a library like authlib to parse and validate tokens, and enforce that the token’s claims match the expected route prefix.
from flask import Flask, request, jsonify
from authlib.integrations.flask_client import OAuth
app = Flask(__name__)
app.config['OAUTH2_REFRESH_TOKEN_GENERATOR'] = None
# Example in-memory token introspection simulation
def verify_token(token: str):
# In production, call your auth server’s introspection endpoint
if token == 'valid-tenant-a-token':
return {'scope': 'api:tenant_a', 'tenant': 'tenant_a'}
return None
@app.route('/api/tenant_a/resource')
def tenant_a_resource():
auth = request.headers.get('Authorization', '')
if not auth.startswith('Bearer '):
return jsonify({'error': 'missing_token'}), 401
token = auth.split(' ')[1]
claims = verify_token(token)
if not claims or claims.get('tenant') != 'tenant_a':
return jsonify({'error': 'invalid_scope'}), 403
return jsonify({'data': 'secure-tenant-a'})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
2. Restrict host and path usage to prevent SSRF-based escapes
Ensure that any URL construction or proxy logic derived from token context does not allow arbitrary host resolution. Validate hosts against a denylist (e.g., metadata IPs) and use an allowlist for internal services.
import re
from flask import abort
METADATA_IPS = {'169.254.169.254'}
def safe_forward(tenant_id: str, target_url: str):
if any(bad in target_url for bad in METADATA_IPS):
abort(400, 'forbidden_host')
# Continue with controlled outbound call
return f'Forwarded for {tenant_id} to {target_url}'
@app.route('/api/forward')
def forward():
token = request.headers.get('Authorization', '').replace('Bearer ', '')
claims = verify_token(token)
if not claims:
abort(401)
target = request.args.get('url', '')
result = safe_forward(claims['tenant'], target)
return jsonify({'forward': result})
3. Run Flask in a least-privilege container
While this is an infrastructure concern, it is essential: run the container as a non-root user, drop capabilities, and avoid mounting the Docker socket. In your Dockerfile and compose setup, restrict what the Flask process can do if a token is compromised.
# Dockerfile example
FROM python:3.11-slim
RUN adduser --disabled-password flaskuser
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY app.py .
USER flaskuser
EXPOSE 5000
CMD ["python", "app.py"]
4. Tighten OpenAPI spec and security scheme definitions
Ensure your OpenAPI definition explicitly scopes Bearer security schemes and does not use overly broad parameters. This helps both developers and automated scanners like middleBrick to validate expected behavior.
openapi: 3.0.3
info:
title: Tenant API
version: 1.0.0
paths:
/api/tenant_a/resource:
get:
summary: Tenant A resource
security:
- bearerAuth:
- tenant_a:read
responses:
'200':
description: OK
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT