JWT Header JWK 注入提权漏洞
记录 JWT Header 中嵌入 JWK 导致签名信任源错误的原理、Burp JWT Editor 操作过程、接口识别与防御思路。
本文记录授权靶场中的学习过程,仅用于理解 JWT 验签中的信任源问题。
一、漏洞原理
1. 核心成因:信任源错误
JWK(JSON Web Key)是一种使用 JSON 表示密码学密钥的格式。JWT Header 可以包含 jwk 字段,用于携带验证签名所需的公钥。
支持 jwk 字段本身不一定存在漏洞。真正的问题是:后端验签时直接信任客户端传入的公钥,却没有验证这个密钥是否来自受信任来源。这样一来,服务器虽然执行了签名验证,验证的却是攻击者自己提供的密钥。
2. 技术细节
以 RS256 为例,私钥用于签名,公钥用于验证。存在漏洞的后端没有强制使用服务器保存的可信公钥,而是读取了 JWT Header 中客户端可控的 jwk:
{
"kid": "attacker-key-id",
"alg": "RS256",
"jwk": {
"kty": "RSA",
"kid": "attacker-key-id",
"use": "sig",
"alg": "RS256",
"n": "攻击者公钥的模数",
"e": "AQAB"
}
}
jwk 中只嵌入公开部分。对应私钥仍保留在本地,用来给修改后的 Header 和 Payload 重新签名。
3. 攻击闭环
- 在本地生成一对自己的 RSA 密钥,包括公钥和私钥。
- 修改 Payload,将身份提权为
"role":"admin"。 - 将自己的公钥以
jwk字段嵌入 JWT Header。 - 使用对应私钥对修改后的 Header 和 Payload 重新签名。
- 服务器收到 Token 后,错误地使用 Header 中的 JWK 公钥验签。因为签名正是由配套私钥生成的,所以验证通过并造成越权。
这个过程不是破解服务器私钥,也不是跳过签名验证,而是诱导服务器使用攻击者指定的公钥进行验证。
二、解题步骤拆解
1. 抓包分析
登录系统,在 Burp 中捕获身份验证请求,并发送到 Repeater。请求头中包含 JWT:
Authorization: Bearer <Token>
先使用原始 Token 请求目标数据接口,确认当前普通用户的权限和响应,作为后续对照。
2. 生成 RSA 密钥对
进入 Burp 顶部的 JWT Editor Keys 标签页:
- 点击
New RSA Key。 - 点击
Generate,自动生成新的 RSA 密钥对。 - 保存密钥。
生成的密钥只用于授权靶场测试,不应混入真实业务密钥库或提交到博客仓库。
3. 修改 Payload
回到 Repeater,切换至 JSON Web Token 标签页,根据靶场实际使用的身份字段修改 Payload:
{
"username": "admin",
"role": "admin"
}
字段名没有统一规定。必须以原始 Token 和后端实际授权逻辑为准,不是所有系统都通过 username 或 role 判断权限。
4. 执行 Embedded JWK
点击:
Attack → Embedded JWK
选择刚才生成的 RSA 密钥。插件会完成两个关键动作:
- 把该密钥对的公钥部分写入 Header 的
jwk字段; - 使用对应私钥重新签名修改后的 Token。
执行后应检查 Header 中是否已经出现 jwk,并确认顶层 kid 与嵌入密钥的 kid 保持一致。手工构造时,这个细节尤其容易漏掉。
5. 请求数据接口
将目标路径修改为个人信息 API,并携带构造后的 Token:
GET /api/userinfo HTTP/1.1
Authorization: Bearer <构造后的 Token>
发送请求后,根据 HTTP 状态码、响应正文和实际权限综合判断。在该靶场中,成功后可以从 JSON 响应的 flag 字段获取结果。
三、踩坑与考点总结
1. 接口识别
POST /api/login用于获取初始 Token。POST /api/logout仅用于注销会话。- 只有携带 Token 请求数据接口,例如
/api/userinfo,才能触发后端的管理员鉴权逻辑。
2. 前后端分离逻辑
访问页面路由,例如 /user,返回的是前端渲染视图(HTML);访问数据接口,例如 /api/userinfo,返回的是纯 JSON 业务数据。该靶场的 Flag 绑定在 API 的数据响应中。
3. 不要只看状态码
200 可能只是普通用户接口正常返回,302 也可能是应用本身的跳转流程。应对比修改前后的响应内容、身份字段以及是否真正获得了目标权限。
四、防御修复建议
- 建立可信密钥边界: 服务端应使用本地配置、密钥管理服务或经过严格认证和白名单限制的密钥源验签,不能直接信任 Token 自带的公钥。
- 限制 Header 参数: 如果业务不需要嵌入 JWK,应拒绝或忽略客户端传入的
jwk、jku等密钥选择参数;确实需要时,必须验证来源和允许范围。 - 固定允许算法: 服务端根据自身配置确定允许的算法,不能让客户端 Header 单独决定验签策略。
- 校验密钥标识:
kid只能在服务端维护的可信密钥集合中选择,不能据此加载任意密钥或文件。 - 完成声明校验: 签名通过后仍要检查
exp、nbf、iss、aud等声明,并在业务层重新执行权限判断。
五、复盘 Tips
- JWK 是 JSON 格式的密钥表示,JWT 是承载声明的 Token,两者不是同一种东西。
- RS256 中私钥负责签名、公钥负责验签。 Embedded JWK 注入成功的关键,是服务器错误地相信了客户端提供的公钥。
- 签名验证成功不等于 Token 值得信任。 验签所使用的密钥来源也必须可信。
- 手工嵌入 JWK 时注意
kid。 顶层 Header 的kid通常需要与嵌入 JWK 的kid一致;JWT Editor 的自动攻击功能会帮助处理这一步。 - 修改身份字段应遵循最小变更原则。 一次只改真正参与授权的字段,便于判断是哪一步产生了效果。