在现代 Web 应用与微服务架构中,JSON Web Token (JWT) 已经成为身份验证与授权的标准配置。然而,开发者在实现 JWT 机制时,常常因过于依赖库的默认配置或对加密签名理解不足,引入严重的安全漏洞。
1. JWT 结构速览与测试切入点
一个标准的 JWT 由三部分组成,用点号 (.) 隔开:Header.Payload.Signature。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
在测试开始前,使用 base64url 解码前两部分,重点关注以下关键字段:
| 字段 | 属于 | 作用与测试切入点 |
|---|---|---|
alg |
Header | 签名算法(关键攻击点:none 绕过、密钥混淆、弱算法) |
jku / jwk |
Header | 动态密钥/公网密钥检索 URL(关键攻击点:SSRF、任意密钥注入) |
kid |
Header | 密钥标识符 Key ID(关键攻击点:SQLi、目录遍历、命令注入) |
exp / nbf |
Payload | 过期时间 / 生效时间(关键攻击点:Token 永久有效、重放攻击) |
role / admin |
Payload | 用户权限声明(关键攻击点:未验证签名时的垂直越权) |
2. JWT 核心攻击向量与实战思路
向量一:客户端未验证签名 (Unverified Signature)
这是最低级但也最常见的实现错误。部分开发者在后端仅使用了 jwt.decode() 提取 Payload 数据,而未调用 jwt.verify() 校验签名。
-
测试方法:
-
截获合法 JWT,解码 Payload。
-
修改 Payload 中的身份数据(例如将
"user_id": 102改为"user_id": 1或"is_admin": true)。 -
保留原签名或直接删除签名发送请求。
-
-
判定结果:若接口成功返回敏感数据或执行了管理员操作,即存在越权漏洞。
向量二:none 算法绕过 (None Algorithm Flaw)
JWT 规范允许设置 "alg": "none",表示该 Token 无需签名。如果后端验证库配置不当,会误将带有 none 算法的伪造 Token 视为合法。
-
测试方法:
-
修改 Header 中的
alg为none(尝试变体:None,NONE,nOnE)。 -
修改 Payload 中的权限数据。
-
移除最后的 Signature 部分 (但必须保留末尾的点号
.,即Header.Payload.)。
-
-
自动化 PoC 脚本(Python):
import base64
import jsonheader = {"alg": "none", "typ": "JWT"}
payload = {"sub": "admin", "admin": True}def b64_encode(data):
return base64.urlsafe_b64encode(json.dumps(data).encode()).decode().rstrip("=")jwt_none = f"{b64_encode(header)}.{b64_encode(payload)}."
print("PoC JWT:", jwt_none)
向量三:对称加密弱密钥爆破 (Weak HMAC Secret)
在使用 HS256 (HMAC-SHA256) 等对称加密算法时,签名和验签使用的是同一个密钥(Secret)。如果开发者设置的 Secret 过短或属于常用字典词汇,攻击者可直接离线爆破。
-
实战工具:Hashcat
-
将获得的完整 JWT 存入
jwt.txt。 -
使用 Hashcat 进行字典爆破:
hashcat -m 16500 jwt.txt rockyou.txt -
一旦爆破出密钥(如
secret123),即可在本地使用该密钥对任意伪造的 Payload 重新签名,实现完全接管。
-
向量四:算法混淆攻击 (Algorithm Confusion / Key Confusion)
这是 JWT 密码学攻击中最经典的一类。漏洞起源于:服务端本应使用 RS256 (非对称加密:私钥签名,公钥验签),但攻击者强行将 Header 改为 HS256(对称加密)。
-
原理:
-
RS256 下,服务器会在本地存储一个公钥
public.pem用于验签。 -
若服务器把算法误当作 HS256 处理,它会把
public.pem的文件文本内容当成对称加密的 HMAC Secret。 -
因为公钥通常是公开的(例如可以通过
/.well-known/jwks.json或前端静态文件直接获取),攻击者可以使用该公钥作为 HMAC 密钥在本地签发恶意 Token!
-
-
攻击步骤:
-
获取目标的公开公钥文件(如
public.pem)。 -
修改 Header 中的
"alg": "HS256"。 -
修改 Payload(如提权为 admin)。
-
使用
public.pem文本字符串 作为密钥,用 HS256 算法对 Token 进行签名。
-
向量五:Header 参数注入 (Header Parameter Injections)
JWT Header 中的部分可选参数具备强大的解析能力,若后端处理不当,会引入额外的漏洞链:
1. kid (Key ID) 注入
kid 用于指定服务器数据库或文件系统中对应密钥的 ID。
-
目录遍历 (Path Traversal) :若后端通过
kid拼接文件路径读取密钥,可构造"kid": "../../../../../dev/null",此时密钥内容为空(""),攻击者可用空字符串签名 Token。 -
SQL 注入 :若
kid从数据库查询密钥,可构造"kid": "key1' UNION SELECT 'my_secret'--",将密钥强制指定为my_secret。 -
命令注入 :若
kid传入了系统命令处理函数,可尝试; command injection。
2. jku (JWK Set URL) Header 伪造
jku 允许 Token 指定获取公钥的远程 URL。
-
攻击方式 :攻击者在自己的服务器上挂载恶意
jwks.json,并将 Token Header 中的jku修改为[https://evil.com/jwks.json](https://evil.com/jwks.json)。若后端未对jku域名做白名单校验,就会读取攻击者的公钥来验证攻击者伪造的 Token。 -
延伸攻击 :若服务端开启了域名校验但校验逻辑不严,可尝试 SSRF、URL 绕过(如
[https://trust.com@evil.com](https://trust.com@evil.com))等。
向量六:生命周期与重放攻击 (Token Lifetime & Revocation)
-
永不过期 :检查 Payload 中的
exp字段是否存在。若不存在,说明 Token 永久有效。 -
修改过期时间 :尝试将
exp修改为未来的时间戳,观察服务器是否仍接收签名未变的修改版(验证是否做过签名校验)。 -
注销不失效 (Lack of Blacklist):在前端点击"注销/退出登录"后,提取注销前的旧 Token 再次发送请求。若仍能访问,说明后端未建立有效的 Token 黑名单机制(如 Redis 缓存注销 Token)。
3. JWT 渗透测试标准 Checklist
在针对 JWT 进行黑盒/白盒测试时,可按照以下流程图进行快速排查:

4. 防御与安全修复方案
要彻底修复 JWT 相关漏洞,开发团队应遵循以下防护原则:
-
强强制签名校验 :永远不要依赖 JWT Header 传入的
alg参数。后端代码必须显式写死期望使用的算法(例如强制限制为RS256)。 -
严禁使用
none算法 :在 JWT 解析库中禁用none算法支持。 -
保障密钥强度与安全:
-
对称加密(HS256)必须使用至少 256 位(32 字节)的高熵随机密钥。
-
非对称加密(RS256)应妥善保管私钥,公钥校验时严格限定算法类型。
-
-
严格校验 Header 参数 :若使用
jku,必须建立严格的 URL 白名单;若使用kid,必须进行严格的净化(Sanitize)与参数化查询,防止路径遍历或 SQL 注入。 -
完善注销机制:结合 Redis 等内存数据库建立 Token 黑名单(或短生存周期 + Refresh