HIGH symlink attackfastapi

Symlink Attack in Fastapi

How Symlink Attack Manifests in Fastapi

Symlink attacks in FastAPI applications typically occur when file operations are performed without proper validation of path traversal attempts. FastAPI developers often use Path objects from the pathlib module or os.path functions to handle file uploads and downloads, creating opportunities for attackers to exploit symbolic links.

A common vulnerability pattern emerges when FastAPI endpoints accept file paths or allow users to specify directories for file operations. Consider this problematic FastAPI endpoint:

from fastapi import FastAPI, UploadFile, File
from pathlib import Path
app = FastAPI()

@app.post("/upload/")
async def upload_file(file: UploadFile = File(...), target_dir: str = "/uploads"):
    # Vulnerable: no validation of target_dir
    target_path = Path(target_dir) / file.filename
    
    # If target_dir contains a symlink, this could write outside intended directory
    with open(target_path, 'wb') as f:
        content = await file.read()
        f.write(content)
    return {"message": "File uploaded successfully"}

The vulnerability becomes severe when combined with FastAPI's dependency injection system. An attacker could craft requests that traverse symbolic links to overwrite critical files or access sensitive data outside the intended directory structure.

Another FastAPI-specific manifestation occurs with static file serving. When using FastAPI's StaticFiles middleware:

from fastapi.staticfiles import StaticFiles
app.mount("/static", StaticFiles(directory="static"), name="static")

If the static directory contains symlinks or if the directory path is user-controllable, attackers can potentially access files outside the intended static directory.

Fastapi-Specific Detection

Detecting symlink vulnerabilities in FastAPI applications requires both static code analysis and runtime scanning. For static analysis, look for these patterns in your FastAPI codebase:

import re

# Patterns to search for in FastAPI code
VULNERABLE_PATTERNS = [
    r"Path\(.*\) /",  # Path concatenation without validation
    r"os\.path\.join.*upload",  # File path joins with user input
    r"StaticFiles\(directory=.*\)",  # Static file mounts
    r"open\(.*\, 'wb'\)",  # File writes without validation
    r"read_bytes\(\)",  # File reading without validation
    r"\$ref.*file",  # OpenAPI references to file operations
]

# Check if FastAPI app has vulnerable file operations
def scan_fastapi_app_for_symlink_vulns(app):
    vulnerabilities = []
    
    # Check for user-controllable path parameters
    for route in app.routes:
        if hasattr(route, 'endpoint'):
            # Inspect endpoint function for dangerous patterns
            source = inspect.getsource(route.endpoint)
            for pattern in VULNERABLE_PATTERNS:
                if re.search(pattern, source):
                    vulnerabilities.append({
                        'endpoint': route.path,
                        'pattern': pattern,
                        'severity': 'high'
                    })
    
    return vulnerabilities

For runtime detection, middleBrick's API security scanner specifically tests FastAPI applications for symlink vulnerabilities by:

  • Scanning for unauthenticated endpoints that accept file paths or directory parameters
  • Testing path traversal attempts through symbolic links
  • Checking static file mounts for directory traversal vulnerabilities
  • Analyzing OpenAPI specifications for risky file operation endpoints
  • Verifying proper validation of user-supplied paths

middleBrick's FastAPI-specific checks include testing for common symlink attack vectors like:

# Test cases middleBrick would run
TEST_CASES = [
    {"name": "Basic path traversal", "input": "../../etc/passwd"},
    {"name": "Symlink following", "input": "../sensitive-data"},
    {"name": "Null byte injection", "input": "shell.jsp%00.txt"},
    {"name": "Windows path traversal", "input": "..\\..\\windows\\win.ini"},
    {"name": "URL encoding", "input": "%2e%2e%2f%2e%2e%2f"},
]

Fastapi-Specific Remediation

FastAPI provides several native approaches to prevent symlink attacks. The most effective method is using FastAPI's built-in path validation and sanitization features:

from fastapi import FastAPI, UploadFile, File, HTTPException
from pathlib import Path
from fastapi.responses import JSONResponse

app = FastAPI()

# Define a secure base directory
BASE_DIR = Path(__file__).parent / "secure_uploads"
BASE_DIR.mkdir(exist_ok=True)

# Helper function to validate paths
def validate_path(user_path: str) -> Path:
    # Resolve and check if path is within base directory
    requested_path = Path(user_path).resolve()
    base_path = BASE_DIR.resolve()
    
    # Check if resolved path is within base directory
    if not str(requested_path).startswith(str(base_path)):
        raise HTTPException(status_code=400, detail="Path traversal attempt detected")
    
    # Check if path is a symlink (even if within base directory)
    if requested_path.is_symlink():
        raise HTTPException(status_code=400, detail="Symlink access not permitted")
    
    return requested_path

@app.post("/upload-secure/")
async def upload_file_secure(file: UploadFile = File(...), target_dir: str = "uploads"):
    # Validate and resolve target directory
    target_path = validate_path(target_dir) / file.filename
    
    # Ensure parent directory exists
    target_path.parent.mkdir(parents=True, exist_ok=True)
    
    # Write file securely
    contents = await file.read()
    with open(target_path, 'wb') as f:
        f.write(contents)
    
    return {"message": "File uploaded securely", "path": str(target_path)}

For static file serving, FastAPI's StaticFiles middleware can be configured securely:

from fastapi.staticfiles import StaticFiles

# Secure static file mounting with path validation
@app.on_event("startup")
def setup_secure_static_files():
    static_dirs = ["static", "public"]
    for dir_name in static_dirs:
        dir_path = Path(dir_name).resolve()
        
        # Verify directory exists and is not a symlink
        if not dir_path.exists():
            raise RuntimeError(f"Static directory {dir_name} does not exist")
        if dir_path.is_symlink():
            raise RuntimeError(f"Static directory {dir_name} is a symlink")
        
        # Mount securely
        app.mount(f"/{dir_name}", StaticFiles(directory=dir_path), name=dir_name)

Additional FastAPI-specific protections include using Pydantic models for path validation:

from pydantic import BaseModel, constr
from fastapi import Path

class FilePath(BaseModel):
    path: constr(regex=r'^[a-zA-Z0-9_/-]+\.[a-zA-Z0-9]+$')  # Allow only safe characters
    
@app.post("/download/")
def download_file(file_path: FilePath = Body(...)):
    # Pydantic validation ensures path format is safe
    safe_path = validate_path(file_path.path)
    
    if not safe_path.exists():
        raise HTTPException(status_code=404, detail="File not found")
    
    return JSONResponse(content=safe_path.read_text())

Frequently Asked Questions

How does middleBrick specifically detect symlink vulnerabilities in FastAPI applications?
middleBrick scans FastAPI endpoints for unvalidated path parameters and file operations. It tests for path traversal attempts by sending crafted requests with symlink patterns like '../../etc/passwd' and verifies if the application resolves these outside intended directories. The scanner also analyzes OpenAPI specs for endpoints accepting file paths and tests static file mounts for directory traversal vulnerabilities.
Can FastAPI's dependency injection system be exploited for symlink attacks?
Yes, if FastAPI endpoints accept path parameters through dependency injection without validation. An attacker could provide malicious paths that traverse symlinks to access or modify files outside the intended directory. Always validate and resolve paths using Path.resolve() and check they remain within your application's secure base directory.