HIGH symlink attackdjango

Symlink Attack in Django

How Symlink Attack Manifests in Django

A symlink attack in Django occurs when an attacker coerces the application into writing a file to a location under the developer’s control by leveraging symbolic link references. This is relevant when user-influenced paths (e.g., user-supplied filenames or upload destinations) are used to determine where files are stored. If the resolved path is controlled by the attacker via a symlink, the application may read or write sensitive files outside the intended directory, leading to unauthorized data access or code execution.

In Django, this commonly appears in file-handling code that uses FileSystemStorage or manual file operations with open(..., os.path.join(upload_dir, filename)). For example, consider a view that saves a user avatar using a filename derived from user input without canonicalization:

import os
from django.core.files.storage import FileSystemStorage
from django.http import HttpResponseBadRequest
from django.views import View

class AvatarUploadView(View):
    def post(self, request):
        uploaded_file = request.FILES.get('avatar')
        if not uploaded_file:
            return HttpResponseBadRequest('Missing file')
        username = request.POST.get('username')
        fs = FileSystemStorage(location='/var/app/media/avatars')
        filename = fs.get_valid_name(username + '_' + uploaded_file.name)
        fs.save(filename, uploaded_file)
        return HttpResponse('OK')

If /var/app/media/avatars is writable by the Django process and the attacker can place a symlink at a predictable path (e.g., by controlling a shared parent directory or a temporary directory), they can redirect the write to an arbitrary file such as /etc/passwd or application source code. A common pattern involves insecure temporary directories or shared upload locations where symlinks can be planted before the Django code resolves the final path.

Another scenario involves user-controlled paths in models that store file paths in the database. If a field stores a relative path and the application later uses open(os.path.join(base, stored_path)) without resolving symlinks or normalizing the path, an attacker who can update the stored path to point to a symlink can achieve unintended file access. This intersects with Django’s handling of user-uploaded content and the use of Path manipulation in custom storage backends.

Django-Specific Detection

Detecting symlink risks in Django involves combining static analysis of file-handling code with runtime scanning that observes resolved paths. Because middleBrick scans the unauthenticated attack surface and references OpenAPI/Swagger specs where available, it can surface endpoints that accept file uploads or manipulate filesystem paths without requiring credentials.

During a scan, middleBrick reports findings related to input validation and data exposure that may indicate insufficient path canonicalization. For example, if an endpoint accepts a filename parameter and uses it directly in filesystem operations, middleBrick flags this as a potential risk. The scanner cross-references the OpenAPI spec (if provided) with observed runtime behavior, helping identify endpoints where user input influences file paths.

To detect symlink issues manually in Django code, search for patterns such as:

  • FileSystemStorage with dynamic location or base_url derived from user input.
  • open() or os.path.join() where the path includes values from request.POST, request.GET, or request.FILES without using os.path.realpath or Path.resolve().
  • Custom storage classes that override get_available_name or get_valid_name without ensuring the final path is within the intended directory tree.

middleBrick’s findings include severity levels and remediation guidance, enabling teams to prioritize fixes for high-risk endpoints. The dashboard and CLI output provide per-category breakdowns so you can see which checks contributed to the score.

Django-Specific Remediation

Remediation focuses on ensuring file paths are canonicalized and confined to intended directories. Use Django’s storage abstractions together with Python’s path resolution utilities to eliminate symlink-based confusion.

First, validate and sanitize filenames using os.path.realpath or Path.resolve() before performing filesystem operations. When using FileSystemStorage, provide a deterministic get_valid_name implementation and avoid directly trusting user input for directory components:

import os
from pathlib import Path
from django.core.files.storage import FileSystemStorage
from django.http import HttpResponseBadRequest
from django.views import View

class SafeAvatarUploadView(View):
    def post(self, request):
        uploaded_file = request.FILES.get('avatar')
        if not uploaded_file:
            return HttpResponseBadRequest('Missing file')
        username = request.POST.get('username', 'unknown')
        fs = FileSystemStorage(location='/var/app/media/avatars')
        safe_name = fs.get_valid_name(username + '_' + uploaded_file.name)
        # Resolve the final path to eliminate symlinks
        upload_root = Path(fs.location).resolve()
        final_path = (upload_root / safe_name).resolve()
        # Ensure the resolved path is still within the intended directory
        if not str(final_path).startswith(str(upload_root) + os.sep):
            return HttpResponseBadRequest('Invalid path')
        with open(final_path, 'wb+') as destination:
            for chunk in uploaded_file.chunks():
                destination.write(chunk)
        return HttpResponse('OK')

Second, avoid storing user-controlled relative paths in the database. If you must store paths, store absolute paths or use a mapping (e.g., UUID-based filenames) and resolve them with Path.resolve() before opening. For storage backends that serve user-uploaded content, set base_url carefully and ensure directory traversal is not possible via manipulated URL components.

Finally, restrict filesystem permissions for the Django process so that even if a symlink is created, it cannot reach sensitive locations. Combine these practices with regular scans using tools like middleBrick to validate that endpoints handling file uploads remain resilient against symlink-based attacks.

Frequently Asked Questions

Can symlink attacks affect Django applications that use cloud storage (e.g., S3) instead of local files?
Symlink attacks primarily concern local filesystem operations. When using cloud storage backends, the risk shifts to improper access controls or injection into object keys, but the classic symlink vector is not applicable because there is no shared local filesystem.
How often should I scan my Django APIs with middleBrick to catch symlink and other vulnerabilities?
For active development, scan frequently using the CLI or GitHub Action to fail builds if risk scores degrade. The free tier allows 3 scans/month; the Pro plan supports continuous monitoring and configurable schedules for ongoing assurance.