OWASP File Upload Security

Profile pictures, résumés, invoices, and support attachments make applications useful. Each upload also carries risk. A file that looks like a picture may contain a script, a huge file may crash the server, and a clever file name may overwrite important data. Safe upload handling treats every file as untrusted.

The Mailroom Comparison

A company mailroom opens no package blindly. Staff check the sender, inspect the box, weigh it, and send it to the right desk. A secure upload feature follows the same routine for files.

What Can Go Wrong

RiskPlain Meaning
Executable uploadAn uploaded script runs on the server when someone opens its address
Stored script contentAn uploaded page or image runs scripts in visitors' browsers
Path tricksA file name such as ../../config overwrites files outside the upload folder
Oversized filesLarge uploads fill the disk or memory
MalwareInfected documents spread to other users
Decompression bombsA small archive expands into enormous data

Safe Upload Pipeline

 User selects file
        |
        ↓
 [1] Size limit check
        |
        ↓
 [2] Extension allowlist check  (.jpg .png .pdf)
        |
        ↓
 [3] Content check  (file signature matches claimed type)
        |
        ↓
 [4] Malware scan
        |
        ↓
 [5] Rename with random name
        |
        ↓
 [6] Store outside web root or in object storage
        |
        ↓
 [7] Serve through controlled download with safe headers

Step-by-Step Guidance

Allow Only Needed Types

Define the exact extensions the feature requires. A profile picture form accepts JPG and PNG and nothing else.

Verify the Content

The extension and the declared content type come from the user and can lie. Read the first bytes of the file, known as the file signature or magic bytes, and confirm they match the claimed type. Re-encode images with a trusted library to strip hidden data.

Generate New File Names

Ignore the user's file name. Create a random name, such as a long unique identifier, and keep the original name in the database for display only. Random names stop path tricks and overwrites.

Store Files Safely

Place uploads outside the folder that the web server serves directly, or use cloud object storage. Remove execute permission from the upload directory.

Control Delivery

Serve files with the correct content type, the X-Content-Type-Options: nosniff header, and a Content-Disposition: attachment header when the browser should download instead of display. Host user files on a separate domain so that any script inside them cannot reach your main site cookies.

Code Pattern

import uuid, os

ALLOWED = {".jpg", ".jpeg", ".png"}
MAX_BYTES = 2 * 1024 * 1024

def save_upload(file_bytes, original_name):
    ext = os.path.splitext(original_name)[1].lower()
    if ext not in ALLOWED or len(file_bytes) > MAX_BYTES:
        raise ValueError("File rejected")
    new_name = uuid.uuid4().hex + ext
    with open(os.path.join("/data/uploads", new_name), "wb") as f:
        f.write(file_bytes)
    return new_name

Extra Safeguards

  • Require login before upload and limit the number of uploads per user.
  • Scan archives with limits on depth and expanded size.
  • Log every upload with the user, time, and file hash.
  • Delete unused files on a schedule.

Key Takeaways

  • Every uploaded file is untrusted input.
  • Allowlists, content checks, and random names block the most common attacks.
  • Safe storage and controlled delivery stop uploaded files from running.
  • Size limits and scanning protect resources and other users.