is_uploaded_file() 是 PHP 內建的安全函數,用於驗證檔案是否真正是透過 HTTP POST 上傳的。許多開發者僅知其用法,卻不解其背後防禦的具體攻擊向量。本文深入剖析 /etc/passwd 範例的含義、偽造上傳攻擊原理,並對比兩種上傳代碼的安全性差異。
/etc/passwd?/etc/passwd 是 Linux/Unix 系統儲存用戶帳號資訊的關鍵系統檔案(含用戶名、UID、GID、家目錄、Shell 等)。雖然現代系統密碼雜湊存放於 /etc/shadow,但 /etc/passwd 仍屬敏感資訊,不可公開讀取。
假設上傳處理代碼未使用 is_uploaded_file(),直接信任 $_FILES['file']['tmp_name']:
<?php
// 危險代碼:未驗證來源
$dest = "./uploads/" . $_FILES['file']['name'];
move_uploaded_file($_FILES['file']['tmp_name'], $dest);
惡意用戶可構造請求(透過代理工具、curl、瀏覽器擴充套件修改表單資料):
$_FILES['file']['tmp_name'] = /etc/passwd(或 ../../etc/passwd)$_FILES['file']['name'] = passwd.txt若 PHP 進程權限足夠(如以 root 執行、或開啟 open_basedir 限制不當),move_uploaded_file() 可能將系統敏感檔案「移動」到 Web 目錄下,導致洩露。甚至若配合 copy()、file_get_contents() 等函數,可直接讀取任意可讀檔案。
is_uploaded_file() 如何防禦?PHP 內部維護一份本次請求真正透過 HTTP multipart/form-data 上傳的暫存檔路徑清單。is_uploaded_file($path) 僅檢查 $path 是否在該清單中:
因此,任何處理上傳檔案的代碼,必須先通過 is_uploaded_file() 驗證。
copy() + 基於原始檔名$classnotes = "./" . $_FILES['classnotes']['name'];
copy($_FILES['classnotes']['tmp_name'], "./classnotes/" . basename($classnotes));
風險:
tmp_name 來源,易受偽造路徑攻擊。name,可能包含路徑遍歷序列(../../../etc/passwd),導致任意寫入。copy() 不會檢查來源是否為上傳檔案,且不會自動清理暫存檔。is_uploaded_file() + move_uploaded_file()if (is_uploaded_file($_FILES['classnotes']['tmp_name'])) {
$safe_name = basename($_FILES['classnotes']['name']); // 僅取檔名部分
$dest = "./classnotes/" . $safe_name;
// 進一步建議:重命名為隨機名稱、驗證副檔名/MIME、限制大小
move_uploaded_file($_FILES['classnotes']['tmp_name'], $dest);
} else {
echo "上傳失敗:非法檔案來源";
}
優勢:
is_uploaded_file() 確保來源合法。move_uploaded_file() 同樣內建來源驗證,且處理完成後自動刪除暫存檔(節省磁碟空間、避免殘留)。basename() 移除目錄成分,阻斷路徑遍歷(但仍建議自行生成隨機檔名)。is_uploaded_file() / move_uploaded_file()。uniqid()、hash()、UUID 等生成隨機檔名,儲存原始檔名於資料庫。['jpg','png','gif','pdf']),結合 finfo_file() 檢測真實 MIME。php.ini upload_max_filesize、post_max_size + 代碼端二次檢查。php_flag engine off / Nginx location ~ \.php$ { deny all; }。/etc/passwd 範例揭示的核心風險是「信任用戶端提供的檔案路徑」。is_uploaded_file() 是 PHP 提供的第一道防線,配合 move_uploaded_file()、隨機重命名、MIME 驗證、目錄隔離等纵深防禦措施,才能構建安全可靠的檔案上傳功能。切記:永遠不要信任 $_FILES 中的任何客戶端可控欄位。