highAuthentication

Unrestricted File Upload Vulnerabilities

What Is This Vulnerability?

Unrestricted file upload vulnerabilities occur when an application allows users to upload files without properly validating the file type, size, or contents. Attackers can upload malicious files such as web shells, scripts, or executables that can then be accessed via the web server to achieve remote code execution or serve malware to other users.

Why It Happens

Applications need file upload functionality for profile pictures, document attachments, or media content. Developers often validate only the file extension from the filename rather than inspecting the actual file content. The uploaded file is then stored in a publicly accessible directory where the web server may execute it.

Example Code

Vulnerableroutes/upload.ts
import multer from "multer";

const upload = multer({ dest: "public/uploads/" });

app.post("/upload", upload.single("file"), (req, res) => {
  res.json({ url: `/uploads/${req.file!.filename}` });
});
Fixedroutes/upload.ts
import multer from "multer";
import path from "path";
import { fileTypeFromBuffer } from "file-type";

const ALLOWED_TYPES = ["image/jpeg", "image/png", "image/webp"];
const MAX_SIZE = 5 * 1024 * 1024; // 5MB

const upload = multer({
  dest: "temp-uploads/",
  limits: { fileSize: MAX_SIZE },
  fileFilter: (_req, file, cb) => {
    const ext = path.extname(file.originalname).toLowerCase();
    if ([".jpg", ".jpeg", ".png", ".webp"].includes(ext)) {
      cb(null, true);
    } else {
      cb(new Error("Invalid file type"));
    }
  },
});

app.post("/upload", upload.single("file"), async (req, res) => {
  const buffer = await fs.readFile(req.file!.path);
  const detected = await fileTypeFromBuffer(buffer);
  if (!detected || !ALLOWED_TYPES.includes(detected.mime)) {
    await fs.unlink(req.file!.path);
    return res.status(400).send("Invalid file content");
  }

  const safeName = `${crypto.randomUUID()}${path.extname(req.file!.originalname)}`;
  await fs.rename(req.file!.path, `private-uploads/${safeName}`);
  res.json({ id: safeName });
});

How Hackers Exploit It

Attackers upload a file with a .php, .jsp, or .js extension containing a web shell. If the file is stored in a publicly accessible directory and the server executes it, the attacker gains remote code execution. They can also upload HTML files containing scripts for stored XSS, or rename malicious executables with image extensions to bypass simple filename checks.

How to Fix It

Validate both the file extension and the actual file content using magic bytes (file signature detection). Store uploaded files outside the web root or use a cloud storage service like S3. Generate random filenames to prevent path-based attacks. Set Content-Disposition: attachment headers when serving files. Never execute or serve uploaded files with their original filenames.

Frequently Asked Questions

Why is checking the file extension not enough?
File extensions can be trivially changed. An attacker can rename a PHP web shell to image.jpg. The server may still execute it based on content type or other configuration. Always validate the actual file content by reading its magic bytes (file signature) to confirm it matches the claimed type.
Where should uploaded files be stored?
Store uploads outside the web root so they cannot be accessed directly by URL. Serve them through a controller that checks permissions and sets proper headers. Better yet, use a cloud storage service like S3 with pre-signed URLs for time-limited access.
Can image files contain malicious code?
Yes. Polyglot files can be valid images that also contain embedded scripts or PHP code. Image metadata (EXIF data) can contain XSS payloads. Strip metadata from uploaded images using a processing library, and serve all uploads with Content-Type headers that prevent browser execution.

Related Security Topics

Check Your Code for This Vulnerability

Run a free scan to check if your site is affected by unrestricted file upload vulnerabilities.