CWE-434CriticalMITRE entry

Unrestricted File Upload

Unrestricted Upload of File with Dangerous Type

What it is

CWE-434 exists when a web application accepts file uploads without restricting type, size, or storage location, allowing an attacker to upload a file that is then executed or served as a different type than expected. The classic exploitation is uploading a PHP or JSP script to a web root and requesting it to obtain remote code execution.

Why it matters

File upload vulnerabilities map to immediate remote code execution in PHP, JSP, ASP, and CGI environments, and to stored XSS or content-spoofing in static asset stores. The pattern recurs in profile-picture uploaders, resume submission forms, document management features, and image conversion pipelines. Cloud storage with overly permissive ACLs amplifies the impact: a malicious file uploaded to a public-read S3 bucket can be linked from phishing sites under a trusted-looking domain.

Common patterns

  • Saving uploaded files inside a web-server document root using the client-supplied filename.
  • Validating type by checking only the file extension or the Content-Type header (both attacker-controlled).
  • Storing uploads without resetting executable bits or without an explicit Content-Disposition header.
  • Allowing archive uploads (zip, tar) that extract without validating entry paths (Zip Slip, CWE-22 overlap).
  • Accepting SVG uploads served with image/svg+xml: SVG can carry executable JavaScript (stored XSS).
  • Image preview pipelines that pass user-controlled files to ImageMagick, ffmpeg, or LibreOffice without sandboxing (CWE-78 overlap).

Languages affected

PythonJavaScriptTypeScriptRubyJavaGoPHP

What Deva detects

Deva tracks data flow from file upload handlers (multer, busboy, Flask request.files, Django UploadedFile, Spring MultipartFile, Rails ActiveStorage) to filesystem write calls. The scanner reports findings when extension is not validated against an allowlist, when content-type is trusted as a security control, when the destination path is constructed from the client-supplied filename, and when archive extraction lacks per-entry path validation.

Example

Vulnerable

import express from 'express'
import multer from 'multer'

const upload = multer({ dest: 'public/uploads/' })
const app = express()

app.post('/upload', upload.single('file'), (req, res) => {
  res.json({ url: `/uploads/${req.file.originalname}` })
})

Fixed

import express from 'express'
import multer from 'multer'
import path from 'path'
import crypto from 'crypto'
import { fileTypeFromFile } from 'file-type'

const ALLOWED_EXT = new Set(['.png', '.jpg', '.jpeg', '.gif'])
const ALLOWED_MIME = new Set(['image/png', 'image/jpeg', 'image/gif'])

const upload = multer({
  dest: '/var/uploads-private/',
  limits: { fileSize: 5 * 1024 * 1024 },
  fileFilter: (req, file, cb) => {
    const ext = path.extname(file.originalname).toLowerCase()
    if (!ALLOWED_EXT.has(ext)) return cb(new Error('Bad extension'))
    cb(null, true)
  },
})

const app = express()
app.post('/upload', upload.single('file'), async (req, res) => {
  // Verify content by sniffing magic bytes, not by trusting client metadata.
  const detected = await fileTypeFromFile(req.file.path)
  if (!detected || !ALLOWED_MIME.has(detected.mime)) {
    return res.status(400).json({ error: 'Bad file content' })
  }
  // Rename to a server-generated filename so attacker cannot control path.
  const safeName = crypto.randomBytes(16).toString('hex') + '.' + detected.ext
  const target = path.join('/var/uploads-private/', safeName)
  await fs.promises.rename(req.file.path, target)
  res.json({ id: safeName })
})

// Serve via a controlled route that sets Content-Disposition, not via static middleware
// from a web-accessible upload directory.

Explanation

The vulnerable version stores uploads in a publicly-served directory using the attacker's filename. Uploading evil.php gives the attacker a remote code execution endpoint at /uploads/evil.php. The fix stores uploads outside the web root, validates the extension against an allowlist, verifies content by sniffing magic bytes (file-type), generates a server-side filename, and serves files via a controlled route that forces download via Content-Disposition. The four layers together are the standard defense.

Where this fits in OWASP Top 10

Compliance framework mapping

FrameworkControls
OWASP Top 10 (2021)
A03:2021 InjectionA04:2021 Insecure Design
NIST 800-53 Rev 5
SI-10 Information Input ValidationSC-18 Mobile Code
PCI-DSS v4.0
6.2.4 Software engineering techniques
CMMC 2.0 L2
SI.L2-3.14.1 Flaw remediation

Related CWEs

Deva detects CWE-434 alongside 970+ other CWE patterns at write time, with AI-assisted fix generation that maintains compliance.