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
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}` });
});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.