CWE-434 · OWASP A04:2021

File Upload Security: Risks, Detection, and Fixes

Unrestricted file upload (CWE-434) allows attackers to upload malicious files including web shells, scripts, and exploits. The fix requires layered validation: extension allowlisting, MIME type checking, magic byte verification, and safe storage. This guide covers every layer.

What is an unrestricted file upload vulnerability?

An unrestricted file upload vulnerability occurs when an application accepts user-uploaded files without properly validating their type, content, or name. The most dangerous consequence is remote code execution: an attacker uploads a web shell or script and then accesses it directly through the web server.

Even without direct execution, malicious uploads can exploit server-side file processors (image libraries, PDF parsers, XML handlers) or be used for phishing by hosting malicious content on a trusted domain.

Validation layers — defense in depth

LayerRisky approachSafe approachNote
Extension allowlistNo extension checkAllow only .jpg, .png, .pdf etc.First line of defense — easy to implement
MIME type checkTrust Content-Type headerVerify python-magic / file-typeContent-Type can be spoofed by attacker
Magic byte checkOnly check extensionRead first bytes, verify signatureCatches renamed files (shell.php.jpg)
Filename sanitizationUse original filenameGenerate random UUID filenamePrevents path traversal via filename
Storage locationStore in web rootStore outside web rootPrevents direct execution of uploaded files

Vulnerable vs secure examples

Python / Flask

Vulnerable

@app.route("/upload", methods=["POST"])
def upload():
    f = request.files["file"]
    # Dangerous: no validation, stores original name
    f.save(f"uploads/{f.filename}")
    return "ok"

Secure

import uuid, magic
ALLOWED = {".jpg", ".png", ".pdf"}

@app.route("/upload", methods=["POST"])
def upload():
    f = request.files["file"]
    ext = os.path.splitext(f.filename)[1].lower()
    if ext not in ALLOWED:
        abort(400)
    mime = magic.from_buffer(f.read(2048), mime=True)
    if mime not in {"image/jpeg","image/png","application/pdf"}:
        abort(400)
    f.seek(0)
    # Safe: random name, no original filename
    name = f"{uuid.uuid4()}{ext}"
    f.save(f"/var/uploads/{name}")
    return "ok"

Node.js / Express (Multer)

Vulnerable

const upload = multer({ dest: "uploads/" });
// Dangerous: no fileFilter, any type accepted
app.post("/upload", upload.single("file"), (req, res) => {
  res.send("uploaded");
});

Secure

const ALLOWED_MIMES = new Set(["image/jpeg","image/png","application/pdf"]);
const upload = multer({
  dest: "/var/uploads/",
  fileFilter: (req, file, cb) => {
    cb(null, ALLOWED_MIMES.has(file.mimetype));
  },
  limits: { fileSize: 5 * 1024 * 1024 }, // 5 MB
});
app.post("/upload", upload.single("file"), (req, res) => {
  res.send("uploaded");
});

How to detect file upload vulnerabilities

  • Look for file upload handlers with no extension or MIME type check
  • Check whether the original filename is used directly in storage paths (path traversal risk)
  • Check whether uploaded files are stored under the web root (execution risk)
  • Verify that file size limits are enforced (DoS risk)
# Semgrep: Flask upload handler with no extension check
rules:
  - id: flask-unrestricted-upload
    patterns:
      - pattern: $F.save(...)
      - pattern-not-inside: |
          if $EXT not in $ALLOWED:
              ...
    message: File upload handler may lack extension validation (CWE-434)
    severity: WARNING
    languages: [python]

How Orbis AppSec detects file upload vulnerabilities

Orbis AppSec identifies file upload handlers that lack validation and opens automated fix pull requests adding extension allowlisting and MIME type checks.

Source:request.files / multipart upload
Sink:file.save(), fs.writeFile()
Missing control:Extension allowlist, MIME check, magic bytes
CWE:CWE-434
Fix:Allowlist + MIME + UUID filename + safe storage path

Want Orbis AppSec to find and fix file upload vulnerabilities in your GitHub repositories?

Try Orbis AppSec

Related CWEs

Real-world Orbis AppSec case studies

Browse posts where Orbis AppSec detected and fixed file upload vulnerabilities in real open-source repositories:

View all security case studies

FAQ

What is an unrestricted file upload vulnerability?

An unrestricted file upload (CWE-434) occurs when an application accepts uploaded files without validating their type, content, or name. Attackers can upload web shells, malicious scripts, or files that exploit server-side processors.

What CWE is unrestricted file upload?

CWE-434: Unrestricted Upload of File with Dangerous Type.

Is checking the file extension enough?

No. Attackers can rename files to bypass extension checks (e.g., shell.php.jpg). Defense in depth requires extension allowlisting, MIME type verification, and magic byte validation.

How do you securely validate file uploads in Python?

Use an extension allowlist, verify MIME type with python-magic, read and check magic bytes, generate a UUID for the stored filename, and store files outside the web root.

Can static analysis detect file upload vulnerabilities?

Yes. Semgrep rules can flag file upload handlers that lack extension or MIME type checks, and CodeQL can trace file data flows to storage sinks.

What should the upload directory permissions be?

The upload directory should not be executable and should not be directly accessible by the web server as a static file root. Serve uploaded files through application logic that sets a safe Content-Type.

References