Hardening upload file berarti menganggap semua input dari klien tidak tepercaya, termasuk nama file, ekstensi, MIME type, dan tampilan ikon di UI. File yang terlihat “biasa” di desktop atau browser belum tentu benar-benar aman; representasi visual dapat menyesatkan, sementara backend tetap menerima byte mentah yang harus diperiksa sendiri.
Jika Anda membangun fitur upload pada aplikasi web, pertahanan dasarnya adalah: gunakan allowlist tipe file, validasi konten berdasarkan signature atau magic number, simpan file di lokasi terisolasi, beri nama file acak, batasi ukuran, scan file bila perlu, dan lindungi endpoint upload dengan session, CSRF, serta kontrol abuse seperti rate limit. Fokus artikel ini adalah backend, karena di situlah keputusan keamanan yang sebenarnya dibuat.
Mengapa upload file sering gagal diamankan
Banyak implementasi upload masih bergantung pada pemeriksaan yang mudah dipalsukan:
- Nama file bisa diganti bebas oleh pengguna.
- Ekstensi seperti
.jpgatau.pdftidak menjamin isi file sesuai. - MIME type dari client dikirim sebagai metadata request dan bisa dimanipulasi.
- Ikon atau preview UI hanya membantu pengguna, bukan bukti teknis jenis file.
Masalah utamanya: backend terlalu sering memperlakukan metadata dari klien sebagai fakta. Padahal yang perlu dipercaya adalah hasil pemeriksaan di server atas konten file itu sendiri.
Prinsip utama hardening upload file
1. Gunakan allowlist, bukan blocklist
Jangan mulai dari daftar file yang dilarang. Mulailah dari daftar pendek file yang memang dibutuhkan aplikasi. Misalnya, jika fitur hanya menerima dokumen, mungkin cukup izinkan PDF dan beberapa format gambar tertentu. Semakin sempit allowlist, semakin kecil permukaan serangan.
Contoh kebijakan yang lebih aman:
- Hanya izinkan: PDF, PNG, JPEG
- Tolak: arsip, executable, script, office macro-enabled, dan format yang tidak dibutuhkan
Trade-off-nya jelas: pengalaman pengguna bisa lebih ketat, tetapi ini biasanya sepadan untuk keamanan dan operasional.
2. Validasi tipe berdasarkan konten
Pemeriksaan terpenting adalah membaca byte awal file dan memastikan formatnya cocok dengan tipe yang diizinkan. Banyak format populer memiliki signature khas. Contohnya:
- PDF biasanya diawali
%PDF- - PNG diawali byte signature PNG
- JPEG umumnya diawali marker JPEG
Pemeriksaan ini sering disebut validasi magic number atau file signature. Ini bukan solusi sempurna untuk semua format, tetapi jauh lebih kuat daripada memeriksa ekstensi.
Catatan: beberapa format kontainer seperti ZIP bisa dipakai oleh banyak jenis file. Jangan berhenti di signature umum saja. Jika Anda menerima format berbasis kontainer, periksa struktur internal yang relevan atau pertimbangkan untuk tidak mengizinkannya jika tidak benar-benar diperlukan.
3. Jangan simpan file upload di bawah web root
Kesalahan klasik adalah menaruh hasil upload langsung di direktori yang bisa dieksekusi atau diakses publik oleh web server. Jika terjadi salah konfigurasi, file berbahaya bisa diunduh, diparse, atau bahkan dieksekusi oleh komponen lain.
Praktik yang lebih aman:
- Simpan file di storage terisolasi di luar document root
- Akses file melalui aplikasi atau layanan file terpisah
- Jika memakai object storage, gunakan bucket dan policy yang ketat
- Pisahkan bucket atau path untuk file mentah, file tervalidasi, dan file karantina
4. Gunakan nama file acak
Jangan gunakan nama asli file sebagai path penyimpanan. Nama asli boleh disimpan sebagai metadata untuk ditampilkan ke pengguna, tetapi file fisik sebaiknya disimpan dengan identifier acak, misalnya UUID atau token kriptografis.
Ini membantu mencegah:
- Tabrakan nama file
- Path traversal terselubung
- Kebocoran informasi dari nama file
- Penebakan URL jika file diakses melalui pola nama tertentu
5. Batasi ukuran dan jumlah file
Pembatasan ukuran adalah kontrol keamanan dan operasional. File besar dapat menghabiskan bandwidth, memori, disk, waktu parsing, dan antrean scanning. Batas ideal bergantung pada kebutuhan bisnis, tetapi prinsipnya adalah tetapkan batas sekecil mungkin.
Selain ukuran per file, pertimbangkan juga:
- Batas jumlah file per request
- Batas total ukuran per request
- Batas total upload per user per jam atau per hari
- Timeout untuk upload dan pemrosesan
Validasi konten upload di backend
Urutan validasi yang disarankan
Urutan ini membantu menghemat resource dan mengurangi risiko:
- Verifikasi autentikasi dan session
- Verifikasi CSRF untuk request berbasis browser session
- Terapkan rate limit awal
- Tolak request yang melebihi batas ukuran maksimum
- Simpan sementara ke lokasi staging non-publik
- Periksa signature atau magic number
- Cocokkan hasil deteksi tipe dengan allowlist
- Lakukan scanning tambahan bila kebijakan mengharuskan
- Normalisasi metadata yang diperlukan
- Pindahkan ke storage final dengan nama acak
- Catat audit log dan hasil validasi
Contoh pseudocode upload yang aman
function handleUpload(request, session) {
requireAuthenticatedSession(session)
requireCsrfToken(request)
enforceRateLimit(session.userId, request.ip)
file = request.files["document"]
if (!file) reject(400, "file_required")
if (file.size <= 0 || file.size > MAX_BYTES) reject(413, "file_too_large")
tempPath = saveToStaging(file.stream)
detectedType = detectFileTypeFromContent(tempPath)
if (!ALLOWED_TYPES.includes(detectedType)) {
deleteFile(tempPath)
reject(415, "unsupported_type")
}
if (!scanFile(tempPath)) {
moveToQuarantine(tempPath)
reject(422, "file_rejected")
}
storageKey = generateRandomStorageKey(detectedType)
finalPath = moveToIsolatedStorage(tempPath, storageKey)
recordUpload({
userId: session.userId,
originalName: safeStoreAsMetadata(file.originalName),
detectedType: detectedType,
size: file.size,
storageKey: storageKey,
uploadedAt: now()
})
return success({ id: storageKey })
}Poin penting dari alur di atas:
- Validasi akses lebih dulu, agar resource server tidak habis untuk request anonim yang tidak sah.
- Simpan ke staging sebelum dipromosikan ke storage final.
- Keputusan tipe file didasarkan pada hasil deteksi server, bukan metadata klien.
- Scanning dan karantina memisahkan file yang dicurigai dari file yang lolos validasi.
Pemeriksaan magic number: apa yang bisa dan tidak bisa dilakukan
Pemeriksaan magic number efektif untuk mendeteksi banyak pemalsuan sederhana, misalnya file executable yang diberi nama .jpg. Namun ada batasannya:
- Beberapa format kompleks memerlukan validasi struktur internal, bukan hanya byte awal.
- File bisa saja valid secara format tetapi tetap berbahaya dalam konteks aplikasi tertentu.
- Polyglot file atau file yang sengaja dibentuk untuk lolos beberapa parser bisa menipu validasi sederhana.
Karena itu, magic number sebaiknya dipakai sebagai lapisan pertama, bukan satu-satunya perlindungan.
Proteksi penyimpanan dan akses file
Isolasi storage
Sediakan area storage dengan pemisahan yang jelas:
- staging/ untuk file baru yang belum tervalidasi
- safe/ untuk file yang lolos validasi dan scanning
- quarantine/ untuk file yang dicurigai atau gagal scan
Jika memakai object storage, pisahkan prefix atau bucket bila kebijakan organisasi mendukungnya. Batasi siapa yang bisa menulis dan membaca masing-masing area.
Path traversal dan manipulasi path
Jangan pernah membentuk path final langsung dari input pengguna. Nama file seperti ../../app.env atau varian encoding-nya bisa menyebabkan file ditulis ke lokasi yang tidak semestinya jika aplikasi menggabungkan path secara naif.
Prinsip aman:
- Jangan gunakan nama asli untuk path final
- Bangun path dari identifier internal yang dihasilkan server
- Jika perlu menyimpan nama asli, perlakukan hanya sebagai metadata tampilan
- Saat menyajikan file kembali, gunakan lookup berdasarkan ID, bukan path dari user
Hindari menyajikan file mentah tanpa kontrol
Jika aplikasi mengizinkan unduhan, pertimbangkan alur berikut:
- Client meminta file berdasarkan ID
- Server memeriksa otorisasi
- Server mengirim file dari storage aman atau menghasilkan URL sementara
Ini lebih aman daripada mengekspos struktur path internal ke pengguna.
Session, otorisasi, CSRF, dan abuse guard
Autorisasi berbasis session
Endpoint upload sebaiknya tidak hanya memeriksa apakah pengguna login, tetapi juga apakah ia berhak mengunggah ke resource tertentu. Misalnya, user boleh mengunggah avatar miliknya sendiri, tetapi tidak boleh mengunggah dokumen ke akun user lain hanya karena mengetahui ID-nya.
Periksa minimal:
- ID user dari session valid
- Hak akses terhadap entitas target, misalnya project, ticket, invoice, atau profil
- Status akun atau kebijakan bisnis yang membatasi upload
CSRF untuk upload berbasis browser
Jika upload dilakukan melalui browser dengan cookie session, endpoint perlu perlindungan CSRF. Tanpa itu, situs lain dapat mencoba memicu request atas nama user yang sedang login.
Gunakan token CSRF yang diverifikasi di server. Untuk API non-browser berbasis token otorisasi eksplisit, strategi ini bisa berbeda, tetapi untuk form atau AJAX yang memakai cookie session, CSRF tetap relevan.
Rate limit dan mitigasi abuse
Upload file adalah endpoint mahal. Selain bandwidth, server Anda mungkin melakukan buffering, deteksi tipe, hashing, ekstraksi metadata, dan scanning. Karena itu, rate limit perlu diterapkan sebagai kontrol inti, bukan tambahan.
Contoh dimensi rate limit:
- Per IP
- Per user/session
- Per tenant atau organisasi
- Per endpoint
Mitigasi abuse lain yang berguna:
- Batas jumlah file gagal per periode waktu
- Challenge tambahan untuk pola mencurigakan
- Blok sementara untuk client yang terus mengirim tipe file terlarang
- Audit log untuk ukuran file tidak lazim, frekuensi tinggi, atau pola bypass
Praktik baik: pisahkan respons internal dan eksternal. Ke klien, kembalikan error yang cukup informatif tanpa membocorkan detail mekanisme deteksi. Di log internal, simpan alasan detail penolakan untuk investigasi.
Scanning, parsing, dan risiko lanjutan
Kapan perlu scanning antivirus atau content scanning
Jika aplikasi menerima file dari pihak luar dan file itu akan didistribusikan lagi, diunduh user lain, atau diproses lebih lanjut, scanning biasanya layak dipertimbangkan. Contohnya:
- Portal dokumen
- Lampiran tiket dukungan
- Sistem kolaborasi
- Marketplace konten
Model implementasi yang umum adalah upload ke staging, lalu kirim job scanning asinkron. File baru dianggap belum tersedia sampai hasil scan dinyatakan aman.
Waspadai parser dan proses turunan
Terkadang masalah bukan pada file yang disimpan, tetapi pada sistem yang membacanya kemudian. Thumbnail generator, OCR, extractor metadata, converter dokumen, atau library parser dapat menjadi titik serang.
Mitigasi praktis:
- Jalankan pemrosesan file di worker terisolasi
- Batasi hak akses proses worker
- Terapkan timeout dan batas memori
- Gunakan antrean terpisah untuk tugas pemrosesan berat
- Hindari memproses format yang tidak dibutuhkan
Contoh alur request upload yang aman
- User login dan mendapatkan session yang sah.
- Browser mengirim request upload dengan cookie session dan token CSRF.
- Server memeriksa session, otorisasi terhadap resource target, dan CSRF.
- Server menerapkan rate limit per user dan per IP.
- Server menolak lebih awal jika header ukuran jelas melampaui batas yang diizinkan.
- Stream file disimpan ke lokasi staging non-publik.
- Server menghitung hash bila diperlukan untuk deduplikasi atau audit.
- Server mendeteksi tipe file dari konten dan mencocokkannya dengan allowlist.
- Server menjalankan scanning atau validasi tambahan.
- Jika lolos, file dipindahkan ke storage final dengan nama acak.
- Metadata upload disimpan di database, termasuk pemilik, ukuran, tipe terdeteksi, dan status.
- Server mengembalikan ID internal, bukan path file fisik.
Alur ini bekerja karena setiap tahap membatasi risiko berbeda: akses ilegal, upload massal, file palsu, path traversal, dan file yang baru diketahui berbahaya setelah diperiksa lebih dalam.
Checklist implementasi hardening upload file
- Gunakan allowlist tipe file yang sempit dan sesuai kebutuhan bisnis
- Jangan percaya nama file, ekstensi, MIME type dari client, atau ikon UI
- Deteksi tipe file dari konten atau magic number
- Tolak format kompleks yang tidak benar-benar dibutuhkan
- Terapkan batas ukuran per file, per request, dan per user
- Simpan file ke staging non-publik sebelum validasi selesai
- Gunakan nama file acak untuk storage final
- Jangan bangun path dari input pengguna
- Isolasi storage: staging, safe, dan quarantine
- Lindungi endpoint dengan session yang valid dan otorisasi resource-level
- Aktifkan CSRF protection untuk upload berbasis browser session
- Terapkan rate limit per IP dan per user
- Scan file jika model ancamannya membutuhkan
- Jalankan parser atau converter di worker terisolasi
- Simpan audit log untuk penolakan, anomali, dan aktivitas tinggi
- Sajikan file kembali melalui ID internal atau URL sementara, bukan path mentah
Kesalahan umum yang sering lolos review
Hanya memeriksa ekstensi di frontend
Validasi frontend berguna untuk UX, tetapi tidak boleh dijadikan kontrol keamanan. Request bisa dikirim tanpa melalui UI Anda.
Mengandalkan header Content-Type dari browser
Header ini adalah petunjuk, bukan bukti. Banyak client dapat mengubahnya dengan mudah.
Menyimpan upload langsung di folder publik
Ini memperbesar dampak salah konfigurasi, kebocoran data, dan pemanfaatan file berbahaya oleh komponen lain.
Menggunakan nama asli file sebagai nama penyimpanan
Selain riskan untuk traversal, pendekatan ini juga memicu tabrakan nama dan kebocoran informasi.
Tidak memverifikasi otorisasi target
Endpoint sering memeriksa login, tetapi lupa mengecek apakah user berhak mengunggah ke entitas yang dituju.
Tidak ada rate limit karena dianggap upload sudah mahal
Justru karena mahal, upload perlu dilindungi. Tanpa pembatasan, endpoint ini mudah dipakai untuk denial of service atau pemborosan storage.
Menganggap file aman setelah lolos upload
Risiko bisa muncul saat file diproses, dipreview, atau diunduh kembali. Perlindungan harus mencakup seluruh siklus hidup file.
Tips debugging dan verifikasi
- Uji file dengan ekstensi yang dipalsukan, misalnya executable yang dinamai sebagai gambar
- Uji file kosong, file sangat besar, dan file yang dipotong di tengah upload
- Uji nama file aneh: unicode, spasi berlebih, path separator, dan urutan traversal
- Pastikan file yang gagal validasi benar-benar dihapus atau dipindah ke karantina
- Periksa log untuk melihat apakah alasan penolakan cukup jelas untuk investigasi
- Uji bahwa user A tidak bisa mengunggah file ke resource milik user B
- Simulasikan burst upload untuk memastikan rate limit bekerja
Penutup
Hardening upload file bukan soal satu filter tunggal, melainkan rangkaian kontrol yang saling melengkapi. Backend harus mengambil keputusan berdasarkan isi file dan konteks akses, bukan berdasarkan tampilan UI atau metadata dari klien. Dengan validasi konten, storage terisolasi, nama file acak, pembatasan ukuran, session dan CSRF yang benar, serta abuse guard yang memadai, fitur upload Anda jauh lebih tahan terhadap penyalahgunaan yang sering luput dari implementasi dasar.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!