Home / Artikel / Web Security
Web Security

Fitur Upload File Bukan Sekadar Tombol: Cara Membuatnya Lebih Aman di PHP dan WordPress

Fitur upload foto, dokumen, atau avatar sering dianggap sederhana, padahal bisa menjadi jalan masuk file berbahaya ke server. Pelajari cara membatasi tipe file, memeriksa isinya, mengganti nama file, dan menyimpannya ta…

Fitur Upload File Bukan Sekadar Tombol: Cara Membuatnya Lebih Aman di PHP dan WordPress

Fitur upload file terlihat sederhana: pengguna memilih berkas, menekan tombol, lalu server menyimpannya. Masalahnya, file yang masuk bukan hanya “gambar” atau “dokumen”. Ia juga membawa nama file, ukuran, tipe MIME, isi biner, dan kemungkinan muatan berbahaya yang harus diproses aplikasi.

Jika validasinya hanya memeriksa akhiran .jpg atau percaya pada Content-Type dari browser, perlindungannya masih tipis. File dengan nama yang tampak aman dapat berisi format yang berbeda, terlalu besar, atau sengaja dibuat untuk mengeksploitasi proses pemrosesan di server. Karena itu, keamanan upload perlu dibangun berlapis, bukan mengandalkan satu pemeriksaan saja.

Kenapa upload file sering menjadi celah?

Risikonya bergantung pada apa yang dilakukan aplikasi setelah file diterima. File dapat disimpan di dalam webroot, lalu diakses langsung melalui URL. Jika server salah konfigurasi dan mengizinkan file tertentu dieksekusi, penyerang mungkin mencoba mengunggah skrip server.

Risiko lain tidak selalu berupa pengambilalihan server. File besar dapat memenuhi ruang penyimpanan. Arsip dapat mengembang berkali-kali lipat saat diekstrak. Dokumen atau gambar yang diproses oleh library lama dapat memicu kerentanan pada parser. File yang bisa diakses publik juga dapat digunakan untuk phishing atau menyimpan konten berbahaya.

OWASP File Upload Cheat Sheet menyarankan pendekatan defense in depth: membatasi ekstensi, memeriksa tipe file, mengganti nama file, mengatur ukuran, membatasi akses pengguna, dan memilih lokasi penyimpanan yang aman.

Mulai dari allowlist, bukan daftar larangan

Jika fitur hanya membutuhkan foto profil, tentukan sejak awal format yang benar-benar diperlukan, misalnya JPEG, PNG, dan WebP. Jangan menerima semua jenis file lalu mencoba memblokir beberapa ekstensi berbahaya. Daftar larangan hampir selalu berisiko melewatkan variasi baru atau format yang tidak terpikirkan.

Gunakan allowlist, yaitu daftar tipe yang diizinkan. Untuk dokumen, mungkin hanya PDF dan DOCX. Untuk lampiran internal, bisa jadi tidak perlu menerima arsip ZIP sama sekali karena arsip lebih sulit diperiksa dan dapat berisi banyak jenis file.

Validasi ini harus dilakukan di server. Pemeriksaan JavaScript di browser hanya membantu pengalaman pengguna; pemeriksaan tersebut bisa dilewati dengan request buatan sendiri.

Jangan percaya nama file dan Content-Type

Nama file berasal dari pengguna. Nama seperti laporan.pdf belum membuktikan bahwa isinya benar-benar PDF. Begitu pula header Content-Type dapat dipalsukan oleh client.

Di PHP, finfo_file() dapat membantu membaca informasi tipe berdasarkan isi file. Namun, pemeriksaan MIME bukan satu-satunya pertahanan. Gunakan kombinasi pemeriksaan ekstensi yang diizinkan, ukuran maksimum, tipe yang terdeteksi, dan validasi khusus sesuai format file.

$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['avatar']['tmp_name']);

$allowed = [
    'image/jpeg' => 'jpg',
    'image/png'  => 'png',
    'image/webp' => 'webp'
];

if (!isset($allowed[$mime])) {
    throw new RuntimeException('Tipe file tidak diizinkan');
}

Contoh ini hanya ilustrasi dasar. Untuk gambar, proses tambahan seperti membuka lalu menulis ulang gambar dengan library yang sesuai dapat membantu menghapus data tambahan yang tidak diperlukan. Tetap perbarui library pemrosesan gambar dan dokumen karena parser juga dapat memiliki kerentanan.

Ganti nama file sebelum menyimpannya

Jangan menggunakan nama asli file sebagai nama di server. Nama tersebut bisa mengandung karakter khusus, path traversal, ekstensi ganda, atau menimpa file yang sudah ada. Simpan file menggunakan nama acak yang dibuat aplikasi, misalnya UUID atau hasil random_bytes().

Ekstensi penyimpanan juga sebaiknya ditentukan dari tipe yang sudah divalidasi, bukan disalin begitu saja dari nama asli. Dengan begitu, nama foto.php.jpg tidak ikut menjadi dasar keputusan penyimpanan.

$randomName = bin2hex(random_bytes(16)) . '.' . $allowed[$mime];
$destination = $uploadDir . DIRECTORY_SEPARATOR . $randomName;

if (!move_uploaded_file($_FILES['avatar']['tmp_name'], $destination)) {
    throw new RuntimeException('File gagal disimpan');
}

Fungsi move_uploaded_file() memastikan sumber file berasal dari proses upload HTTP PHP. Namun, fungsi ini tidak otomatis menentukan apakah file aman. Validasi tipe, ukuran, izin pengguna, dan lokasi tujuan tetap menjadi tanggung jawab aplikasi.

Simpan di luar webroot jika memungkinkan

Webroot adalah direktori yang dapat dilayani langsung oleh web server. Menyimpan file upload di luar lokasi ini mengurangi risiko file diakses langsung melalui URL atau diproses sebagai kode oleh server.

Jika file harus ditampilkan kepada pengguna, gunakan endpoint aplikasi yang memeriksa hak akses terlebih dahulu, lalu mengirimkan file dengan header dan nama yang sesuai. Jangan menjadikan path file sebagai input langsung dari pengguna. Gunakan ID internal yang dipetakan ke lokasi file di server.

Jika penyimpanan di dalam webroot tidak dapat dihindari, pastikan direktori tersebut tidak mengizinkan eksekusi skrip. Konfigurasi spesifik bergantung pada Apache, Nginx, hosting, dan jenis file yang diterima, sehingga pengujian perlu dilakukan di lingkungan yang menyerupai produksi.

Jangan lupa batas ukuran dan otorisasi

Batasi ukuran file di beberapa lapisan: validasi aplikasi, konfigurasi PHP seperti upload_max_filesize dan post_max_size, web server, serta layanan penyimpanan. Batas pada aplikasi membantu memberi pesan yang jelas; batas infrastruktur membantu mencegah request besar membebani server.

Upload juga harus membutuhkan otorisasi yang sesuai. Tidak semua pengguna perlu memiliki kemampuan mengunggah PDF, mengubah avatar, atau mengirim lampiran ke ruang kerja bersama. Terapkan pemeriksaan login, izin tindakan, dan bila relevan perlindungan CSRF.

Untuk sistem yang menerima banyak dokumen, pertimbangkan pemindaian antivirus atau sandbox sebelum file tersedia bagi pengguna lain. Ini bukan pengganti validasi dasar, tetapi lapisan tambahan untuk menurunkan risiko.

Bagaimana dengan WordPress?

WordPress sudah menyediakan alur upload yang melakukan pemeriksaan seperti ukuran, status upload, dan tipe file. Dokumentasi fungsi internal _wp_handle_upload() menunjukkan bahwa WordPress memvalidasi file upload dan memeriksa tipe file melalui fungsi terkait.

Masalah sering muncul ketika plugin atau tema mengganti alur standar, menambahkan ekstensi secara sembarangan, atau menerima file melalui endpoint custom tanpa pemeriksaan kemampuan pengguna. Karena itu, hindari menyalin kode upload mentah ke plugin. Gunakan API WordPress yang sesuai, batasi MIME type dengan jelas, dan periksa current_user_can() sebelum memproses file.

Rujukan teknisnya dapat dilihat pada dokumentasi WordPress tentang _wp_handle_upload(). Jika sebuah plugin meminta izin upload untuk pengguna dengan peran terlalu luas, itu juga perlu ditinjau ulang.

Checklist yang bisa dilakukan sekarang

  • Tentukan tipe file yang benar-benar dibutuhkan, lalu gunakan allowlist.
  • Validasi di server, bukan hanya melalui JavaScript.
  • Jangan percaya nama file dan header Content-Type dari pengguna.
  • Periksa ukuran, tipe berdasarkan isi, dan bila perlu tanda tangan file.
  • Ganti nama file dengan nilai acak buatan aplikasi.
  • Simpan di luar webroot atau nonaktifkan eksekusi skrip pada direktori upload.
  • Batasi siapa yang boleh mengunggah dan siapa yang boleh mengakses hasilnya.
  • Catat aktivitas upload dan siapkan proses penghapusan bila file terindikasi bermasalah.

Intinya, upload file bukan sekadar urusan memindahkan berkas dari browser ke server. Ia adalah jalur masuk data dari pihak luar, sehingga perlu diperlakukan seperti endpoint publik: dibatasi, diperiksa, dicatat, dan diuji. Dengan langkah berlapis, fitur upload tetap nyaman digunakan tanpa menjadikan server tempat menunggu masalah.

Sumber & bacaan lebih lanjut

Jelajahi juga

– Rio Yotto @rioyotto