HIGH symlink attackflask

Symlink Attack in Flask

How Symlink Attack Manifests in Flask

Symlink attacks in Flask applications typically exploit how Flask handles file uploads and static file serving. When an attacker can control filenames or upload paths, they can create symbolic links that point to sensitive files outside the intended upload directory.

The most common Flask-specific scenario involves the send_from_directory function. Consider this vulnerable pattern:

from flask import Flask, request, send_from_directory

app = Flask(__name__)
UPLOAD_FOLDER = '/var/www/uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

@app.route('/upload', methods=['POST'])
def upload_file():
    file = request.files['file']
    filename = file.filename
    file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
    return 'File uploaded successfully'

@app.route('/download/<filename>')
def download_file(filename):
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)

An attacker could upload a file named ../../../../etc/passwd or create a symlink to /etc/passwd within the upload directory. When another user requests this file through the download endpoint, the symlink attack allows reading sensitive system files.

Another Flask-specific manifestation occurs with blueprint routing. If a blueprint mounts at /api and uses relative paths for file operations, an attacker might exploit path traversal combined with symlinks:

@app.route('/api/<path:filepath>')
def serve_file(filepath):
    return send_from_directory('static', filepath)

Here, filepath could contain ../ sequences that, when combined with symlinks in the static directory, bypass intended access controls.

Flask's development server also has unique symlink-related vulnerabilities. The server's automatic reloading can be triggered by symlink changes, potentially causing denial of service or information disclosure if an attacker can manipulate files in watched directories.

Flask-Specific Detection

Detecting symlink attacks in Flask requires both static code analysis and runtime monitoring. For static analysis, look for these Flask-specific patterns:

import ast
import re

def find_symlink_vulnerabilities(code):
    tree = ast.parse(code)
    issues = []
    
    # Check for send_from_directory usage with user input
    for node in ast.walk(tree):
        if isinstance(node, ast.Call):
            if (isinstance(node.func, ast.Attribute) and 
                node.func.attr == 'send_from_directory'):
                # Check if directory argument is user-controlled
                if (isinstance(node.args[0], ast.Name) or
                    (isinstance(node.args[0], ast.BinOp) and 
                     isinstance(node.args[0].left, ast.Name))):
                    issues.append({
                        'type': 'send_from_directory',
                        'line': node.lineno,
                        'description': 'Potential symlink attack via send_from_directory'
                    })
    
    # Check for path.join with user input
    for node in ast.walk(tree):
        if isinstance(node, ast.Call):
            if (isinstance(node.func, ast.Attribute) and 
                node.func.attr == 'join' and 
                isinstance(node.func.value, ast.Name) and 
                node.func.value.id == 'os'):
                if isinstance(node.args[1], ast.BinOp):
                    issues.append({
                        'type': 'path_join',
                        'line': node.lineno,
                        'description': 'Path join with user input may allow symlink attacks'
                    })
    
    return issues

Runtime detection requires monitoring file operations. Flask middleware can intercept file reads:

from flask import Flask, request, g
import os

def symlink_protection_middleware(app):
    def middleware(environ, start_response):
        # Check if requested file is a symlink
        path = environ.get('PATH_INFO', '')
        if path.startswith('/download/'):
            filename = path.split('/')[-1]
            full_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
            
            if os.path.islink(full_path):
                # Log and block symlink access
                app.logger.warning(f'Symlink attack attempt: {full_path}')
                response = 'Forbidden: Symlink access detected'
                return Response(response, status=403, mimetype='text/plain')(environ, start_response)
        
        return app(environ, start_response)
    return middleware

app.wsgi_app = symlink_protection_middleware(app.wsgi_app)

For comprehensive detection, use automated scanning tools. middleBrick's black-box scanner specifically tests for symlink vulnerabilities by attempting controlled symlink creation and access attempts during its 5-15 second scan:

# Using middleBrick CLI to scan for symlink vulnerabilities
npm install -g middlebrick
middlebrick scan https://your-flask-app.com

The scanner tests common symlink attack patterns including path traversal attempts combined with symlink exploitation, providing a security score and specific findings about symlink-related vulnerabilities.

Flask-Specific Remediation

Remediating symlink attacks in Flask requires defense-in-depth. Start with proper path validation before any file operation:

import os
from pathlib import Path
from flask import abort

def safe_path_join(base_dir, user_path):
    # Resolve the user path and check if it stays within base_dir
    try:
        # Use pathlib for secure path resolution
        base = Path(base_dir).resolve()
        target = (base / user_path).resolve()
        
        # Ensure target is within base directory
        if base in target.parents or target == base:
            return str(target)
        else:
            raise ValueError('Path traversal detected')
    except (ValueError, RuntimeError) as e:
        raise ValueError(f'Invalid path: {e}')

@app.route('/download/<path:filename>')
def download_file(filename):
    try:
        safe_path = safe_path_join(app.config['UPLOAD_FOLDER'], filename)
        return send_from_directory(app.config['UPLOAD_FOLDER'], safe_path)
    except ValueError:
        abort(403)

For file uploads, sanitize filenames and prevent symlink creation:

import re
from werkzeug.utils import secure_filename

def secure_upload(file):
    # Get original filename
    original_filename = secure_filename(file.filename)
    
    # Generate a safe, unique filename
    import uuid
    safe_filename = f'{uuid.uuid4().hex}_{original_filename}'
    
    # Save file with safe name
    file.save(os.path.join(app.config['UPLOAD_FOLDER'], safe_filename))
    
    return safe_filename

@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        return 'No file part', 400
    
    file = request.files['file']
    if file.filename == '':
        return 'No selected file', 400
    
    # Process upload with symlink protection
    try:
        safe_filename = secure_upload(file)
        return f'File uploaded as {safe_filename}', 201
    except Exception as e:
        app.logger.error(f'Upload error: {e}')
        return 'Upload failed', 500

Implement runtime symlink detection in your Flask application:

from flask import after_this_request

def check_for_symlinks(directory):
    for root, dirs, files in os.walk(directory):
        for name in dirs + files:
            path = os.path.join(root, name)
            if os.path.islink(path):
                # Log and optionally remove malicious symlinks
                app.logger.warning(f'Found symlink: {path}')
                # os.remove(path)  # Uncomment to auto-remove

@app.before_request
def security_check():
    # Check upload directory for symlinks before processing requests
    if request.endpoint == 'download_file':
        check_for_symlinks(app.config['UPLOAD_FOLDER'])

For production deployments, use containerization with read-only filesystems where possible, and implement proper file system permissions to prevent symlink creation in upload directories.

Frequently Asked Questions

How can I test if my Flask app is vulnerable to symlink attacks?
Create a test file in your upload directory, then create a symlink to it with a path traversal name like ../../../../test.txt. Try accessing it through your download endpoint. If you can read files outside the intended directory, you're vulnerable. Use middleBrick's automated scanning to test this systematically without manual exploitation.
Does Flask's development server have any built-in protection against symlink attacks?
No, Flask's development server does not include symlink attack protection. It will follow symlinks when serving files, making it vulnerable to these attacks in development. Always use the remediation techniques shown above, and never rely on the development server for production deployments where symlink attacks are a concern.