8. Validasi Upload File

PHP: Validasi Upload File

Bismillah.

Pada dua pertemuan terakhir kita telah membahas upload file pada PHP. Baik upload untuk single file maupun upload untuk multiple files.

Tapi, masih ada satu hal yang belum kita bahas.

Apa itu?

Validasi upload file. Validasi yang dimaksud adalah: kita akan memastikan bahwa file yang diupload oleh user adalah benar-benar file yang kita ijinkan. Misal ada form upload foto, maka validasinya akan memastikan bawha:

  1. File yang diupload harus benar-benar foto
  2. Dan ukuran file harus kurang dari batas yang kita tentukan

Kenapa File Upload Harus Divalidasi

Kenapa file harus divalidasi?

Pertanyaan yang menarik.

Jawabannya adalah: karena file upload ini adalah gerbang menuju celah keamanan yang sangat berbahaya terhadap server maupun aplikasi kita.

Langkah pertama yang dilakukan oleh penyerang adalah: berusaha memasukkan kode program “jahat” ke dalam server. Lalu, setelah berhasil, mereka akan berusaha untuk menjalankan kode program “jahat” tersebut [1].

Dan jika fitur upload file di web yang kita bangun tidak kita validasi, ini akan menjadi santapan empuk bagi mereka para penyerang.

Bahkan, menurut statistik yang dipaparkan oleh wordfence, serangan yang diakibatkan oleh file upload ini termasuk nomor 3 yang paling sering terjadi [2].

Lalu, apa yang harus kita lakukan?

Tenang. Kita seduh ☕ dulu agar lebih santuy 😁

Persiapan

Oke, setelah mengetahui pentingnya melakukan validasi untuk file-file yang diupload, kita akan mencoba mempraktikkannya secara langsung.

Ada beberapa jenis validasi yang bisa kita lakukan ketika proses upload file, semisal:

  1. Validasi agar file harus diisi
  2. Validasi ukuran minimal atau maksimal file yang diijinkan
  3. Validasi jenis atau tipe file

Sebelum mulai, siapkan dua buah file seperti berikut:

src/
├── index.html
└── proses.php

Isi dari file index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Validasi Upload File | Jago Ngoding</title>
</head>
<body>
  <h1>Validasi Upload File | Jago Ngoding</h1>
  <p>
    Belajar 3 jenis validasi untuk file upload.
  </p>
  <form
    action="proses.php"
    method="POST"
    enctype="multipart/form-data">

    <div>
      <label>Foto SIM</label> <br>
      <input type="file" name="foto_sim">
    </div>

    <button style="margin-top: 1rem">Upload</button>
  </form>
</body>
</html>

Kemudian, buat dua buah variabel pada file proses.php seperti berikut:

<?php

/**
 * Simpan file foto yang terupload ke variabel,
 * dan casting menjadi objek untuk memudahkan.
 */
$fileFoto = (object) @$_FILES['foto_sim'];

/**
 * Array ini digunakan untuk menyimpan setiap error yang terjadi.
 * Jika array ini kosong, berarti file yang diupload telah lolos
 * validasi.
 */
$listPesanError = [];

Penjelasan

Penggunaan statemen (object) saat pendeklarasian variabel $fileFoto dinamakan proses casting. Bertujuan agar variabel $_FILES['foto_sim'] yang berupa array akan terkonversi ke dalam bentuk objek. Pembahasannya telah kita lakukan pada tutorial Casting Variabel Pada PHP.

Sedangkan penggunaan @ sebelum mengakses array $_FILES bertujuan agar jika key foto_sim tidak ada, PHP tidak mengembalikan sebuah error, dan menganggtinya dengan nilai null.

Validasi Agar File Harus Diisi

Tetap buka file proses.php.

Kita akan mulai dengan peraturan pertama: yaitu file foto wajib diisi! Tidak boleh kosong!

Untuk memeriksanya, kita akan memanfaatkan atribut name pada variabel $fileFoto. Jika atribut tersebut bernilai null atau dianggap false, artinya file fotonya kosong alias tidak diupload.

Begini kodenya:

<?php

...

/**
 * Peraturan Pertama: file harus diisi
 */
if (!@$fileFoto->name) {
    array_push($listPesanError, "File SIM tidak boleh kosong.");
}

Validasi Untuk Ukuran Maksimal File

Untuk peraturan kedua, kita akan membuat sebuah blok if lagi.

Apa yang akan kita periksa?

Kita akan memeriksa ukuran file. Ia tidak boleh melebihi 1MB (alias 1000 * 1000 byte).


...

/**
 * Peraturan kedua: ukuran minimal file adalah 1MB.
 * 
 * Di sini kita bisa menggunakan elseif dari pada membuat
 * if baru seperti ini.
 */
if ($fileFoto->size > 1000 * 1000) {
    array_push($listPesanError, "Ukuran maksimal file adalah 1MB.");
}

Di sini kita membuat blok if baru.

Itu artinya: jika kondisi if pertama terpenuhi, if yang kedua akan tetap dieksekusi.

Berbeda halnya jika kita membuat blok kode elseif, maka jika kondisi yang pertama terpenuhi, maka kondisi kedua tidak akan dieksekusi.

Tampilkan Pesan Error dan Hentikan Program

Oke. Jangan banyak-banyak. Kita coba 2 rule terlebih dahulu.

Sekarang, kita akan memeriksa variabel $listPesanError apakah ia kosong atau tidak.

Kalau kosong artinya file yang kita upload sudah benar, kalau tidak kosong, berarti ada yang tidak memenuhi peraturan.

Tambahkan kode program di bawah ini:

<?php

...

/**
 * Tampilkan pesan error dan hentikan program jika validasi tidak lolos.
 */
if ($listPesanError) {
    foreach ($listPesanError as $pesanError) {
        echo "<strong style='color: red'>{$pesanError}</strong><br>";
    }

    echo "<a href='index.html'>Kembali</a>";

    # hentikan program
    die();
}

Nah, sekarang saatnya test drive.

Kita coba dua skenario:

  1. Klik tombol Upload tanpa memilih file.
  2. Upload file dengan ukuran lebih dari 1MB.

Untuk skenario pertama, kita akan dapat pesan error sebagai berikut:

Dan untuk skenario kedua, kita akan dapat pesan error seperti ini:

Validasi Jenis File Berdasarkan Ekstensi (Tidak Direkomendasikan)

Oke. Kita rehat dulu sejenak.

Ambil secangkir ☕ yang sudah kita seduh, lalu kita nikmati bersama-sama 😁

Masih mau lanjut?

Oke.

Sekarang kita lanjutkan lagi untuk rule ketiga, yaitu pemeriksaan tipe file.

Ini adalah pemeriksaan yang sangat penting.

Ada dua metode untuk melakukan pemeriksaan tipe file, kita bisa memeriksa berdasarakan ekstensi nama file, dan kita juga bisa memeriksa dari mime type.

Cara yang pertama tidak direkomendasikan karena ekstensi file bisa saja menipu. Kita bisa dengan mudah membuat file malware, lalu memberinya ekstensi .png atau .jpg dan sistem akan mengira bahwa itu adalah file gambar betulan.

Untuk memeriksa tipe file berdasarkan ekstensi, kita bisa mendapatkannya secara langsung dari $_FILES['nama_input']['type']. Contoh tipe-tipe file:

  • image/jpeg
  • image/png
  • video/mp4
  • video/webm
  • dan lain-lain.

Untuk memeriksanya, kita bisa melakukannya sebagai berikut:

<?php
... 

/**
 * Peraturan ketiga: periksa file berdasarkan ekstensi
 */
$ekstensiYangDibolehkan = [
    'image/png',
    'image/jpg',
    'image/jpeg',
    'image/webp'
];

if (!in_array(@$fileFoto->type, $ekstensiYangDibolehkan)) {
    array_push($listPesanError, "Ekstensi file tidak diijinkan.");
}

...

Tes Drive

Saya memiliki file video dengan nama video.webm dan ukuran 1.1MB.

File tersebut tidak memenuhi 2 peraturan:

  • ukuran maksimum 1MB.
  • ekstensi file tidak diijinkan.

Sehingga, jika saya upload, saya dapat pesan:

Rekayasa Ekstensi File

Hmmm…

Karena kita hanya memeriksa tipe file dari nama ekstensinya, itu artinya kita bisa memanipulasinya.

Dengan linux, kita bisa merename ekstensi file secara langsung seperti saat kita mengedit nama.

Kita coba rename video.webm menjadi video.jpg.

Lalu upload ulang.

Apa yang terjadi?

File video tadi lolos pemeriksaan!

Bayangkan jika yang diupload tadi aslinya adalah file php yang berisi malware? Server kita bisa diambil alih oleh penyerang.

Validasi Jenis File Berdasarakan Mime Content Type (Direkomendasikan)

Tenang saja. Untuk memeriksa tipe file yang lebih akurat, kita bisa memeriksanya berdasarkan mime content type.

Caranya gimana?

Kita bisa melakukannya dengan memanggil fungsi mime_content_type(path_file) dan memasukkan $fileFoto->tmp_name sebagai parameternya.

Untuk mencobanya, tambahkan kode program berikut setelah peraturan ketiga:

<?php

...

/**
 * Peraturan keempat: periksa tipe file berdasarkan mime content type
 */
if (!in_array(mime_content_type($fileFoto->tmp_name), $ekstensiYangDibolehkan)) {
    array_push($listPesanError, "Tipe file tidak diijinkan.");
}

...

Nah. Sekarang, file video.jpg saya tetap dianggap sebagai file video meskipun ekstensi pada nama file-nya adalah jpg 😁

Upload File Jika Lolos

Jika tidak ada pesan error apa pun, kita bisa mulai melakukan upload file seperti biasanya.

Kode Program Lengkap

Untuk kode program lengkap (ditambah dengan upload file jika berhasil), kalian bisa mengaksesnya pada repository php-web-dinamis di github.

Jangan lupa kasih ⭐ ya!

Pertemuan Selanjutnya

Insyaallah pada pertemuan berikutnya kita akan membahas session pada PHP.

Stay tune!

Terima kasih banyak.

Referensi

[1] https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload - Diakses pada tanggal 15 Februari 2021
[2] https://www.wordfence.com/learn/how-to-prevent-file-upload-vulnerabilities/ - Diakses pada tanggal 15 Februari 2021