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 发送给服务器。
服务器的错误逻辑:
如果服务器的验证代码类似以下伪代码(存在缺陷):
# 错误示例:完全信任客户端提供的 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_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:
- 发送请求:将伪造的 JWT 置于
Authorization: Bearer <fake_token>中,发送到需认证的接口(如/api/admin/delete)。 - 结果:若服务器存在漏洞,会使用公钥作为 HMAC 密钥验证签名,认为令牌合法,授予攻击者管理员权限。
步骤5:防护措施
- 强制指定验证算法:在验证代码中,不依赖客户端提供的
alg,而是显式指定预期算法。# 正确示例:强制使用 RS256 from jwt import decode decoded = decode(token, public_key, algorithms=["RS256"]) # 只允许 RS256 - 使用密钥 ID(kid)验证:JWT Header 可包含
kid,指定用于验证的密钥 ID。服务器应维护受信的密钥列表,并确保kid与算法匹配。 - 公钥与对称密钥严格分离:非对称验证的公钥不应被用作 HMAC 密钥。密钥管理上区分用途。
- 更新 JWT 库:使用最新版本的 JWT 库(如
pyjwt、java-jwt),它们默认要求显式指定算法,避免自动推断。 - 使用非对称算法时避免公钥泄露:若非必要,不公开公钥;若必须公开(如 OAuth),确保验证逻辑强制算法检查。
- 采用固定算法组合:在架构设计时,固定使用一种算法(如 RS256),避免在同一个系统中混用对称/非对称算法。
步骤6:测试与验证
- 代码审计:检查 JWT 验证函数是否显式传入算法白名单。
- 渗透测试:使用工具(如
jwt_tool)尝试算法混淆:
观察服务器是否接受python3 jwt_tool.py <JWT> -X k -pk public.pemalg从 RS256 改为 HS256 的令牌。 - 监控日志:关注 JWT 验证失败的日志,特别是算法不匹配的错误,这可能标识攻击尝试。
总结
JWT 密钥混淆攻击利用了算法选择逻辑的缺陷,其根本原因是服务器过度信任客户端控制的输入(alg字段)。防护核心在于不信任客户端声明的算法,始终在服务端强制指定预期的验证算法,并保持密钥管理的严格隔离。通过安全编码和库的正确使用,可有效防御此类攻击。