Container Escape in Django
How Container Escape Manifests in Django
Container escape in Django applications typically occurs when attackers exploit the application's ability to interact with the underlying host system. Django's design as a full-stack web framework provides multiple attack surfaces that can lead to container escape if not properly secured.
The most common Django-specific container escape vectors include:
- Unsafe file operations: Django's file handling utilities can inadvertently expose the host filesystem. For example, using
FileSystemStoragewith user-controlled paths oropen()with..path traversal. - Command injection via Django management commands: Custom management commands that execute shell commands with user input can break out of the container.
- Subprocess execution: Django views or background tasks using
subprocesswith user input can escape containers. - Database file access: SQLite databases with paths constructed from user input can access files outside the intended directory.
- Django Debug Toolbar exposure: When left enabled in production, it can expose sensitive system information and file access.
A concrete example of container escape in Django might look like this vulnerable view:
from django.http import JsonResponse
import subprocess
def escape_attempt(request):
cmd = request.GET.get('cmd', 'ls')
# Vulnerable: no input validation, allows container escape
result = subprocess.run(cmd, shell=True, capture_output=True)
return JsonResponse({'output': result.stdout.decode()})
An attacker could exploit this by sending ?cmd=cat /etc/shadow or ?cmd=ls / to traverse the host filesystem. In a containerized environment, this could reveal sensitive host files or even allow mounting host volumes.
Another Django-specific pattern involves file uploads. Consider this vulnerable code:
from django.core.files.storage import FileSystemStorage
def upload_file(request):
if request.method == 'POST':
uploaded_file = request.FILES['file']
# Vulnerable: no path validation
fs = FileSystemStorage(location='/var/www/uploads')
filename = fs.save(uploaded_file.name, uploaded_file)
return JsonResponse({'filename': filename})
An attacker could upload a file with a path like ../../etc/passwd, causing the file to be written outside the intended directory, potentially exposing or modifying host system files.
Django-Specific Detection
Detecting container escape vulnerabilities in Django applications requires both static code analysis and dynamic runtime scanning. middleBrick provides comprehensive detection capabilities specifically designed for Django applications.
middleBrick's Django-specific detection includes:
- Static analysis of Django views: Scanning for dangerous patterns like
subprocess.run(),os.system(), and unsafe file operations in view functions. - Management command analysis: Identifying custom management commands that execute shell commands or access the filesystem unsafely.
- Template injection detection: Finding templates that might allow code execution or filesystem access.
- Model field validation: Checking for FileField and ImageField configurations that might allow path traversal.
- Middleware inspection: Analyzing custom middleware for unsafe operations.
middleBrick's scanning process for Django applications includes:
# Using middleBrick CLI to scan a Django API
middlebrick scan https://your-django-app.com/api/
The scanner tests for container escape by attempting controlled path traversal, command injection, and file access patterns specific to Django's architecture. It checks for:
- Path traversal attempts: Testing
../sequences in file-related endpoints. - Command injection probes: Sending payloads like
; cat /etc/passwdto detect unsafe subprocess usage. - SSRF detection: Checking if the Django app can make outbound requests that might access internal services.
- Debug mode detection: Identifying if Django Debug Toolbar or DEBUG=True is enabled in production.
middleBrick also analyzes your Django application's OpenAPI/Swagger spec to understand the API surface and correlate findings with the documented endpoints. This helps identify discrepancies between intended and actual behavior.
For CI/CD integration, you can add middleBrick to your Django deployment pipeline:
# GitHub Action for Django API security
- name: Run middleBrick Security Scan
uses: middlebrick/middlebrick-action@v1
with:
url: https://staging.yourdjangoapp.com/api/
fail-on-severity: high
This ensures container escape vulnerabilities are caught before deployment to production environments.
Django-Specific Remediation
Remediating container escape vulnerabilities in Django requires a defense-in-depth approach using Django's built-in security features and Python best practices.
1. Input Validation and Sanitization
Always validate and sanitize user input before using it in file operations or system commands:
import re
from django.http import JsonResponse
from django.core.exceptions import ValidationError
def validate_safe_filename(filename):
# Only allow alphanumeric, hyphens, underscores, and dots
if not re.match(r'^[\w,\-,\.]+$', filename):
raise ValidationError('Invalid filename')
return filename
def safe_file_operation(request):
filename = request.GET.get('filename', 'default.txt')
try:
safe_name = validate_safe_filename(filename)
except ValidationError:
return JsonResponse({'error': 'Invalid filename'}, status=400)
# Safe: validated filename
with open(f'/safe/directory/{safe_name}', 'r') as f:
content = f.read()
return JsonResponse({'content': content})
2. Safe File Handling with Django Storage
Use Django's storage backends with proper configuration:
from django.core.files.storage import FileSystemStorage
from django.conf import settings
def secure_upload(request):
if request.method == 'POST':
uploaded_file = request.FILES['file']
# Use a dedicated, non-writable directory
fs = FileSystemStorage(location=settings.MEDIA_ROOT)
# Generate safe filename
safe_name = re.sub(r'[^\w\.\-]', '_', uploaded_file.name)
# Save with safe name
filename = fs.save(safe_name, uploaded_file)
return JsonResponse({'filename': filename})
3. Secure Management Commands
Always validate arguments in custom management commands:
from django.core.management.base import BaseCommand
import subprocess
class Command(BaseCommand):
help = 'Safe file listing command'
def add_arguments(self, parser):
parser.add_argument('directory', type=str, help='Directory to list')
def handle(self, *args, **options):
directory = options['directory']
# Validate directory is within allowed paths
allowed_base = '/var/www/myapp'
if not directory.startswith(allowed_base):
self.stderr.write('Directory not allowed')
return
try:
# Use safe method instead of shell=True
result = subprocess.run(['ls', '-la', directory],
capture_output=True, text=True)
self.stdout.write(result.stdout)
except Exception as e:
self.stderr.write(f'Error: {e}')
4. Django Security Middleware
Implement security middleware to prevent path traversal:
from django.http import HttpResponseForbidden
import os
class PathTraversalMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Check for path traversal in GET parameters
for key, value in request.GET.items():
if '..' in str(value) or '/' in str(value):
return HttpResponseForbidden('Path traversal detected')
response = self.get_response(request)
return response
5. Container Runtime Security
While middleBrick detects vulnerabilities, you should also implement runtime protections:
# docker-compose.yml with security context
django:
image: yourdjangoapp:latest
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp
volumes:
- ./media:/var/www/media:ro # read-only where possible
user: "1000:1000" # non-root user
6. Django Settings Security
Configure Django settings to minimize attack surface:
# settings.py
import os
# Disable debug in production
DEBUG = os.getenv('DJANGO_DEBUG', 'False') == 'True'
# Set allowed hosts
ALLOWED_HOSTS = os.getenv('DJANGO_ALLOWED_HOSTS', 'localhost,127.0.0.1').split(',')
# Secure file uploads
MEDIA_ROOT = '/var/www/media'
MEDIA_URL = '/media/'
# Disable debug toolbar in production
if not DEBUG:
MIDDLEWARE = [m for m in MIDDLEWARE if 'debug_toolbar' not in m]
INSTALLED_APPS = [app for app in INSTALLED_APPS if app != 'debug_toolbar']
By combining these Django-specific remediation techniques with middleBrick's detection capabilities, you can effectively prevent container escape vulnerabilities in your Django applications.