文件上傳是 Web 應用高危功能區。本系列首篇分析**「使用用戶原始文件名保存」**導致的經典漏洞:攻擊者構造惡意文件名(含路徑遍歷、截斷、雙重擴展名),繞過後綴檢查,實現任意文件寫入、甚至 Getshell。
<?php
if (isset($_FILES['img'])) {
$file = save_file($_FILES['img']);
if ($file === false) exit('上傳失敗!');
echo "上傳成功!$file";
}
function check_file($img) {
if ($img['error'] > 0) return false;
$filename = $img['name']; // 直接取用戶文件名
$len = strrpos($filename, '.');
if ($len === false) return false;
$ext = strtolower(substr($filename, $len + 1));
return in_array($ext, ['jpg', 'jpeg', 'png']); // 僅檢查後綴
}
function save_file($img) {
if (!check_file($img)) return false;
$filename = $img['name']; // 再次直接使用
$newfile = "upload/" . $filename; // 拼接路徑
if (!move_uploaded_file($img['tmp_name'], $newfile)) return false;
return $newfile;
}
?>
構造文件名:`shell.php.jpg` 或 `shell.php%00.jpg`
現狀:PHP 5.3.4+ 核心修復了 null byte 截斷,但老版本、ASP/JSP 仍大量存在。
構造文件名:`../../../var/www/html/shell.php.jpg`
<?php
function secure_upload($file, $allowedExt = ['jpg', 'png', 'gif'], $saveDir = '/var/www/uploads/') {
// 1. 基礎校驗
if ($file['error'] !== UPLOAD_ERR_OK) return ['success' => false, 'msg' => '上傳錯誤'];
if ($file['size'] > 5 * 1024 * 1024) return ['success' => false, 'msg' => '文件過大'];
// 2. 後綴白名單 (從原始名提取,僅作參考)
$origName = basename($file['name']); // 防路徑遍歷
$ext = strtolower(pathinfo($origName, PATHINFO_EXTENSION));
if (!in_array($ext, $allowedExt)) return ['success' => false, 'msg' => '禁止的文件類型'];
// 3. 內容檢測 (MIME + 魔數) - 關鍵!
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
$allowedMime = ['image/jpeg', 'image/png', 'image/gif'];
if (!in_array($mime, $allowedMime)) return ['success' => false, 'msg' => '文件內容類型不合法'];
// 4. 隨機文件名 + 正確後綴
$newName = bin2hex(random_bytes(16)) . '.' . $ext; // 如 a3f2...jpg
$destPath = $saveDir . $newName;
// 5. 保存目錄權限:不可執行 (Nginx/Apache 配置 location ~ \.php$ { deny all; })
if (!move_uploaded_file($file['tmp_name'], $destPath)) {
return ['success' => false, 'msg' => '保存失敗'];
}
// 6. 可選:二次渲染/壓縮 (GD/Imagick) 徹底破壞圖片馬
// $img = imagecreatefromjpeg($destPath); imagejpeg($img, $destPath);
return ['success' => true, 'path' => $destPath, 'url' => '/uploads/' . $newName];
}
?>
| 層級 | 措施 | 作用 |
|---|---|---|
| 應用層 | 隨機文件名 + 白名單後綴 + MIME/魔數驗證 | 源頭阻斷惡意文件 |
| 應用層 | 圖片二次渲染 (GD重寫) | 破壞隱藏在圖片數據段的 Shell 代碼 |
| 存儲層 | 上傳目錄獨立分區/掛載 `noexec,nodev,nosuid` | 系統級禁止執行 |
| Web服務器 | 上傳目錄禁用腳本解析 (Nginx `location /uploads { deny all; }` 或 Apache `SetHandler default-handler`) | 即使寫入 PHP 也無法執行 |
| 網絡層 | 上傳域名分離 (upload.example.com),Cookie 隔離 | 縮小 XSS/CSRF 影響面 |