方法一:副檔名白名單(最簡單、最不可靠)
function getTypeByExt(string $filename): string {
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$allowed = ['jpg','jpeg','png','gif','bmp','webp','pdf','doc','docx'];
return in_array($ext, $allowed, true) ? $ext : 'invalid';
}
方法二:MIME 類型偵測 (finfo / Fileinfo 擴展)
function getMimeType(string $tmpPath): string {
$finfo = new finfo(FILEINFO_MIME_TYPE);
return $finfo->file($tmpPath); // 例: image/jpeg, application/pdf
}
// 對照表驗證
$mimeMap = [
'jpg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',
'pdf' => 'application/pdf',
];
if (($mimeMap[$ext] ?? null) !== $mime) {
throw new Exception('檔案類型不符');
}
方法三:讀取檔頭魔數 (Bytes Signature) - 最底層驗證
function getTypeByMagic(string $path): string {
$fp = fopen($path, 'rb');
$header = fread($fp, 8); // 讀前 8 位元組足夠大多數格式
fclose($fp);
$signatures = [
"\xFF\xD8\xFF" => 'jpg',
"\x89\x50\x4E\x47" => 'png', // PNG
"\x47\x49\x46\x38" => 'gif', // GIF87a/89a
"\x42\x4D" => 'bmp',
"\x25\x50\x44\x46" => 'pdf', // %PDF
"\x50\x4B\x03\x04" => 'zip', // ZIP 基礎 (docx/xlsx/epub 等)
"\x7F\x45\x4C\x46" => 'elf', // Linux 執行檔
];
foreach ($signatures as $sig => $type) {
if (str_starts_with($header, $sig)) return $type;
}
return 'unknown';
}
綜合驗證策略(推薦)
- 副檔名白名單:快速攔截明顯非法副檔名。
- finode MIME 驗證:核心防禦,確認檔案內容與副檔名一致。
- 圖片重繪:
imagecreatefromstring() → imagejpeg() 覆寫暫存檔,徹底剝離夾帶腳本/EXIF。
- 儲存重命名:
bin2hex(random_bytes(16)) . '.' . $ext,防路徑遍歷、覆蓋、執行。
- 儲存目錄禁用腳本執行:Nginx
location ~ \.php$ { deny all; }。
常見檔頭魔數速查
| 格式 | Hex 簽名 | ASCII |
| JPEG | FF D8 FF | ÿØÿ |
| PNG | 89 50 4E 47 0D 0A 1A 0A | ‰PNG... |
| GIF | 47 49 46 38 37/39 61 | GIF87a/89a |
| PDF | 25 50 44 46 | %PDF |
| ZIP/DOCX/XLSX | 50 4B 03 04 | PK.. |
| RAR | 52 61 72 21 1A 07 | Rar!.. |
| BMP | 42 4D | BM |
| EXE/DLL | 4D 5A | MZ |