HIGH zip slipdjango

Zip Slip in Django

How Zip Slip Manifests in Django

Zip Slip vulnerabilities in Django applications typically occur when user-supplied archives are extracted without proper path validation. Django's file handling utilities, while robust for many scenarios, don't automatically prevent path traversal in extracted files. The vulnerability allows attackers to write files outside the intended directory by including '../' sequences in filenames within the archive.

A common Django pattern that's vulnerable looks like this:

import zipfile
from django.core.files.storage import default_storage
from django.conf import settings

def upload_view(request):
    if request.method == 'POST':
        archive = request.FILES['archive']
        path = settings.MEDIA_ROOT
        
        with zipfile.ZipFile(archive) as zip_ref:
            zip_ref.extractall(path)  # Vulnerable: no path validation
        
        return HttpResponse('Upload successful')

The critical issue is that zipfile.extractall() doesn't validate paths. An attacker can create a ZIP file containing:

../../settings.py
../../manage.py
etc/passwd

When extracted, these files overwrite critical application files or sensitive system files. In Django specifically, this could allow overwriting settings.py to inject malicious configurations, replace manage.py to execute arbitrary code, or write to locations that will be served by django.contrib.staticfiles.

Another Django-specific scenario involves media file uploads where extracted files are later served by django.views.static.serve or through MEDIA_URL. An attacker could place a malicious .py file in a location that Django will attempt to import, leading to remote code execution.

The vulnerability is particularly dangerous in Django because of its dynamic nature—Python files can contain arbitrary code that will execute when imported, and Django's auto-reloading development server will automatically reload modified files, potentially executing malicious code immediately.

Django-Specific Detection

Detecting Zip Slip vulnerabilities in Django requires examining both code patterns and runtime behavior. Code review should focus on these specific Django patterns:

import re

def is_zip_slip_vulnerable(code):
    patterns = [
        r'zipfile\.ZipFile\(.*\).extractall\(',
        r'extractall\(',
        r'ZipFile\(.*\)\.extract\(',
        r'extract\(',
        r'pathlib\.Path\(\).*\.mkdir\(parents=True\)',
        r'os\.makedirs\(.*parents=True\)'
    ]
    
    for pattern in patterns:
        if re.search(pattern, code):
            return True
    return False

Static analysis tools like Bandit have specific Django checks, but they often miss context-specific vulnerabilities. For comprehensive detection, middleBrick's API security scanner examines Django applications for Zip Slip vulnerabilities by:

  • Analyzing file extraction endpoints for path traversal patterns
  • Testing with crafted ZIP archives containing ../ sequences
  • Verifying that extracted files don't escape the intended directory
  • Checking if extracted files are later served or executed by Django

middleBrick's scanner specifically tests Django applications by submitting ZIP files with paths like ../../django_app/settings.py and ../../templates/malicious.html, then verifies if these files appear outside the intended extraction directory. The scanner also checks for Django-specific patterns like extracting to STATIC_ROOT or MEDIA_ROOT where files might be served without proper validation.

Runtime detection involves monitoring file operations and logging suspicious path patterns. Django middleware can log extraction attempts and flag when paths contain directory traversal sequences:

import logging
from django.utils.deprecation import MiddlewareMixin

logger = logging.getLogger(__name__)

class ZipSlipDetectionMiddleware(MiddlewareMixin):
    def process_request(self, request):
        if 'extract' in request.path and request.method == 'POST':
            if '../' in request.body.decode():
                logger.warning('Potential Zip Slip attempt detected')
                return HttpResponseForbidden('Invalid file path')
        return None

Django-Specific Remediation

The most effective remediation for Zip Slip in Django is to validate and sanitize all paths before extraction. Django's pathlib module provides robust path manipulation that can prevent traversal:

from pathlib import Path
import zipfile
from django.core.files.storage import default_storage
from django.conf import settings

def safe_extract(zip_path, extract_dir):
    extract_dir = Path(extract_dir).resolve()
    
    with zipfile.ZipFile(zip_path) as zip_ref:
        for member in zip_ref.namelist():
            # Check for path traversal
            if '..' in member or member.startswith('/'):
                raise ValueError(f'Illegal path in ZIP archive: {member}')
            
            # Get full path and ensure it's within the extract directory
            member_path = (extract_dir / member).resolve()
            if not member_path.is_relative_to(extract_dir):
                raise ValueError('ZIP archive path traversal attempt detected')
            
            # Create parent directories if needed
            member_path.parent.mkdir(parents=True, exist_ok=True)
            
            # Extract file
            with zip_ref.open(member) as source, open(member_path, 'wb') as target:
                target.write(source.read())

def upload_view(request):
    if request.method == 'POST':
        archive = request.FILES['archive']
        temp_path = default_storage.save('temp_upload.zip', archive)
        
        try:
            safe_extract(temp_path, settings.MEDIA_ROOT)
            default_storage.delete(temp_path)
        except ValueError as e:
            default_storage.delete(temp_path)
            return HttpResponseBadRequest(str(e))
        
        return HttpResponse('Upload successful')

This approach validates each file path before extraction, ensuring it cannot escape the intended directory. The is_relative_to() method provides robust protection against path traversal.

For Django applications using django-storages or cloud storage backends, the validation logic remains the same, but extraction targets different storage systems:

from storages.backends.s3boto3 import S3Boto3Storage
import boto3

def safe_extract_to_s3(zip_path, bucket_name, prefix=''):
    s3 = boto3.client('s3')
    
    with zipfile.ZipFile(zip_path) as zip_ref:
        for member in zip_ref.namelist():
            if '..' in member or member.startswith('/'):
                raise ValueError(f'Illegal path in ZIP archive: {member}')
            
            # Ensure prefix doesn't allow traversal
            if '../' in prefix:
                raise ValueError('Invalid prefix for S3 extraction')
            
            # Upload to S3
            key = f'{prefix.rstrip('/')}/{member.lstrip('/')}'
            with zip_ref.open(member) as file_obj:
                s3.upload_fileobj(file_obj, bucket_name, key)

Django middleware can also provide an additional layer of protection by intercepting file uploads and scanning archives before processing:

import zipfile
from io import BytesIO

class ZipSlipPreventionMiddleware(MiddlewareMixin):
    def process_request(self, request):
        if 'archive' in request.FILES:
            archive = request.FILES['archive']
            try:
                with zipfile.ZipFile(archive) as zip_ref:
                    for member in zip_ref.namelist():
                        if '..' in member or member.startswith('/'):
                            return HttpResponseForbidden('Invalid file path in archive')
            except zipfile.BadZipFile:
                return HttpResponseBadRequest('Invalid ZIP file')
        return None

Frequently Asked Questions

How does middleBrick detect Zip Slip vulnerabilities in Django applications?
middleBrick performs black-box scanning of Django API endpoints that handle file uploads. It submits crafted ZIP archives containing path traversal sequences like '../../django_app/settings.py' and verifies whether files are extracted outside the intended directory. The scanner also analyzes the application's file handling patterns and checks if extracted files are later served or executed by Django, providing a comprehensive security assessment without requiring source code access.
Can Zip Slip vulnerabilities in Django lead to remote code execution?
Yes, Zip Slip in Django can lead to remote code execution if an attacker can overwrite critical files. Python files placed in importable locations will execute when imported, and Django's auto-reloading development server will execute modified files immediately. An attacker could overwrite settings.py to inject malicious configurations, replace views.py to add malicious endpoints, or place .py files in locations that Django will import, giving them full control over the application.