JWT 身份篡改与签名绕过漏洞分析

整理 JWT 身份篡改、未校验签名、none 算法绕过及相关防御要点。

  • #JWT
  • #身份认证
  • #签名绕过
  • #越权
  • #Web安全

一、漏洞核心原理

这两类漏洞的本质都可能导致垂直越权(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。

如果身份凭证存放在 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

  1. JWT ≠ 加密。 Header、Payload 通常只是 Base64Url 编码,拿到 Token 基本就能看内容;签名负责防篡改,不负责保密。
  2. decode ≠ verify。 能解析出 Payload 不代表 Token 合法。JWT 漏洞经常就出在“后端解析了,但没正确验证”。
  3. 改 Payload 后签名理论上必然失效。 如果改 sub、role、userId 后请求仍成功,立刻重点怀疑签名验证。
  4. alg 是客户端提供的信息,但不能由客户端决定安全策略。 服务端应该自己规定允许哪些算法,而不是 Header 说 none 就使用 none。
  5. alg: none 常见细节:Header.Payload.。 Signature 为空,但第三段的位置还在,不能随手把最后的 . 也删掉。
  6. JWT 中优先关注身份和权限字段: sub、role、userId、username、admin、scope 等。不过字段名没有统一规定,关键要看后端使用哪个字段做授权。
  7. 先做最小修改。 怀疑 sub 有问题,就先只改 sub,不要 Header、Payload、Cookie 一起修改,否则成功或失败后都难以判断是哪一步造成的。
  8. 签名绕过 ≠ 签名破解。 none 或不校验签名属于让服务器不检查;弱密钥攻击才更接近于获得生成正确签名的能力。
  9. 看到 RS256 别想着自己重新签。 没有私钥通常签不出合法的 RS256 Token,这时应该研究验证逻辑是否存在问题。
  10. 最重要的一句:JWT 能改很正常,改完服务器还信才是漏洞。

工具说明

  • Burp Suite:修改 JWT、重放请求并观察认证结果。
  • JWT Inspector 或类似解析工具:查看 Header 和 Payload,辅助理解 JWT 结构。
  • Node.js crypto:在服务端实现 HMAC 签名校验。