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 .jpg atau .pdf tidak 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:

  1. Verifikasi autentikasi dan session
  2. Verifikasi CSRF untuk request berbasis browser session
  3. Terapkan rate limit awal
  4. Tolak request yang melebihi batas ukuran maksimum
  5. Simpan sementara ke lokasi staging non-publik
  6. Periksa signature atau magic number
  7. Cocokkan hasil deteksi tipe dengan allowlist
  8. Lakukan scanning tambahan bila kebijakan mengharuskan
  9. Normalisasi metadata yang diperlukan
  10. Pindahkan ke storage final dengan nama acak
  11. 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

  1. User login dan mendapatkan session yang sah.
  2. Browser mengirim request upload dengan cookie session dan token CSRF.
  3. Server memeriksa session, otorisasi terhadap resource target, dan CSRF.
  4. Server menerapkan rate limit per user dan per IP.
  5. Server menolak lebih awal jika header ukuran jelas melampaui batas yang diizinkan.
  6. Stream file disimpan ke lokasi staging non-publik.
  7. Server menghitung hash bila diperlukan untuk deduplikasi atau audit.
  8. Server mendeteksi tipe file dari konten dan mencocokkannya dengan allowlist.
  9. Server menjalankan scanning atau validasi tambahan.
  10. Jika lolos, file dipindahkan ke storage final dengan nama acak.
  11. Metadata upload disimpan di database, termasuk pemilik, ukuran, tipe terdeteksi, dan status.
  12. 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.