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