React RCE 事件留下的教訓:為何現在需要 HMAC 簽名、金鑰輪替與零信任

RCE 漏洞所揭示的:"一旦相信資料就結束了"



最近在 React Server Components/Next.js 中發生的 RCE 漏洞(CVE-2025-55182)不應該只被視為「React 被突破」的新聞。 這件事傳遞的核心訊息更具原則性。

「只要一次相信客戶端送來的資料,終究會被突破。」

這次漏洞的本質在於,伺服器在沒有充分驗證的情況下,直接使用客戶端送來的 Flight 協議資料(metadata) 進行模組載入與物件存取。

  • 「這個值由 React 自動安全處理。」
  • 「我們沒有直接使用的 API,應該沒問題。」

這種隱式信任累積到最後,導致 在未經預先認證的情況下就能執行 RCE 的點。

現在我們需要改變問題的角度。

  • 「React 為什麼被突破?」
  • 「我們現在在沒有任何簽名或驗證的情況下,信任哪些資料?」

在尋找答案的過程中,自然會出現 HMAC 簽名零信任 這兩個概念。


為什麼 HMAC 簽名很重要:"這資料真的由我們的伺服器產生嗎?"

實際系統往往比想像中更依賴「伺服器之間的信任」。

  • 前端 ↔ 後端
  • 微服務 A ↔ B
  • 後端 ↔ 後端(非同步工作、佇列、Webhook、內部 API 等)

在這些交互中,資料往往被假設為安全。

「這個 URL 沒有對外公開,只有內部系統會呼叫。」

「這個 token 格式只有我們的服務知道,安全。」

但攻擊者總是從打破這些「假設」開始。

此時,HMAC(如 HMAC-SHA256) 的存在是為了回答一個根本問題。

「這個請求/訊息,真的由「我們擁有秘密金鑰」的人產生嗎?」

更具體來說:

  • 只有知道 HMAC 金鑰的一方才能產生 有效簽名(signature)
  • 伺服器接收 payload + signature 後,
  • 可以確認「簽名是否正確」
  • 確認「中間是否被篡改」

沒有 HMAC 簽名的世界

如果 REST/Webhook/內部 API 沒有 HMAC 簽名,會發生什麼?

  • 攻擊者假設已經知道 URL 結構與參數。
  • 他們可以隨意造請求,並讓伺服器誤以為是「自然的內部請求」。

例如:

POST /internal/run-action
Content-Type: application/json

{
  "action": "promoteUser",
  "userId": 123
}

即使原本是「只在後台呼叫的內部端點」,只要

  • 網路邊界被突破,
  • 內部代理存在漏洞,
  • CI/CD、日誌、範例程式碼、文件被外部曝光,

攻擊者就能假裝內部系統呼叫此端點。

此時,如果有 HMAC 簽名,情況就完全不同。

  • 對整個請求體加上 HMAC 簽名,
  • 伺服器每次都驗證 signature

「即使知道 URL、參數,沒有金鑰也無法產生有效請求」


範例:用 HMAC 簽名保護內部動作



以下示範一個簡單範例(TypeScript/Node.js 風格的偽程式碼)。

1. 伺服器擁有共享秘密金鑰

const HMAC_SECRET = process.env.HMAC_SECRET!; // 從 .env 或 Secret Manager 注入

2. 客戶端(或內部服務)產生請求時

import crypto from 'crypto';

function signPayload(payload: object): string {
  const json = JSON.stringify(payload);
  return crypto
    .createHmac('sha256', HMAC_SECRET)
    .update(json)
    .digest('hex');
}

const payload = {
  action: 'promoteUser',
  userId: 123,
};

const signature = signPayload(payload);

// 傳送
fetch('/internal/run-action', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Signature': signature,
  },
  body: JSON.stringify(payload),
});

3. 伺服器驗證時

function verifySignature(payload: any, signature: string): boolean {
  const json = JSON.stringify(payload);
  const expected = crypto
    .createHmac('sha256', HMAC_SECRET)
    .update(json)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expected, 'hex'),
  );
}

app.post('/internal/run-action', (req, res) => {
  const signature = req.headers['x-signature'];

  if (!signature || !verifySignature(req.body, String(signature))) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // 到這裡代表:
  // 1) payload 未被篡改
  // 2) 由知道 HMAC_SECRET 的主體產生
  handleInternalAction(req.body);
});

這個結構即是「這資料真的由我們發送」的最基本證明模式。


若有多位管理員(運維/開發者):HMAC 金鑰輪替是「非選擇而是必須」

HMAC 金鑰的另一個實際問題是「人」。

  • 運維、SRE、後端開發者、外包人員、實習生…
  • 不是每個人都能存取 .env,但
  • GitOps、CI/CD、文件、Slack、Notion 等任何地方都有可能洩露金鑰。

關鍵在於:

「金鑰可能洩露」這個假設必須成為設計的前提。

也就是說,「金鑰最終會洩露」,設計時必須預先考慮。

因此,HMAC 金鑰輪替(rotation) 成為必須。

為什麼需要輪替

  1. 人員變動 * 離職、團隊調動、外包結束等。 * 過去能看到金鑰的人,現在不應再能看到。

  2. 無法完全追蹤金鑰洩露 * 可能在 Slack DM、本地筆記、截圖、備忘錄中。 * 「從未洩露」的保證幾乎不可能。

  3. 事故後仍需可恢復 * 若某時點之前的金鑰洩露, * 必須能宣告「從此以後只接受新金鑰簽名的請求」。

實務中的 HMAC 金鑰輪替模式

對於多位管理員的組織,通常建議以下模式。

  1. 金鑰版本管理 * 例如 HMAC_SECRET_V1HMAC_SECRET_V2, * 在請求標頭或負載中傳送 kid(key id)。

  2. 伺服器同時持有多個金鑰,驗證時按版本處理 * 例如:先驗證 V1、V2, * 一段時間後棄用 V1,只允許 V2。

  3. 輪替流程文件化 * 「產生新金鑰 → 部署 → 兩邊(產生者/驗證者)都切換到新金鑰 → 刪除舊金鑰」 * 用 checklist + runbook 形式,確保人員變動時仍能跟隨。

  4. 權限分離 * 產生/管理金鑰的人與 * 編寫應用程式碼的人分離,若可能的話。

當 HMAC 金鑰成為「一旦設定就不變」的值時, 金鑰洩露即等於整個系統崩潰

金鑰輪替就是把這個風險隨時間限制的技術手段。


零信任:"信任不是結構,而是每一次請求都要建立"

最後,React RCE 事件再次提醒我們的安全基礎原則。

安全的基礎是零信任(Zero Trust)。

零信任的核心非常簡單。

  • 「因為是內部所以沒問題」
  • 「前端只用的值所以安全」
  • 「我們自己的框架所以自動保護」

這些說法必須根本禁止

相反,我們應該問:

  • 「這個輸入,是否在假設可能是惡意的情況下設計?」
  • 「這個請求真的來自我們預期的主體嗎?」
  • 「這個資料在傳輸過程中是否被篡改?」
  • 「如果金鑰洩露,影響範圍是什麼,如何回收/恢復?」

React RCE 事件也可以從這個框架來看。

  • RSC Flight 資料被「React 會給的」這種信任處理。
  • 結果,客戶端可操縱的序列化資料直接影響伺服器模組載入路徑

從零信任角度看,這種結構本應一開始就被視為風險訊號。

「客戶端可操縱的序列化資料影響伺服器執行路徑 = 必定風險」

而在這種情況下,最先想到的防禦措施就是:

  • HMAC 簽名(完整性與來源驗證)
  • 金鑰輪替(假設金鑰洩露時限制損失)

總結:"不要信任,讓它證明自己"

本文想表達的核心訊息只有三點。

  1. 基本上不要信任資料 * 即使是「框架內部使用的資料」,只要經過網路, * 也可能成為攻擊面。

  2. HMAC 簽名是「這資料真的來自我們」的基本工具 * REST API、Webhook、內部 RPC、非同步訊息等 * 所有伺服器間通訊都應考慮 HMAC。 * 若管理人員多,金鑰輪替必須成為流程

  3. 安全的基礎是零信任 * 「內部就好」的說法即為風險訊號。 * 信任必須在每一次請求、每一筆資料中透過驗證建立。

React RCE 事件提醒我們,在前後端邊界模糊的時代, 我們需要採取的態度。

「不要信任,驗證。將驗證寫進程式、流程、金鑰管理政策。」

第一步就是 HMAC 簽名與金鑰輪替, 其上構築的哲學即是 零信任

image