跳至主要內容
Skip to content

檔案上傳系統設計 (3) —— 安全性防禦與驗證

如果你的網站允許使用者上傳任何東西,且沒有做足夠的驗證,那麼你的伺服器就相當於對全世界開放了「寫入權限」。

一位合格的後端工程師,不僅要讓功能跑通,更要防止駭客利用檔案上傳漏洞(File Upload Vulnerability)把你的伺服器變成他的礦機或跳板。這一篇,我們來疊疊層防禦。


一、 不要只相信副檔名:理解 Magic Number

駭客最常用的手法就是:寫一個 cmd.php(內容是執行惡意指令),然後把它改名為 cute_cat.jpg 上傳。

如果你的後端只檢查 .jpg 尾綴,你就中招了。一旦這個檔案在伺服器上被執行,你的系統權限就可能被奪走。

解決方案:檢查檔案識別碼 (Magic Numbers)

大多數檔案格式在二進制數據的最開頭幾個 Bytes,都有特定的標記。

  • JPEG: 總是 FF D8 FF 開頭。
  • PNG: 總是 89 50 4E 47 開頭。
  • PDF: 總是 25 50 44 46 開頭。

Node.js 實作範例

我們可以使用 file-type 套件,或者手動讀取 Buffer 的前幾個位元組:

javascript
const fs = require("fs");

function checkMagicNumber(filePath) {
  const buffer = Buffer.alloc(4); // 讀取前 4 個位元組
  const fd = fs.openSync(filePath, "r");
  fs.readSync(fd, buffer, 0, 4, 0);
  fs.closeSync(fd);

  const hex = buffer.toString("hex").toUpperCase();

  if (hex === "89504E47") return "image/png";
  if (hex.startsWith("FFD8FF")) return "image/jpeg";
  if (hex === "25504446") return "application/pdf";

  return "unknown";
}

二、 限制防護:防止資源耗盡 (DoS)

如果我不檢查檔案大小,駭客可以直接傳送一個 100GB 的「垃圾檔案」給你的 Server。

  • 後場後果:你的硬碟空間會瞬間爆滿,導致資料庫和其他服務因為無法寫入 Log 而報廢。

防禦建議:

  1. 設定 Payload Limit:在 Web 框架(如 Express, Koa)或 Nginx 層級限制請求的最大 Body 大小。
  2. Multer 限制
    javascript
    const upload = multer({
      limits: { fileSize: 5 * 1024 * 1024 }, // 限制 5MB
    });

三、 目錄安全:移除「執行權限」

假設駭客費盡心思把一個惡意腳本傳到了你的 /uploads 資料夾,只要他 不能執行 它,那這個腳本就只是一堆廢物數據。

在 Linux 系統中,你應該確保上傳目錄不具備執行腳本的能力。

指令操作:

bash
# 讓 uploads 資料夾下的檔案只有讀取權限,沒有執行權限
chmod -R 644 /var/www/my-app/uploads

更進階的做法是,將儲存檔案的伺服器與運行代碼的伺服器 物理隔離(例如使用 S3 雲端儲存)。


四、 靜態資源分離:Nginx 的角色

新手常會直接用 Express 寫 app.use('/uploads', express.static('uploads')) 來展示圖片。

這在生產環境並非最佳實踐。

  1. 效能問題:Node.js 是單執行緒,用它來處理大量檔案的 IO 會拖慢你的商務邏輯查詢。
  2. 安全性:Nginx 作為專門處理靜態資源的伺服器,經過高度優化且比應用軟體層更難被攻破。

TIP

推薦架構: 前端 -> Nginx (攔截 /uploads 請求直接由硬碟讀取) -> Node.js (只處理 API 邏輯)。


總結

安全性沒有銀彈,只有不斷堆疊的防禦:

  1. Magic Number:檢查檔案的「真身」,不信副檔名。
  2. 大小限制:保護硬碟與頻寬。
  3. 路徑安全性:不使用原始原始檔名(這在上篇已強調過)。
  4. 許可權控管:禁止上傳目錄執行任何腳本。

下一篇,我們將聊聊「現代化」的作法:效能優化與大檔案處理,包含縮圖自動化與斷點續傳。


️ 進階挑戰

如果你正在做一個允許上傳頭像的功能,除了檢查檔案類型外,是否應該強制將上傳的圖片透過程式碼(如 Sharp)重新處理一次?這對安全性有什麼幫助?


延伸閱讀與資源

← 上一章:資料庫設計與目錄結構 | 返回專案首頁 | 下一章:效能優化與大檔案處理