name: jwt-weakness description: JWT 弱密钥与信息泄露检测 — 检测 JWT 算法配置、密钥强度与敏感 claims 暴露;适用于登录认证、API 网关与单点登录场景。 when-to-use: 当任务涉及鉴权令牌安全、JWT 签名校验与 token 泄露风险评估时 allowed-tools: bash,read_file,list_files,rg user-invocable: false
JWT 弱密钥与信息泄露检测
成因引用
JWT Token / 凭证字符串(用户可控的签名与 claim 载荷)→ JWT 验证逻辑(服务端 HMAC 密钥校验 / 算法决策 / claim 信任 / 过期判定)= JWT 弱密钥与信任滥用类漏洞。一句话讲清成因:服务端把"客户端递交的 token 当作可信凭证",签名校验、算法选择与 claim 信任任一环失守,攻击者就能伪造或重放越权 token。业务命名不可作筛选——不能用"这是登录接口/这是网关 token/这个字段叫 access_token"作为是否检测的依据,是否存在 JWT 弱点要按 sink 语义(签名校验代码路径、算法白名单逻辑、claim 信任面、过期判定)判断;同一系统里 SSO 网关 token、刷新 token、设备 token 完全可能采用不同密钥与不同算法。详见同根目录 pentest/web-security-testing/SKILL.md 漏洞成因图谱 · JWT 弱密钥与信任滥用行(不在本 skill 重复成因)。
触发线索(基线检查项)
以下是已知的常见 JWT 弱密钥与信息泄露触发线索,作为基线起点而非必检硬清单:
- 适用且已完成 → 标注
[x] done - 明确不适用 → 标注
[-] n/a (原因) - 基线未列出但实际发现 → 新增条目并标注
[+] added (来源)
基线触发线索按"sink 语义"分类(不按业务命名):
- 签名校验 sink:响应或请求头中出现
eyJ开头的三段式字符串;Authorization: Bearer <jwt>;Cookie 名为token / access_token / id_token / session且值为 JWT 形态;前端 localStorage / sessionStorage 写入 JWT。 - 算法决策 sink:
alg=HS256/HS384/HS512且无密钥强度声明;alg=none仍被接受;同时存在 RS256 公钥可读端点(.well-known/jwks.json、/oauth/keys)且服务端未锁定算法白名单。 - claim 信任 sink:payload 含
role / isAdmin / scope / tenant_id / user_id等权限判定字段;payload 含明文敏感数据(手机号、邮箱、身份证、内部 ID、API key 片段);iss / aud字段未在服务端校验。 - 过期与撤销 sink:
exp / nbf / iat字段存在但服务端是否拒收过期 token 未知;登出后旧 token 是否仍被接受未知;无 jti / 黑名单机制。 - 代码模式:后端代码出现
jwt.parse(token)、jwt.decode(token, verify=False)、new JwtParser().setSigningKey(...).parseClaimsJws(...)等"签名校验或解码"形态;密钥来源为常量字符串、配置文件明文、环境变量但默认值为弱口令。
思考检查点
加载本 skill 时按这些问题思考:
- 这个 token 的签名校验代码路径在哪?密钥从何处加载?算法是否硬编码还是从 token 头部读取?
- 服务端是否同时接受多种算法(HS / RS / none)?算法白名单是收敛到一种还是只看 token 自己声明的
alg? - 哪些 claim 被用来做权限判定?这些 claim 在服务端是否被独立校验,还是直接信任 token 内容?
- 弱密钥结论是否可继续推到"伪造 token 能访问受保护资源"?只有签名能伪造不算闭环。
- 跨子系统(网关 token / SSO / 内部服务间 token)是否各自有独立密钥?任一子系统弱密钥都不能推断其他子系统也弱或强。
前置条件与安全边界
- 仅分析授权获取的 token,禁止采集或传播无授权敏感凭证。
- 弱密钥验证仅限小规模受控字典(首选项目内置
jwt.secrets.txt或同等规模),不进行高强度暴力尝试。 - 非 HMAC 算法(如 RS256/ES256)不做"弱密钥爆破"结论;仅检查算法混淆、claim 信任与过期判定面。
- 命中高风险后立即停止扩展尝试并进入修复建议阶段,不在生产环境继续构造越权请求。
检测步骤
Step 1:基线采集
提取请求中的 Authorization header / Cookie / body / 前端存储中的 JWT;记录采集位置(哪个端点的请求/响应)以便后续端点矩阵传播登记。
Step 2:解码分析
调用 jwt_decode 做无签名校验解码,记录:
- 算法
alg、密钥 IDkid - 过期相关字段
exp / nbf / iat - 身份与权限相关 claims(
sub / role / scope / tenant_id / isAdmin / iss / aud) - payload 中是否含敏感明文(手机号、邮箱、内部 ID、API key 片段)
Step 3:弱密钥验证(条件执行)
若为 HMAC 算法(HS256/HS384/HS512):
- 已知或可猜测密钥时优先尝试定向
key解密; - 未知时调用
jwt_crack并按需开启内置字典批量验证(use_builtin_wordlist=true)。
非 HMAC 算法(RS256/ES256):
- 不做弱密钥爆破,转而验证"算法混淆"——尝试把
alg=RS256改成alg=HS256,把公钥当 HMAC 密钥使用是否被服务端接受。 - 验证
alg=none是否被接受(移除签名段)。
Step 4:闭环利用验证
将命中的密钥或绕过形态用于伪造一个新 token(最小化篡改,如把 role=user 改为 role=admin),重发到受保护端点;观察服务端是否真的据此放权。仅能解码或能签出新 token 但服务端未接受,不能判 confirmed。
示例库
正例形态(代码层根因)
jwt.parse(token).getBody()(无算法白名单约束)— 服务端未限制算法,攻击者可改alg=none绕过签名(jwt-alg-none-accepted)new JwtParser().setSigningKey(publicKeyPem).parseClaimsJws(token)(同时支持 RS256 与 HS256)— 经典 RS256→HS256 算法混淆(jwt-rs256-to-hs256-confusion)- HMAC 密钥来自
application.yml中jwt.secret: secret123— 硬编码弱密钥(jwt-hs256-weak-secret-bruteforce) claims.get("role")直接用于权限判定且无服务端二次校验 — claim 篡改无防护(jwt-claim-tampering-no-verify)
窄化反例(必须避免)
以下是 JWT 弱密钥与信息泄露维度的典型窄化误判:
- "已测 HS256 弱密钥就跳过 alg=none / 算法混淆" — 错。HS256 弱密钥、
alg=none、RS256→HS256 混淆是三个独立的 sink 语义形态,密钥强度判定不能替代算法决策面的覆盖。 - "JWT 看起来挺长(300+ 字符)就跳过弱密钥" — 错。token 长度由 payload 决定,与密钥强度无关;密钥强度按 Shannon 熵或字典命中判定,不能用 token 字面长度推断。
- "某子系统的 JWT 是强密钥就推断其他子系统也是" — 错。网关 token、刷新 token、设备 token、内部服务间 token 通常由不同模块签发,必须跨子系统独立结账。
- "Token 含
exp字段 → 已防过期" — 错。exp是 claim 声明,需要服务端实际验证;很多实现只解码不校验时间,必须用过期 token 重放确认服务端是否拒收。 - "参数名/字段名不含
token/jwt就跳过" — 错。按 sink 语义(任何三段式 Base64URL 形态、任何被服务端当作签名凭证使用的字符串)筛选,不按命名筛选。
反例义务(必须遵守)
为什么这里是「必须」:反例义务属于交付契约——"该子系统无 JWT 弱点"或"已防护"结论是覆盖完整性的产物声明,缺失反向验证清单会让下游误信"该维度全站安全"。
写"未发现 JWT 弱点"或"已防护"前,产物必须包含:
- 测过的 JWT 候选端点完整清单(按 sink 语义枚举,不按参数名筛选;含 Authorization 头、Cookie、localStorage/sessionStorage、内部服务间调用、SSO 回调、刷新接口)
- 每个端点测过的形态:弱密钥字典命中、
alg=none、RS256↔HS256 混淆、claim 篡改、过期重放 - 每个端点的响应证据(HTTP 状态、响应体关键字段、是否放权)
清单不完整 → 结论降级为 partial-coverage 并显式声明未覆盖范围。
特别警示:最容易漏的是"算法决策面跨签发模块差异"——主登录走 RS256 强密钥,刷新 token 走 HS256 弱密钥;只看一处会直接判全站安全。每个独立签发模块都要单独跑一遍弱密钥与算法混淆。
闭环验证要求(必须遵守)
通用闭环口径见同根目录 common/closure-verification.md(技能表 path 列同一抽取根下,需要时 read_file 读取)。核心:完整证据链才判 confirmed,中间信号最多 suspected。本漏洞特有要点:
- 仅
jwt_crack命中弱密钥、仅能离线伪造 token——属中间信号,需进一步把伪造 token 发到受保护端点并观察服务端放权才能confirmed。 - claims 泄露需证明接收方范围不应看到这些内容(如普通用户的 token 含他人手机号),仅"payload 含敏感字段"无法判 confirmed。
- 算法混淆与
alg=none必须用伪造后的 token 实际请求受保护资源,仅能成功签名不算闭环。
判定标准
| 现象 | 判定 |
|---|---|
jwt_crack 明确命中 HMAC 签名密钥,且据此最小化伪造/篡改 token 后被服务端接受并放权 |
confirmed |
alg=none / 算法混淆 / claim 篡改后的 token 被服务端接受并放权 |
confirmed |
| 命中弱密钥但未验证受保护资源接受;或 claims 暴露不必要敏感信息但影响范围未确认 | suspected |
| 算法与内容均无明显问题,且未出现弱密钥命中证据 | not vulnerable |
| 覆盖清单不完整(如未跨签发模块测算法混淆/未测 alg=none) | partial-coverage |
修复建议
- HMAC 模式使用高强度随机密钥(≥ 256 bit,Shannon 熵 ≥ 7.0)并定期轮换,避免默认/弱口令密钥。
- 优先采用非对称签名(RS256/ES256)并严格管理私钥;服务端锁定单一算法白名单,禁止从 token 头读取
alg。 - 显式拒绝
alg=none;接收端始终用对应算法的密钥校验,杜绝 RS256/HS256 混淆。 - claims 仅保留最小必要信息,避免放入敏感身份与业务数据;权限判定在服务端独立查库二次校验。
- 严格校验
exp/nbf/iss/aud,服务端实现 token 撤销(黑名单 / 短 TTL + 刷新机制)。