JWT 身份篡改与签名绕过漏洞分析
整理 JWT 身份篡改、未校验签名、none 算法绕过及相关防御要点。
一、漏洞核心原理
这两类漏洞的本质都可能导致垂直越权(Vertical Privilege Escalation)。服务端接收到客户端传入的 JWT 后,如果没有安全、可靠地校验签名,攻击者就可能直接篡改 Payload 中的用户标识,例如将:
{"sub":"wiener"}
修改为:
{"sub":"administrator"}
从而尝试以管理员身份执行敏感操作。是否能够成功,取决于服务端的签名校验和授权逻辑。
二、两类典型漏洞对比
| 维度 | 未校验签名(Unverified Signature) | 算法绕过(Flawed none Algorithm) |
|---|---|---|
| 后端根源 | 未调用校验函数,直接解析并信任 Payload | 允许客户端控制 Header 中的 alg |
| 防御缺陷 | 无论签名是否存在或正确,服务端都全盘接收 | 客户端可以让服务端认为不需要签名 |
| Token 结构 | Header.Payload.Signature |
Header.Payload.,第三段为空但末尾的点仍保留 |
| 常见测试思路 | 修改 Payload 后观察服务端是否仍接受 | 将 alg 改为 none,清空第三段签名 |
三、后端代码实现对比
1. 漏洞代码示例:none 算法绕过
// 存在漏洞:信任了客户端传入的 alg 算法
function verifyToken(tokenString, secretKey) {
const parts = tokenString.split(".");
const header = JSON.parse(base64UrlDecode(parts[0]));
const payload = JSON.parse(base64UrlDecode(parts[1]));
// 致命点:客户端传入 none 后,后端直接跳过签名校验
if (header.alg === "none") {
return payload;
}
// 其他校验逻辑略
}
2. 安全代码示例:固定算法并校验签名
HS256 是 JWT 中的算法名称,而 Node.js crypto.createHmac() 使用的是哈希算法名称,因此需要进行映射:
const HASH_ALG = {
HS256: "sha256",
};
function verifyTokenSafely(tokenString, secretKey) {
const parts = tokenString.split(".");
if (parts.length !== 3) {
throw new Error("Token 结构无效");
}
const header = JSON.parse(base64UrlDecode(parts[0]));
const payload = JSON.parse(base64UrlDecode(parts[1]));
const allowedAlg = "HS256";
// 算法由服务端规定,不能由客户端决定
if (header.alg !== allowedAlg) {
throw new Error("不支持的算法");
}
const expectedSig = crypto
.createHmac(HASH_ALG[allowedAlg], secretKey)
.update(parts[0] + "." + parts[1])
.digest();
// JWT 第三段是 Base64Url 编码的签名
const receivedSig = Buffer.from(parts[2], "base64url");
// timingSafeEqual 要求两个 Buffer 长度一致
if (
receivedSig.length !== expectedSig.length ||
!crypto.timingSafeEqual(receivedSig, expectedSig)
) {
throw new Error("签名校验失败");
}
return payload;
}
实际项目中还应根据业务校验 exp、nbf、iss、aud 等声明,并在认证通过后继续检查用户是否有执行当前操作的权限。
四、防御延伸与拓展考点
CSRF 风险
如果敏感操作使用 GET 请求,且只依赖 Cookie 鉴权,攻击者可能诱导管理员访问恶意链接,借用其身份执行操作。
常见防护包括:
- 使用 Anti-CSRF Token;
- 为 Cookie 设置
SameSite=Lax或SameSite=Strict; - 状态变更操作使用 POST、PUT、PATCH 或 DELETE,而不是 GET。
XSS 与 Cookie 泄露
如果身份凭证存放在 Cookie 中,建议设置:
HttpOnly:阻止前端 JavaScript 读取 Cookie;Secure:只通过 HTTPS 发送 Cookie;- 配合 HTTPS,降低传输过程中的窃取风险。
五、实战测试避坑指南
- 不要直接使用 jwt.io 重新生成签名。RS256 等非对称算法没有私钥时无法生成合法签名;测试未校验签名时,直接在 Burp Inspector 中修改 Payload 并应用,保留原签名观察结果。
- 正确理解 JWT 解码。JWT 由三个部分组成,各部分独立进行 Base64URL 编码;不要把带有
.的整串 Token 直接放入通用 Base64 解码器。 - 保持请求路径干净。使用 Burp Repeater 重放时,清理之前遗留的表单参数,例如
&csrf=...,避免请求解析异常导致 401 或 400。
六、复盘 Tips
- JWT ≠ 加密。 Header、Payload 通常只是 Base64Url 编码,拿到 Token 基本就能看内容;签名负责防篡改,不负责保密。
decode≠verify。 能解析出 Payload 不代表 Token 合法。JWT 漏洞经常就出在“后端解析了,但没正确验证”。- 改 Payload 后签名理论上必然失效。 如果改
sub、role、userId后请求仍成功,立刻重点怀疑签名验证。 alg是客户端提供的信息,但不能由客户端决定安全策略。 服务端应该自己规定允许哪些算法,而不是 Header 说none就使用none。alg: none常见细节:Header.Payload.。 Signature 为空,但第三段的位置还在,不能随手把最后的.也删掉。- JWT 中优先关注身份和权限字段:
sub、role、userId、username、admin、scope等。不过字段名没有统一规定,关键要看后端使用哪个字段做授权。 - 先做最小修改。 怀疑
sub有问题,就先只改sub,不要 Header、Payload、Cookie 一起修改,否则成功或失败后都难以判断是哪一步造成的。 - 签名绕过 ≠ 签名破解。
none或不校验签名属于让服务器不检查;弱密钥攻击才更接近于获得生成正确签名的能力。 - 看到 RS256 别想着自己重新签。 没有私钥通常签不出合法的 RS256 Token,这时应该研究验证逻辑是否存在问题。
- 最重要的一句:JWT 能改很正常,改完服务器还信才是漏洞。
工具说明
- Burp Suite:修改 JWT、重放请求并观察认证结果。
- JWT Inspector 或类似解析工具:查看 Header 和 Payload,辅助理解 JWT 结构。
- Node.js
crypto:在服务端实现 HMAC 签名校验。