Risiko File Upload
Fitur upload file adalah salah satu attack vector terbesar di aplikasi web. Tanpa validasi yang tepat, penyerang bisa mengupload file berbahaya yang dieksekusi di server.
Ancaman dari File Upload
- Web shell — File PHP/JSP/ASP yang memberikan kontrol penuh terhadap server
- Malware — File yang menginfeksi server atau pengguna lain yang mengunduh
- Path traversal — Menulis file ke lokasi yang tidak diizinkan
- DoS — Upload file berukuran sangat besar untuk menghabiskan storage
- XSS via SVG/HTML — File SVG atau HTML dengan JavaScript embedded
Validasi File Upload
1. Validasi Tipe File
// BURUK: hanya cek extension (mudah dibypass)
if (file.name.endsWith(".jpg")) { /* upload */ }
// BAIK: cek MIME type dari file header (magic bytes)
const fileType = await import("file-type");
const type = await fileType.fromBuffer(buffer);
const allowedTypes = ["image/jpeg", "image/png", "image/webp"];
if (!type || !allowedTypes.includes(type.mime)) {
throw new Error("Tipe file tidak diizinkan");
}
2. Batasi Ukuran File
// Express.js dengan multer
const upload = multer({
limits: { fileSize: 5 * 1024 * 1024 }, // Maksimal 5MB
fileFilter: (req, file, cb) => {
const allowed = ["image/jpeg", "image/png"];
if (allowed.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error("Tipe file tidak diizinkan"), false);
}
}
});
3. Rename File
// JANGAN gunakan nama file asli dari user
// BURUK:
const path = `/uploads/${file.originalname}`; // Bisa path traversal!
// BAIK: generate nama random
const crypto = require("crypto");
const ext = allowedExtensions[type.mime]; // Tentukan ext dari MIME
const filename = crypto.randomUUID() + "." + ext;
const path = `/uploads/${filename}`;
4. Simpan di Luar Web Root
# BURUK: file bisa diakses langsung dan dieksekusi
/var/www/html/uploads/shell.php → http://site.com/uploads/shell.php
# BAIK: simpan di luar web root, serve melalui controller
/var/data/uploads/abc123.jpg
# Akses melalui route yang controlled: /api/files/abc123
5. Laravel File Validation
// Laravel validation rules
$request->validate([
"avatar" => [
"required",
"file",
"image", // Hanya gambar
"mimes:jpg,png,webp", // Extension yang diizinkan
"max:5120", // Maksimal 5MB
"dimensions:max_width=2000,max_height=2000"
]
]);
Checklist Secure File Upload
- Validasi MIME type dari file content, bukan extension
- Batasi ukuran file
- Rename file dengan nama random
- Simpan di luar web root
- Gunakan antivirus scanning untuk file yang diupload
- Set Content-Disposition: attachment saat serving file