授权测试基于 JWT 的身份认证:签名算法混淆、None 算法与重放安全验证教程

枫少@KillBoy
枫少@KillBoy
枫少@KillBoy
管理员
371
文章
0
粉丝
渗透测试1 8阅读模式
AI智能摘要
JWT 虽是无状态认证的核心,但若后端校验逻辑存在松懈,攻击者极易通过篡改算法或剥离签名突破鉴权屏障。本文深入剖析三大高危缺陷:None 算法大小写变体绕过、RS256 至 HS256 的算法混淆,以及令牌缺乏有效吊销机制导致的无限重放风险。文中不仅给出利用 Burp Suite 构造无害化 PoC 的具体步骤,更强调必须严守授权边界与非破坏性原则。当伪造令牌成功获取越权数据时,如何精准判定漏洞成立并立即终止测试?
— AI 生成的文章内容摘要

在现代 Web 架构与 API 安全体系中,JSON Web Token(JWT)常作为无状态身份认证与会话管理的核心载体。依据 OWASP Web 安全测试指南(WSTG)的身份验证测试框架,针对 JWT 的安全性评估需要聚焦于其算法解析逻辑、签名有效性校验以及生命周期约束。若后端验证逻辑存在松懈或配置缺陷,攻击者可能通过篡改算法类型、伪造数据段甚至直接剥离签名来突破身份鉴权屏障。开展此类安全验证必须立足于严谨的授权边界与受控影响原则,通过清晰的测试用例与无害化验证手段排查系统薄弱点。

JWT结构拆解与授权验证流程示意图

授权测试基线与无害化控制

在进行 JWT 专项安全验证前,测试人员必须与业务方确认书面授权协议,明确受测目标 API、测试端点及测试账号角色边界。未经授权擅自对生产环境执行算法伪造或重放属于高危越权行为。为保障被测系统稳定运行,测试过程应遵守以下控制要求:

  • 专用测试凭据:所有测试用例必须基于授权提供的低权限测试账号展开,禁止捕获或复用非授权的真实用户凭据。
  • 非破坏性载荷(Safe PoC):在修改 Payload 时,仅微调无害声明(例如将 user_id 替换为授权范围内的专用辅助测试 ID,或在自定义字段填入 test_probe),禁止随意提升至超级管理员权限执行写入、更新或删除等破坏性业务操作。
  • 即时停止条件:一旦构造的篡改令牌成功调用受限只读接口(如获取到测试账号权限外的状态响应码 200 及只读信息),即可判定漏洞成立并立即终止后续测试,严禁利用该状态进一步实施内网横向移动或持久化利用。
  • 证据脱敏与记录:截获并保存的 HTTP 请求与响应数据包中,应将敏感参数、用户隐私数据进行掩码处理,仅保留算法字段、状态码及关键验证字段作为复核依据。

缺陷验证一:None 算法未限制绕过

部分早期的 JWT 解析库在设计时遵循标准兼容性,支持将算法标识 alg 设置为 none,用于未签名或签名由外部通道保障的场景。如果后端应用未显式禁用该算法,解析组件将跳过签名校验逻辑,直接信任令牌主体内容。

1. 验证原理与假设

当服务端仅以“解析成功且未报错”作为鉴权依据,且依赖的库函数在解析 alg: none 时默认放行,测试人员只要修改 Payload 中的用户身份标识并清除签名部分,即可实现非授权身份伪造。

2. 测试用例构造

获取合法登录后的 JWT 原始串,将其分解为三段 Base64URL 编码值:

Header: {"alg":"HS256","typ":"JWT"}
Payload: {"sub":"test_user_01","role":"user","iat":1710000000}
Signature: [原始二进制签名 Base64URL 编码]

测试人员构造 Header,将 alg 分别替换为 none、None、NONE、noNe 等大小写变体以排查大小写过滤绕过漏洞:

{
  "alg": "none",
  "typ": "JWT"
}

保持原始 Payload 或注入已授权测试用的其他账号标识,组装最终令牌时保留第二段末尾的点号(.)但移除第三段签名:

eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJ0ZXN0X3VzZXJfMDIiLCJyb2xlIjoidXNlciIsImlhdCI6MTcxMDAwMDAwMH0.

3. 请求发送与判定

通过安全测试代理工具(如 Burp Suite 或 OWASP ZAP)将构造的令牌替换至目标受保护接口的 Authorization: Bearer <token> 标头中。

  • 漏洞成立标志:服务器返回 200 OK,且接口响应内容体现出 test_user_02 的只读上下文。
  • 安全响应标准:服务器返回 401 Unauthorized 或 403 Forbidden,日志记录“Unsupported or invalid algorithm”并拒绝解析。
RS256 到 HS256 算法混淆漏洞验证机制

缺陷验证二:RS256 到 HS256 算法混淆

算法混淆(Algorithm Confusion)常见于使用非对称加密(如 RS256)的认证体系中。该缺陷源于服务端校验逻辑将非对称公钥错误地当作对称加密的共享密钥使用。

1. 漏洞机制解析

在 RS256 机制下,服务端拥有私钥用于签发 Token,验证端(如 API 网关或微服务)使用对应的 RSA 公钥验签。由于 RSA 公钥是公开资源(常发布于 /.well-known/jwks.json 端点),如果后端使用的 JWT 验证函数未严格绑定预期算法,而是依据请求 Header 中的 alg 参数动态选择验签流程,当测试者将 alg 改为 HS256 并使用服务端的 RSA 公钥原文作为 HMAC 密钥生成签名时,服务端若直接将存储的 RSA 公钥传给 HMAC 验签接口,两端计算出的签名将会匹配成功。

2. 规范化测试流程

  1. 公钥信息捕获:从授权范围内的公开端点(如 OAuth2 的 JWKS 配置路径)提取 PEM 格式或 JWK 格式的 RSA 公钥证书。
  2. 规范化公钥文本:确保公钥包含标头(-----BEGIN PUBLIC KEY-----)与换行符,其字节序列必须与服务器内部存储并读取的公钥格式完全一致。
  3. 重写令牌头部与载荷:
   // 修改后的 Header
   {
     "alg": "HS256",
     "typ": "JWT"
   }
  1. 生成 HMAC-SHA256 伪造签名:使用获取的公钥内容作为 HMAC 的 secret 对 Base64URL(Header) + "." + Base64URL(Payload) 进行哈希计算并拼接。
  2. 单次受控验证:将伪造的令牌投递至目标测试接口。若系统未发生签名校验失败而是成功返回业务响应,说明服务端存在算法混淆逻辑。记录单次请求与响应日志后,立即停止测试并标记漏洞。

缺陷验证三:令牌未绑定有效期的重放测试

JWT 的无状态特性要求其生命周期必须具备高时效性与失效判定机制。若缺少 exp(过期时间)声明,或服务端未建立黑名单/吊销列表机制,将导致令牌被截获后产生无限期重放风险。

1. 有效期字段缺失测试

  1. 观察合法生成的 JWT 载荷中是否存在 exp 声明。
  2. 若存在 exp,利用授权测试工具截获 Token,等待其经过设定的超时间隔(例如 15 分钟),重新向接口发起请求。
  3. 若请求在超期后依然成功响应,证明服务端解析库未开启时间窗校验,或容差范围(clock skew)设置过长。

2. 登出与吊销状态重放测试

  1. 使用测试账号在受控客户端正常登录,记录当前有效的 JWT。
  2. 在客户端执行正常的“退出登录”或“修改密码”动作。
  3. 立即使用退出前记录的 JWT 重新请求需认证接口。
  4. 若接口仍返回受保护的资源数据,说明服务端未实施分布式缓存黑名单(如 Redis Token Blacklist)或基于版本号(Token Versioning/JTI)的吊销控制,会话重放风险成立。

证据留存与服务端加固整改

测试完成后,渗透测试人员应形成标准化的安全复核依据,并向研发团队提供清晰的整改建议。

1. 证据规范化记录要求

报告中严禁包含生产账号凭证,应提供以下要素以确保漏洞可复现、可研判:

  • 触发缺陷的具体 API 端点与 HTTP 方法。
  • 明确展示篡改后的 Header(如 "alg": "HS256")、Payload 声明对比。
  • 服务端返回的脱敏响应报文(保留状态行、关键响应头及非敏感验证字段)。
  • 明确标注验证所用公钥的获取端点与计算环境。

2. 服务端加固与代码整改规范

修复 JWT 认证缺陷的核心原则在于拒绝信任客户端声明的算法类型,推行严格的服务端白名单校验。

Python (PyJWT) 规范示例

调用 jwt.decode 时,必须显式指定允许的算法列表,禁止根据 Header 动态切换,同时强制要求特定声明的存在:

import jwt

# 正确做法:显式锁定期望的算法白名单,并强制校验过期时间
def verify_token(token: str, rsa_public_key: str):
    try:
        decoded_payload = jwt.decode(
            token,
            key=rsa_public_key,
            algorithms=["RS256"],  # 仅允许 RS256,彻底阻断 none 与 HS256 混淆
            options={
                "require": ["exp", "iat", "sub"],
                "verify_exp": True,
                "verify_signature": True
            }
        )
        return decoded_payload
    except jwt.InvalidTokenError as e:
        # 统一抛出凭据异常,避免暴露具体验证细节
        raise PermissionError("Invalid authentication token")

Node.js (jsonwebtoken) 规范示例

在基于 Node.js 的微服务网关中,验签配置同样需强制静态绑定:

const jwt = require('jsonwebtoken');

function verifyClientToken(token, publicKey) {
    return new Promise((resolve, reject) => {
        jwt.verify(token, publicKey, {
            algorithms: ['RS256'], // 拒绝接受客户端 Header 指定的其他算法
            complete: false
        }, (err, decoded) => {
            if (err) {
                return reject(new Error('Token validation failed'));
            }
            resolve(decoded);
        });
    });
}

针对令牌重放与撤销问题,架构层面应缩短 Access Token 的生命周期(建议控制在 5 至 15 分钟),并配合 Refresh Token 轮换机制;对于用户注销、权限变更或密码重置等敏感操作,应在持久化缓存中维护基于 jti(JWT ID)的黑名单,或在数据库与 Token 内部同时维护 token_version 字段,确保历史凭据在核心动作触发后即时失效。完成代码加固后,测试团队需按相同用例执行回归复测,确保验证逻辑闭环。

 
枫少@KillBoy
    • 旅途如歌
      旅途如歌 1

      请问这种测试一般用哪个框架方便些?

    匿名

    发表评论

    匿名网友

    拖动滑块以完成验证