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.
- JPEG —
FF D8 FF - PNG —
89 50 4E 47 0D 0A 1A 0A - GIF —
47 49 46 38 - PDF —
25 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>
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.
# 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.