JSON Web Token (JWT) 的密钥混淆攻击(Key Confusion)漏洞与防护
字数 2711 2025-12-15 02:04:20

JSON Web Token (JWT) 的密钥混淆攻击(Key Confusion)漏洞与防护

知识点描述
JWT 密钥混淆攻击(也称为算法混淆攻击)是一种利用 JWT 库在处理令牌时,对签名验证算法的选择逻辑缺陷而实施的攻击。攻击者通过篡改 JWT 的头部(Header),将算法(alg字段)从非对称算法(如 RS256)改为对称算法(如 HS256),并利用公钥(通常是公开的)作为对称密钥来伪造签名,诱使服务器使用错误的方式验证签名,从而绕过认证,实现身份伪造或权限提升。

核心问题
JWT 的签名验证依赖于alg字段来选择合适的密钥和验证逻辑。如果服务端的验证代码在算法选择上存在逻辑缺陷(例如,未强制指定预期算法,而是完全信任客户端提供的alg值),就可能被攻击者利用,将本应使用私钥签名的非对称签名验证,改为使用公开的公钥作为对称密钥来验证,从而允许攻击者伪造合法令牌。

循序渐进讲解


步骤1:回顾JWT基本结构与签名机制
JWT 由三部分组成,以点号分隔:Header.Payload.Signature

  • Header:包含令牌元数据,通常是{"alg": "HS256", "typ": "JWT"}alg声明签名算法,常见的有:
    • HS256:使用 HMAC SHA-256 的对称签名,同一密钥用于签名和验证。
    • RS256:使用 RSA SHA-256 的非对称签名,私钥签名,公钥验证。
  • Payload:存放声明(如用户ID、过期时间等)。
  • Signature:对前两部分(Base64Url编码后)的签名,确保完整性。

关键:验证签名时,服务器需根据alg值选择对应的验证逻辑和密钥。


步骤2:理解密钥混淆攻击的原理
假设一个应用在发布 JWT 时,本意是使用非对称算法 RS256:

  • 服务器用私钥签名,公钥通常公开(例如通过 JWKS 端点/.well-known/jwks.json发布)。
  • 验证时,服务器应使用公钥验证签名。

攻击者的操作

  1. 截获或构造一个 JWT,将其 Header 中的algRS256改为HS256
  2. 保持 Payload 不变(例如,将"sub":"user"改为"sub":"admin")。
  3. 服务器的公钥作为 HMAC 的对称密钥,重新计算签名(生成Signature部分)。
  4. 将篡改后的 JWT 发送给服务器。

服务器的错误逻辑
如果服务器的验证代码类似以下伪代码(存在缺陷):

# 错误示例:完全信任客户端提供的 alg
def verify_jwt(token, public_key):
    header, payload, signature = decode_unverified(token)  # 不验证签名,先解码头和载荷
    if header['alg'] == 'HS256':
        # 错误:使用公钥作为 HMAC 密钥验证对称签名
        expected_sig = HMAC-SHA256(public_key, header + '.' + payload)
        return expected_sig == signature
    elif header['alg'] == 'RS256':
        return RSA_verify(public_key, signature, header + '.' + payload)

攻击者即可通过步骤3的伪造,使expected_sigsignature匹配,验证通过。


步骤3:攻击的必要条件

  1. 服务器使用非对称算法(如 RS256、PS256)签名 JWT,且公钥可被攻击者获取(通常是公开的)。
  2. 服务器的 JWT 验证库或自定义代码,在验证时信任客户端提供的alg值,未强制检查算法是否符合预期。
  3. 服务器使用的 JWT 库在某些版本中存在默认行为缺陷(例如,早期版本的某些库在算法不匹配时可能回退到弱验证)。

步骤4:完整攻击流程示例
假设场景:一个 Web 应用使用 RS256 签发 JWT,公钥可通过接口/api/public_key获取。

  1. 信息收集:攻击者访问/api/public_key获得公钥(PEM 格式)。
  2. 构造恶意 JWT
    • 原始合法 JWT:eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyIiwicm9sZSI6InVzZXIifQ.SignatureRS256
    • 修改 Header:{"alg": "HS256", "typ": "JWT"} -> Base64Url 编码为eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    • 修改 Payload:{"sub":"admin","role":"admin"} -> 编码为eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9
    • 计算 HMAC-SHA256 签名:以公钥为密钥,对eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9进行签名,得到malicious_sig
    • 组合:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.malicious_sig
  3. 发送请求:将伪造的 JWT 置于Authorization: Bearer <fake_token>中,发送到需认证的接口(如/api/admin/delete)。
  4. 结果:若服务器存在漏洞,会使用公钥作为 HMAC 密钥验证签名,认为令牌合法,授予攻击者管理员权限。

步骤5:防护措施

  1. 强制指定验证算法:在验证代码中,不依赖客户端提供的alg,而是显式指定预期算法。
    # 正确示例:强制使用 RS256
    from jwt import decode
    decoded = decode(token, public_key, algorithms=["RS256"])  # 只允许 RS256
    
  2. 使用密钥 ID(kid)验证:JWT Header 可包含kid,指定用于验证的密钥 ID。服务器应维护受信的密钥列表,并确保kid与算法匹配。
  3. 公钥与对称密钥严格分离:非对称验证的公钥不应被用作 HMAC 密钥。密钥管理上区分用途。
  4. 更新 JWT 库:使用最新版本的 JWT 库(如pyjwtjava-jwt),它们默认要求显式指定算法,避免自动推断。
  5. 使用非对称算法时避免公钥泄露:若非必要,不公开公钥;若必须公开(如 OAuth),确保验证逻辑强制算法检查。
  6. 采用固定算法组合:在架构设计时,固定使用一种算法(如 RS256),避免在同一个系统中混用对称/非对称算法。

步骤6:测试与验证

  • 代码审计:检查 JWT 验证函数是否显式传入算法白名单。
  • 渗透测试:使用工具(如jwt_tool)尝试算法混淆:
    python3 jwt_tool.py <JWT> -X k -pk public.pem
    
    观察服务器是否接受alg从 RS256 改为 HS256 的令牌。
  • 监控日志:关注 JWT 验证失败的日志,特别是算法不匹配的错误,这可能标识攻击尝试。

总结
JWT 密钥混淆攻击利用了算法选择逻辑的缺陷,其根本原因是服务器过度信任客户端控制的输入(alg字段)。防护核心在于不信任客户端声明的算法,始终在服务端强制指定预期的验证算法,并保持密钥管理的严格隔离。通过安全编码和库的正确使用,可有效防御此类攻击。

JSON Web Token (JWT) 的密钥混淆攻击(Key Confusion)漏洞与防护 知识点描述 JWT 密钥混淆攻击(也称为算法混淆攻击)是一种利用 JWT 库在处理令牌时,对签名验证算法的选择逻辑缺陷而实施的攻击。攻击者通过篡改 JWT 的头部(Header),将算法( alg 字段)从非对称算法(如 RS256)改为对称算法(如 HS256),并利用公钥(通常是公开的)作为对称密钥来伪造签名,诱使服务器使用错误的方式验证签名,从而绕过认证,实现身份伪造或权限提升。 核心问题 JWT 的签名验证依赖于 alg 字段来选择合适的密钥和验证逻辑。如果服务端的验证代码在算法选择上存在逻辑缺陷(例如,未强制指定预期算法,而是完全信任客户端提供的 alg 值),就可能被攻击者利用,将本应使用私钥签名的非对称签名验证,改为使用公开的公钥作为对称密钥来验证,从而允许攻击者伪造合法令牌。 循序渐进讲解 步骤1:回顾JWT基本结构与签名机制 JWT 由三部分组成,以点号分隔: Header.Payload.Signature 。 Header :包含令牌元数据,通常是 {"alg": "HS256", "typ": "JWT"} 。 alg 声明签名算法,常见的有: HS256 :使用 HMAC SHA-256 的对称签名,同一密钥用于签名和验证。 RS256 :使用 RSA SHA-256 的非对称签名,私钥签名,公钥验证。 Payload :存放声明(如用户ID、过期时间等)。 Signature :对前两部分(Base64Url编码后)的签名,确保完整性。 关键 :验证签名时,服务器需根据 alg 值选择对应的验证逻辑和密钥。 步骤2:理解密钥混淆攻击的原理 假设一个应用在发布 JWT 时,本意是使用非对称算法 RS256: 服务器用私钥签名,公钥通常公开(例如通过 JWKS 端点 /.well-known/jwks.json 发布)。 验证时,服务器应使用公钥验证签名。 攻击者的操作 : 截获或构造一个 JWT,将其 Header 中的 alg 从 RS256 改为 HS256 。 保持 Payload 不变(例如,将 "sub":"user" 改为 "sub":"admin" )。 用 服务器的公钥 作为 HMAC 的对称密钥,重新计算签名(生成 Signature 部分)。 将篡改后的 JWT 发送给服务器。 服务器的错误逻辑 : 如果服务器的验证代码类似以下伪代码(存在缺陷): 攻击者即可通过步骤3的伪造,使 expected_sig 与 signature 匹配,验证通过。 步骤3:攻击的必要条件 服务器使用非对称算法(如 RS256、PS256)签名 JWT,且公钥可被攻击者获取(通常是公开的)。 服务器的 JWT 验证库或自定义代码,在验证时信任客户端提供的 alg 值,未强制检查算法是否符合预期。 服务器使用的 JWT 库在某些版本中存在默认行为缺陷(例如,早期版本的某些库在算法不匹配时可能回退到弱验证)。 步骤4:完整攻击流程示例 假设场景:一个 Web 应用使用 RS256 签发 JWT,公钥可通过接口 /api/public_key 获取。 信息收集 :攻击者访问 /api/public_key 获得公钥(PEM 格式)。 构造恶意 JWT : 原始合法 JWT: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyIiwicm9sZSI6InVzZXIifQ.SignatureRS256 修改 Header: {"alg": "HS256", "typ": "JWT"} -> Base64Url 编码为 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 修改 Payload: {"sub":"admin","role":"admin"} -> 编码为 eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9 计算 HMAC-SHA256 签名:以公钥为密钥,对 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9 进行签名,得到 malicious_sig 。 组合: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.malicious_sig 发送请求 :将伪造的 JWT 置于 Authorization: Bearer <fake_token> 中,发送到需认证的接口(如 /api/admin/delete )。 结果 :若服务器存在漏洞,会使用公钥作为 HMAC 密钥验证签名,认为令牌合法,授予攻击者管理员权限。 步骤5:防护措施 强制指定验证算法 :在验证代码中,不依赖客户端提供的 alg ,而是显式指定预期算法。 使用密钥 ID(kid)验证 :JWT Header 可包含 kid ,指定用于验证的密钥 ID。服务器应维护受信的密钥列表,并确保 kid 与算法匹配。 公钥与对称密钥严格分离 :非对称验证的公钥不应被用作 HMAC 密钥。密钥管理上区分用途。 更新 JWT 库 :使用最新版本的 JWT 库(如 pyjwt 、 java-jwt ),它们默认要求显式指定算法,避免自动推断。 使用非对称算法时避免公钥泄露 :若非必要,不公开公钥;若必须公开(如 OAuth),确保验证逻辑强制算法检查。 采用固定算法组合 :在架构设计时,固定使用一种算法(如 RS256),避免在同一个系统中混用对称/非对称算法。 步骤6:测试与验证 代码审计 :检查 JWT 验证函数是否显式传入算法白名单。 渗透测试 :使用工具(如 jwt_tool )尝试算法混淆: 观察服务器是否接受 alg 从 RS256 改为 HS256 的令牌。 监控日志 :关注 JWT 验证失败的日志,特别是算法不匹配的错误,这可能标识攻击尝试。 总结 JWT 密钥混淆攻击利用了算法选择逻辑的缺陷,其根本原因是服务器过度信任客户端控制的输入( alg 字段)。防护核心在于 不信任客户端声明的算法 ,始终在服务端强制指定预期的验证算法,并保持密钥管理的严格隔离。通过安全编码和库的正确使用,可有效防御此类攻击。