Container Escape in Django with Bearer Tokens
Container Escape in Django with Bearer Tokens — how this specific combination creates or exposes the vulnerability
A container escape in a Django application that uses Bearer Tokens occurs when an attacker who compromises the API token or the token handling logic can break out of the container’s runtime constraints and interact with the host or other containers. This specific combination is risky because Bearer Tokens are often passed via HTTP headers, stored in environment variables, or logged in ways that expose them to processes that should not see them.
When Django runs inside a container, the process typically runs as a non-root user, but misconfigured permissions or volume mounts can allow a process with the Bearer Token to access host files or execute binaries outside the intended namespace. For example, if the token is stored in an environment variable and the container’s entrypoint script logs environment details for debugging, the token may be written to logs that are shared with the host or to a volume that is mounted read-write by other containers.
Moreover, if Django’s token validation logic makes outbound HTTP calls (for introspection or revocation checks), an SSRF vulnerability combined with a leaked Bearer Token can allow an attacker to reach the host’s metadata service (e.g., http://169.254.169.254) and retrieve credentials or configuration that enable further escape. Insecure deserialization or improper use of Django signals can also allow an attacker to leverage the token’s scope to spawn processes with elevated privileges, effectively breaking container isolation.
Another vector involves the container runtime’s API. If a Bearer Token intended for the Django app is accidentally exposed to a sidecar container that has access to the container runtime socket (e.g., Docker socket mounted as a volume), an attacker with the token could potentially create new containers or modify existing ones, leading to full host compromise. This is especially dangerous in environments where the same token is used for both application authentication and infrastructure orchestration.
To detect such risks, scanning tools like middleBrick evaluate the unauthenticated attack surface of the Django endpoint, check for exposed tokens in logs or error messages, and verify that token handling does not grant unintended access to host resources. The scan includes checks for SSRF, unsafe consumption patterns, and inventory management misconfigurations that could allow a Bearer Token to be used outside its intended scope.
Bearer Tokens-Specific Remediation in Django — concrete code fixes
Remediation focuses on ensuring Bearer Tokens are never treated as secrets for container isolation and are handled with the same care as session credentials. Below are concrete, safe patterns for Django.
1. Secure Bearer Token Storage and Access
Do not store Bearer Tokens in environment variables that are visible to all processes or logged. Use Django’s configuration system with restricted file permissions.
# settings.py — read token from a restricted file, not env
import os
def get_bearer_token():
token_path = os.getenv('BEARER_TOKEN_PATH', '/run/secrets/api_token')
if not os.path.exists(token_path):
raise ImproperlyConfigured('Bearer token file not found')
with open(token_path, 'r') as f:
return f.read().strip()
# Ensure the file is mounted with proper SELinux/AppArmor labels
# and is not world-readable.
2. Token Usage in HTTP Requests
When Django makes outbound calls that require the Bearer Token, explicitly set the Authorization header and avoid passing the token through global session state or logs.
# utils.py — safe outbound call with Bearer Token
import requests
from django.conf import settings
def call_protected_service(endpoint: str, token: str):
headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json',
}
# Enforce timeouts and avoid SSRF by validating/sanitizing endpoints
response = requests.get(endpoint, headers=headers, timeout=5, verify=True)
response.raise_for_status()
return response.json()
3. Avoid Logging or Serializing Tokens
Ensure that request/response logging middleware never writes Bearer Tokens to disk or console.
# middleware.py — redact tokens from logs
import logging
logger = logging.getLogger(__name__)
class TokenRedactionMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Redact token from any logging within the request
request.META.pop('HTTP_AUTHORIZATION', None)
# Re-add if you need to pass it downstream securely, otherwise avoid storing it
response = self.get_response(request)
return response
4. Container-Specific Hardening
- Do not mount the Docker socket inside containers that handle Bearer Tokens.
- Run the Django process as a non-root user and use read-only filesystems where possible.
- Use Kubernetes secrets or Docker secrets with proper file permissions (e.g., mode 0400) and restrict access using pod security policies.
5. Validation and Scope Limitation
Validate the audience and scope of the Bearer Token within Django to ensure it is not used beyond its intended permissions. Use libraries like oauthlib or integrate with an identity provider that supports token introspection.
# validation.py — basic scope check
from django.core.exceptions import PermissionDenied
def validate_token_scope(token_scopes: list, required_scope: str):
if required_scope not in token_scopes:
raise PermissionDenied('Insufficient scope for this operation')