JWT令牌伪造检测与后端安全合规错误码断言工程案例 - 第4节

4.1 攻击原理:JWT 签名验证的致命缺陷

alg=none 攻击是 JWT 安全中最基础、也最容易被忽视的漏洞。其核心原理在于:JWT 标准(RFC 7519)允许将头部中的 alg(算法)字段设置为 none,表示令牌不使用任何签名算法。当服务端的 JWT 解析库未对 alg 字段进行严格校验时,攻击者可以构造一个不含签名的 JWT,绕过签名验证逻辑,直接伪造任意用户身份。

攻击本质:服务端信任了客户端提供的算法声明,而非强制使用服务端预定义的签名算法。攻击者通过篡改 JWT 头部,将签名算法降级为 none,使签名验证步骤被跳过。

攻击流程可概括为以下步骤:

  1. 拦截合法 JWT:攻击者通过抓包或日志获取一个有效的 JWT。
  2. 篡改头部:将 alg 字段从 HS256 或 RS256 修改为 none。
  3. 伪造载荷:修改 payload 中的用户身份信息(如 sub、role),提升权限。
  4. 移除签名:将 JWT 的第三部分(签名)置空或删除。
  5. 发送恶意请求:将构造好的 header.payload.(注意末尾的点号)发送给服务端。
⚠️ 关键陷阱:许多开发者误以为“只要验证签名就能保证安全”,却忽略了 alg=none 这种“无签名”的例外情况。攻击者不需要知道任何密钥,仅凭对 JWT 结构的理解即可完成攻击。
alg=none 攻击流程示意图 alg=none 攻击流程 攻击者 拦截合法 JWT 篡改头部 alg: none 伪造载荷 sub: admin 移除签名 第三部分置空 服务端 验证通过(漏洞) 攻击者无需密钥 仅篡改头部即可绕过

4.2 Payload 构造:手写一个无签名 JWT

一个标准的 JWT 由三部分组成:header.payload.signature。在 alg=none 攻击中,攻击者需要构造一个 只有两部分 的令牌:header.payload.(末尾保留点号,签名部分为空)。

以下是一个典型的恶意 JWT 构造示例:

// 原始合法 JWT(使用 HS256 签名)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwicm9sZSI6InVzZXIifQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

// 攻击者构造的 JWT(alg=none,无签名)
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFkbWluIiwicm9sZSI6ImFkbWluIn0.

Payload 构造的关键点:

  • 头部(Header):Base64URL 编码后的 {"alg":"none","typ":"JWT"}。
  • 载荷(Payload):Base64URL 编码后的恶意用户信息,如 {"sub":"admin","role":"admin"}。
  • 签名(Signature):完全省略,但 JWT 字符串末尾必须保留一个点号 .,表示签名部分为空。
💡 编码验证:可以使用 echo -n '{"alg":"none","typ":"JWT"}' | base64 | tr '+/' '-_' | tr -d '=' 命令生成 Base64URL 编码的头部。注意 Base64URL 编码与标准 Base64 的区别:+ 替换为 -,/ 替换为 _,并去除末尾的 =。

4.3 服务端漏洞代码示例

以下代码展示了存在 alg=none 漏洞的典型服务端实现。漏洞根源在于:解析库从 JWT 头部读取 alg 字段,并直接使用该值进行验证,而非强制使用服务端预定义的算法。

// 漏洞代码示例(Node.js + jsonwebtoken 库)
const jwt = require('jsonwebtoken');

// 中间件:验证 JWT
function authMiddleware(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) {
    return res.status(401).json({ error: 'Missing token' });
  }

  try {
    // 漏洞:未指定算法白名单,解析库从 token 头部读取 alg
    const decoded = jwt.verify(token, 'my-secret-key');
    req.user = decoded;
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid token' });
  }
}

// 攻击者发送的 token:header.payload.
// jwt.verify() 发现 alg 为 none,直接跳过签名验证,返回解码后的 payload
// 攻击者成功以 admin 身份访问受保护资源

漏洞代码的详细分析:

代码行 问题描述 安全影响
jwt.verify(token, 'my-secret-key') 未指定 algorithms 选项,库默认信任 token 头部的 alg 字段 攻击者可设置 alg: none,绕过签名验证
const decoded = jwt.verify(...) 未校验返回的 decoded 对象是否包含预期字段 攻击者可伪造任意用户身份和权限
req.user = decoded 直接将解码后的载荷赋值给请求对象,未做二次校验 恶意载荷中的 role: admin 被直接信任
⚠️ 常见错误认知:许多开发者认为“只要使用了 JWT 库,安全性就有保障”。实际上,jsonwebtoken 库在 8.5.1 版本之前,默认行为就是信任 alg 头部。即使是最新版本,如果开发者显式不传 algorithms 参数,仍然可能受到攻击。

4.4 防御措施:强制算法白名单与输入校验

防御 alg=none 攻击的核心原则是:永远不要信任客户端提供的算法声明。服务端必须强制使用预定义的签名算法,并对 JWT 的完整性进行严格校验。

以下是最有效的防御措施:

  1. 强制算法白名单:在 JWT 验证时,显式指定允许的算法列表,拒绝 none 及其他未授权算法。
  2. 严格校验 JWT 结构:确保 JWT 包含三个部分(header.payload.signature),拒绝只有两部分或格式异常的令牌。
  3. 使用安全的 JWT 库版本:升级到最新版本,并检查库的默认行为是否已修复 alg=none 漏洞。
  4. 二次校验载荷内容:验证解码后的 payload 是否包含预期的字段和数据类型,防止注入恶意数据。

修复后的安全代码示例:

// 安全代码示例(Node.js + jsonwebtoken 库)
const jwt = require('jsonwebtoken');

function authMiddleware(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) {
    return res.status(401).json({ error: 'Missing token' });
  }

  try {
    // 防御:强制指定算法白名单,拒绝 none
    const decoded = jwt.verify(token, 'my-secret-key', {
      algorithms: ['HS256']  // 只允许 HS256,拒绝 none、HS384 等
    });

    // 二次校验:确保载荷包含必要字段
    if (!decoded.sub || !decoded.role) {
      throw new Error('Invalid payload structure');
    }

    req.user = decoded;
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid token' });
  }
}

防御措施对比表:

防御措施 实现方式 效果
算法白名单 algorithms: ['HS256'] 直接拒绝 alg=none 及所有非白名单算法
结构校验 检查 JWT 是否包含三个部分,且签名部分非空 防止攻击者构造 header.payload. 格式
库版本升级 使用 jsonwebtoken@9+ 或等效安全库 利用库自身的安全修复,减少人为配置错误
载荷二次校验 验证 decoded.sub 是否为预期格式 即使绕过签名,也能通过业务逻辑拦截恶意数据
alg=none 防御措施架构 alg=none 防御措施架构 JWT 请求入口 算法白名单 algorithms: ['HS256'] JWT 结构校验 三部分 + 签名非空 安全库版本 jsonwebtoken@9+ 载荷二次校验 sub/role 字段验证 拒绝 alg=none 攻击 四层防御,层层阻断 任何一层均可拦截攻击
工程实践建议:在微服务架构中,建议将 JWT 验证逻辑封装为独立的中间件或网关服务,统一管理算法白名单和校验规则。同时,在 CI/CD 流水线中加入 JWT 安全扫描,自动检测代码中是否存在 alg=none 漏洞模式。