資安雷達 2026 年 10 月 9 日

2026-10-09 — fast-jwt 補丁不完整,RSA 公鑰前綴再現 HS256 混淆

primary=https://github.com/advisories/GHSA-ww5h-9m49-7xx4 primary=https://github.com/nearform/fast-jwt/commit/d96bbc6c5336055a6dbfa318fdbab01197264cfa primary=https://github.com/nearform/fast-jwt/releases/tag/v6.3.0 primary=https://github.com/advisories/GHSA-mvf2-f6gm-w987

fast-jwt 補丁不完整,RSA 公鑰前綴再現 HS256 混淆

GitHub Advisory Database · 2026-10-08

fast-jwt 在 v6.2.0 為 CVE-2026-34950 加上的 key.trim() 只擋得住空白字元,只要 PEM 公鑰前面多一個非空白位元組,RSA 公鑰就會被當成 HMAC 密鑰,RSA 到 HS256 的演算法混淆攻擊原樣重現。

這則編號為 GHSA-ww5h-9m49-7xx4(CVE-2026-107722)的公告評為 critical、CVSS 3.1 為 9.8,歸類 CWE-347。受影響範圍為 fast-jwt >= 6.2.0, <= 6.2.4,6.3.0 修復。

背景:同一個洞第三次被補

JWT 演算法混淆的核心是:驗證端若依「金鑰長什麼樣」來決定可用演算法,攻擊者就能拿公開的 RSA 公鑰當 HMAC 密鑰簽出 HS256 token。fast-jwt 用 src/crypto.js 內的 PEM 比對器判斷金鑰是不是公鑰,比對不到 PEM 標頭,就當成 HMAC 密鑰。

這個洞已經被補過兩次。CVE-2023-48223 的修補(v3.3.2)把原本的 .includes() 改成 ^ 錨定的正規表示式;GHSA-mvf2-f6gm-w987(CVE-2026-34950,CVSS 9.1)指出前導空白就能繞過它,並在 6.2.0 修復。本次公告則指出,第二次修補只處理了「空白」這一種觸發,而不是整類問題。

漏洞機制

第二次修補在偵測前加了 key.trim(),但 trim() 只剝除 ECMAScript 定義的空白字元。之後的比對器仍然要求標頭落在位置 0:

const publicKeyPemMatcher = /^-----BEGIN(?: (RSA))? PUBLIC KEY-----/
function performDetectPublicKeyAlgorithms(key) {
  const trimmedKey = key.trim()      // 只擋空白
  if (publicKeyPemMatcher.test(trimmedKey)) { /* RSA / EC 路徑 */ }
  // 其餘全部落到 HMAC 路徑
}

公告列出的實測繞過前綴包含:# 註解行、NUL、ESC、DEL、零寬空白 U+200B、HTTP/1.1 200 OK 標頭與 PGP 包裝文字,甚至單一個 .。唯一被 trim() 正確剝除的是 BOM(U+FEFF)。此時 createVerifier({ key }) 把整串含前綴的 PEM 當成 HMAC 密鑰,攻擊者用同一串字串簽 HS256 token 即可通過驗證。

公告的 PoC 只需十行,在 fast-jwt@6.2.2 上以 # some comment\n 加 PEM 作為 key,偽造的 { admin: true, sub: 'attacker' } 被驗證通過。

受影響版本與條件

攻擊要成立,需要同時滿足兩點:驗證端沒有設定 algorithms 白名單,且金鑰的實際內容帶有非空白前綴。後者在實務上不罕見,例如資料庫欄位編碼損壞、YAML 設定夾帶行內註解、從格式化文件複製貼上。

驗證端設定是否受影響
createVerifier({ key })(無白名單)受影響
createVerifier({ key: asyncCallback })受影響
createVerifier({ key, algorithms: ['RS256'] })不受影響
createVerifier({ key, algorithms: ['HS256'] })受影響(攻擊者符合白名單)

公告同時指出 fast-jwt 是 @fastify/jwt 的預設 JWT 後端,因此多數以 Fastify 建的 Node.js API 都在盤點範圍內。

修補與緩解

PR #632(commit d96bbc6)不再把標頭釘在位置 0,而是新增 locatePem(),用 /-----BEGIN [A-Z0-9 ]+?-----/ 找出第一個 PEM 標頭並從該處切片,簽署端與驗證端的偵測器共用這一個函式。完全找不到標頭才視為 HMAC 密鑰;找到未知標頭則拒絕,不再退回當密鑰用。commit 訊息明講,兩個偵測器各自為政正是這條 CVE 譜系一再補不完整的原因。

  • 升級:把 fast-jwt 升到 6.3.0 以上,同時檢查 @fastify/jwt 鎖定的 fast-jwt 版本(npm ls fast-jwt)。
  • 白名單:所有 createVerifier 都明確寫出 algorithms,RSA 公鑰只允許 RS256 等非對稱演算法。
  • 金鑰來源:檢查載入公鑰的設定或資料庫欄位,確認內容以 -----BEGIN 開頭,沒有註解或不可見字元。

影響範圍

最該立刻處理的是:用 @fastify/jwt 或直接用 fast-jwt 6.2.x 驗 RS256 token、又沒設 algorithms 的服務,以及從 YAML、環境變數、資料庫讀公鑰的部署。已經設了 RS256 白名單的服務不受此洞影響,但仍建議升級,因為 release page 同時列有其他修正。v6.3.0 release notes 顯示該版於 2026-07-28 發布,也就是說修補早於這則公告一段時間,只是公告本身在 2026-10-08 才發布。

公告建議的測試補強值得照抄到自己的 JWT 封裝:除了空白,還要覆蓋控制字元、零寬 Unicode、註解前綴與 HTTP/PGP 包裝。

原始來源:GHSA-ww5h-9m49-7xx4、fast-jwt commit d96bbc6、fast-jwt v6.3.0、GHSA-mvf2-f6gm-w987


End of article
0
Would love your thoughts, please comment.x
()
x