Zip Slip in Fastapi
How Zip Slip Manifests in Fastapi
Zip Slip is a directory traversal vulnerability that occurs when an application extracts files from an archive without properly validating the file paths. In Fastapi applications, this typically manifests when handling file uploads or processing archives sent through API endpoints.
The vulnerability exploits path traversal sequences like ../../ in filenames within zip archives. When an attacker crafts a malicious zip file with paths like ../../etc/passwd or ../../../windows/system32/calc.exe, the extraction process can write files outside the intended directory, potentially overwriting system files or accessing sensitive areas of the filesystem.
In Fastapi, Zip Slip commonly appears in these scenarios:
- File upload endpoints that accept zip archives for processing
- API endpoints that extract template files or assets
- Background tasks that process uploaded archives
- Endpoints that handle backup/restore functionality
Here's a vulnerable Fastapi endpoint that demonstrates the issue:
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import JSONResponse
import zipfile
import os
app = FastAPI()
@app.post("/upload-archive/")
async def upload_archive(file: UploadFile = File(...)):
# Vulnerable: no path validation
extract_path = "/var/www/uploads"
with zipfile.ZipFile(file.file, 'r') as zip_ref:
zip_ref.extractall(extract_path)
return JSONResponse(content={"message": "Archive processed"})
An attacker could upload a zip containing:
../../../../../etc/passwd
../../../../../etc/shadow
../../../windows/system32/calc.exe
This would extract these files outside the intended directory, potentially overwriting critical system files or exposing sensitive data.
Another Fastapi-specific manifestation occurs when using background tasks for archive processing:
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import JSONResponse
import zipfile
import os
from concurrent.futures import ThreadPoolExecutor
app = FastAPI()
executor = ThreadPoolExecutor()
@app.post("/upload-archive-bg/")
async def upload_archive_bg(file: UploadFile = File(...)):
# Vulnerable: background task still extracts without validation
await asyncio.get_event_loop().run_in_executor(
executor, process_archive, file.file
)
return JSONResponse(content={"message": "Archive queued for processing"})
def process_archive(file_stream):
extract_path = "/var/www/uploads"
with zipfile.ZipFile(file_stream, 'r') as zip_ref:
zip_ref.extractall(extract_path)
The background processing doesn't change the vulnerability—the path traversal issue remains.
Fastapi-Specific Detection
Detecting Zip Slip in Fastapi applications requires both static analysis of your code and dynamic scanning of your API endpoints. For Fastapi specifically, focus on these detection methods:
Static Code Analysis
Review your Fastapi endpoints that handle file uploads or archive processing. Look for these red flags:
# Dangerous patterns to search for:
zipfile.ZipFile(...).extractall(path) # No path validation
zipfile.ZipFile(...).extract(member, path) # No path validation
# Any file operation with user-controlled paths
open(user_provided_path, 'w')
Dynamic Scanning with middleBrick
middleBrick's black-box scanning approach is particularly effective for detecting Zip Slip in Fastapi applications because it tests the actual runtime behavior without needing source code access.
To scan your Fastapi API:
# Install middleBrick CLI
npm install -g middlebrick
# Scan your Fastapi endpoint
middlebrick scan http://localhost:8000/upload-archive/
middleBrick tests for Zip Slip by attempting to extract specially crafted zip files with path traversal sequences. The scanner creates archives with filenames containing ../../ patterns and verifies whether the application allows extraction outside the intended directory.
Manual Testing Methodology
If you want to manually test your Fastapi application for Zip Slip:
- Create a test zip file with traversal paths:
echo "test" > evil.txt mkdir -p test_traversal/evil_path zip -r traversal_test.zip test_traversal/evil_path/../../evil.txt - Attempt to upload to your Fastapi endpoint
- Check if the file appears outside the intended directory
- Verify if the application handled the traversal safely
middleBrick's Zip Slip Detection Features
middleBrick specifically tests for:
- Path traversal in zip archive extraction
- Directory traversal in tar archives (tar slip)
- Path validation bypass attempts
- Symbolic link exploitation within archives
The scanner provides detailed findings including:
- Exact path traversal sequences that succeeded
- Files that were extracted outside intended directories
- Severity assessment with CVSS scoring
- Remediation guidance specific to Fastapi applications
For Fastapi applications, middleBrick's continuous monitoring feature (Pro plan) can automatically re-scan your API endpoints on a schedule, alerting you if new Zip Slip vulnerabilities are introduced in updates.
Fastapi-Specific Remediation
Remediating Zip Slip in Fastapi applications requires implementing proper path validation before extracting archives. Here are Fastapi-specific solutions:
Safe Archive Extraction with Path Validation
The most robust approach is to validate all paths before extraction:
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import JSONResponse
import zipfile
import os
import pathlib
app = FastAPI()
SAFE_EXTRACT_DIR = pathlib.Path("/var/www/uploads")
def is_safe_path(base_path, target_path, follow_symlinks=True):
# Resolve both paths and check if target is within base
base_path = pathlib.Path(base_path).resolve()
target_path = pathlib.Path(target_path).resolve()
try:
if follow_symlinks:
# Check if base path is same as target path or a parent
return str(base_path) == str(target_path) or str(base_path) in str(target_path.parent)
else:
# Check if base path is same as target path or a parent (without resolving symlinks)
return str(base_path) == str(target_path) or str(base_path) in str(target_path.parent)
except (OSError, RuntimeError):
# If there's an error accessing the paths, reject
return False
@app.post("/upload-archive-safe/")
async def upload_archive_safe(file: UploadFile = File(...)):
extract_path = SAFE_EXTRACT_DIR
with zipfile.ZipFile(file.file, 'r') as zip_ref:
for member in zip_ref.namelist():
# Construct the full target path
target_path = extract_path / member
# Validate the path is safe
if not is_safe_path(extract_path, target_path):
return JSONResponse(
status_code=400,
content={"error": f"Path traversal attempt detected: {member}"}
)
# If all paths are safe, extract
zip_ref.extractall(extract_path)
return JSONResponse(content={"message": "Archive processed securely"})
Using a Dedicated Library for Safe Extraction
For production Fastapi applications, consider using a library designed for safe archive extraction:
from fastapi import FastAPI, File, UploadFile
from fastapi.responses import JSONResponse
import patoolib
import os
app = FastAPI()
@app.post("/upload-archive-patool/")
async def upload_archive_patool(file: UploadFile = File(...)):
extract_path = "/var/www/uploads"
try:
# patoolib handles path traversal automatically
patoolib.extract_archive(file=file.file, outdir=extract_path, interactive=False)
return JSONResponse(content={"message": "Archive processed securely"})
except patoolib.util.PatoolError as e:
return JSONResponse(
status_code=400,
content={"error": f"Archive extraction failed: {str(e)}"}
)
Fastapi Middleware for Archive Protection
Implement a Fastapi dependency to automatically validate archive uploads:
from fastapi import Depends, HTTPException, UploadFile
from fastapi.responses import JSONResponse
import zipfile
import pathlib
async def validate_archive(file: UploadFile = File(...)):
extract_path = pathlib.Path("/var/www/uploads")
with zipfile.ZipFile(file.file, 'r') as zip_ref:
for member in zip_ref.namelist():
target_path = extract_path / member
if not is_safe_path(extract_path, target_path):
raise HTTPException(
status_code=400,
detail=f"Path traversal attempt detected: {member}"
)
# Reset file pointer for downstream processing
file.file.seek(0)
return file
@app.post("/upload-archive-with-dep/")
async def upload_archive_with_dep(
file: UploadFile = Depends(validate_archive)
):
# File is already validated, safe to process
extract_path = "/var/www/uploads"
with zipfile.ZipFile(file.file, 'r') as zip_ref:
zip_ref.extractall(extract_path)
return JSONResponse(content={"message": "Archive processed securely"})
Testing Your Remediation
After implementing fixes, use middleBrick to verify your remediation:
# Test your fixed endpoint
middlebrick scan http://localhost:8000/upload-archive-safe/
middleBrick will attempt to exploit Zip Slip and confirm whether your validation logic successfully blocks path traversal attempts. The scanner provides specific feedback on whether your remediation is effective.
For Fastapi applications in production, consider implementing the Pro plan's continuous monitoring to automatically re-scan your API endpoints whenever code changes are deployed, ensuring new Zip Slip vulnerabilities aren't introduced in future updates.
Frequently Asked Questions
How can I test if my Fastapi application is vulnerable to Zip Slip?
../../ sequences and attempt to upload it to your Fastapi endpoint. If files are extracted outside the intended directory, you're vulnerable. Alternatively, use middleBrick's black-box scanning which automatically tests for Zip Slip by attempting path traversal during archive extraction.