Description
Using an uploaded file's original name directly as a storage path or object name can allow path traversal such as ../, unintended overwrites, or files with executable extensions. Do not trust the original name; generate or sanitize the name on the server.
Potential impact
- Files outside the upload directory may be overwritten.
- Existing files or configuration may be damaged.
- Malicious files may be saved under a path served by the application.
Remediation
- Do not use client-supplied file names directly as storage paths or keys.
- With Flask/Werkzeug, sanitize names with
secure_filename()or generate a server-side name such as a UUID. - With Django/FastAPI, also generate a UUID, random token, or name defined by server policy instead of using the uploaded name.
- Restrict destination directories and file extensions to an allow-list.
Examples
Before
python
import os
from flask import request
def bad_upload():
file = request.files["file"]
file.save(os.path.join("uploads", file.filename))
After
Prepare UPLOAD_DIR as a private directory writable only by the application.
python
import uuid
from pathlib import Path
from flask import abort, request
from werkzeug.utils import secure_filename
UPLOAD_DIR = Path("/srv/app/upload-quarantine")
ALLOWED_EXTENSIONS = {".png", ".jpg", ".jpeg"}
def safe_upload():
file = request.files["file"]
original_name = secure_filename(file.filename or "")
extension = Path(original_name).suffix.lower()
if extension not in ALLOWED_EXTENSIONS:
abort(400, description="Unsupported file extension")
stored_name = f"{uuid.uuid4().hex}{extension}"
file.save(UPLOAD_DIR / stored_name)
return {"id": stored_name}, 201
Explanation:
- Before: The original file name is joined directly to the destination path.
- After: The sanitized original name is used only to check the extension policy. Only permitted extensions are stored in the private upload directory. The actual name is a server-generated UUID, so client-controlled paths, absolute paths, and overwrite targets do not become the file-system path. If file format is a security boundary, verify the MIME type and actual content separately.