Keamanan Upload File di PHP Menggunakan File Signature (Magic Number)

Fitur unggah file sangat sering dipakai di aplikasi web — dari foto profil hingga dokumen. Namun, jika tidak dilindungi dengan baik, fitur ini bisa menjadi jalan masuk bagi penyerang untuk menjalankan web shell atau menempatkan skrip berbahaya di server.

Apa itu file signature?

File signature, yang sering disebut magic number, adalah urutan byte khusus di bagian awal file yang menunjukkan format file tersebut. Karena berada pada level byte, signature lebih sulit dipalsukan dibandingkan sekadar mengganti ekstensi file.

Contoh singkat signature:

  • JPEGFF D8 FF
  • PNG89 50 4E 47 0D 0A 1A 0A
  • GIF47 49 46 38
  • PDF25 50 44 46

Mengapa verifikasi signature penting?

Jika validasi hanya berdasarkan nama/ekstensi file, penyerang bisa mengganti shell.php menjadi foto.jpg dan mengunggahnya. Tanpa pemeriksaan signature, file tersebut mungkin disimpan di folder publik dan dieksekusi oleh server.

Memeriksa file signature memastikan bahwa file yang diterima benar-benar berformat yang diizinkan, sehingga file berbahaya yang menyamar akan terdeteksi dan ditolak.

Contoh implementasi di PHP

Berikut contoh sederhana yang bisa langsung dipakai. Salin potongan kode ini ke file PHP upload_secure.php.

<?php

// Daftar magic number untuk tipe file yang diizinkan
$allowed_signatures = [
    'jpg' => ["FFD8FF"], // JPEG
    'png' => ["89504E470D0A1A0A"], // PNG
    'pdf' => ["25504446"], // PDF

];

// Fungsi untuk membaca magic number file
function getFileSignature($file, $length = 8) {
    $handle = fopen($file, 'rb');
    $bytes = fread($handle, $length);
    fclose($handle);
    return strtoupper(bin2hex($bytes));

}

// Proses upload
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
        die("Error: Tidak ada file yang diupload atau terjadi kesalahan.");
    }

    $tmpFile = $_FILES['file']['tmp_name'];
    $fileName = basename($_FILES['file']['name']);
    $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));

    // Baca signature
    $fileSignature = getFileSignature($tmpFile);
    // Cek apakah ekstensi diizinkan
    if (!array_key_exists($ext, $allowed_signatures)) {
        die("Error: Ekstensi file tidak diizinkan.");
    }

    // Cek apakah signature sesuai dengan daftar yang diizinkan
    $isValid = false;
    foreach ($allowed_signatures[$ext] as $sig) {
        if (strpos($fileSignature, $sig) === 0) {
            $isValid = true;
            break;
        }
    }

    if (!$isValid) {
        die("Error: Signature file tidak valid. Upload ditolak.");
    }

    // Lokasi penyimpanan
    $uploadDir = __DIR__ . '/uploads/';
    if (!is_dir($uploadDir)) {
        mkdir($uploadDir, 0755, true);
    }

    $destPath = $uploadDir . $fileName;
    // Pindahkan file
    if (move_uploaded_file($tmpFile, $destPath)) {
        echo "File berhasil diupload dan lolos verifikasi signature.";
    } else {
        echo "Gagal memindahkan file.";
    }
}

?>

<!-- Form upload -->
<form action="" method="post" enctype="multipart/form-data">
    <label>Pilih file:</label>
    <input type="file" name="file" required>
    <button type="submit">Upload</button>
</form>

Catatan: contoh di atas menggunakan pemeriksaan signature pada beberapa byte awal. Untuk format lain atau signature yang lebih panjang, tambahkan pada array $allowed_signatures.

Jika file berbahaya sudah terlanjur masuk

Walaupun sudah melakukan verifikasi, selalu ada kemungkinan file berbahaya masuk, misalnya karena bug atau oversight. Oleh karena itu sangat penting menonaktifkan kemampuan mengeksekusi skrip dalam folder upload.

uploads/.htaccess (Apache)
# Larang eksekusi file PHP
<FilesMatch "\.(php|phtml|php3|php4|php5|php7|php8)$">
    Deny from all
</FilesMatch>

# (Opsional) Matikan handler PHP sehingga file akan dianggap data biasa
RemoveHandler .php .phtml .php3 .php4 .php5 .php7 .php8
RemoveType .php .phtml .php3 .php4 .php5 .php7 .php8

Letakkan file .htaccess ini di dalam direktori misalnya /uploads. Dengan aturan ini, server Apache akan menolak menjalankan file skrip meskipun file .php berhasil diupload. Semoga bermanfaat.