深入了解Json Web Token之实战篇
字数 2714 2025-08-18 11:37:33
JSON Web Token (JWT) 安全攻防实战指南
1. JWT 基础概念回顾
JSON Web Token (JWT) 是一种开放标准 (RFC 7519),用于在各方之间安全地传输信息作为 JSON 对象。JWT 通常由三部分组成:
- Header (头部)
- Payload (负载)
- Signature (签名)
格式为:header.payload.signature
2. JWT 攻击手段详解
2.1 敏感信息泄露
攻击原理:当服务端的秘钥泄密时,JWT 的伪造变得非常简单。
防御措施:
- 妥善保管私钥
- 使用密钥管理系统
- 定期轮换密钥
2.2 将加密方式改为'none'
攻击原理:
- 将 alg 字段更改为 none
- 删除签名数据,仅保留
header.payload. - 某些 JWT 库支持无算法验证
防御措施:
- 禁用 none 算法
- 将开启 alg:none 作为额外配置选项
- 强制验证签名
实战案例:Juice Shop JWT issue 1
- 获取正常 JWT 令牌
- 修改头部为
{"alg":"none","typ":"JWT"} - 删除签名部分
- 提交修改后的令牌
2.3 将算法 RS256 修改为 HS256
攻击原理:
- RS256 使用非对称加密(私钥签名,公钥验证)
- HS256 使用对称加密(同一密钥签名和验证)
- 攻击者获取公钥后,可修改算法为 HS256 并用公钥签名
防御措施:
- 禁止 HS256 等对称加密算法读取公钥
- 将秘钥与验证算法严格匹配
- 检查密钥类型与算法是否匹配
2.4 HS256 密钥破解
攻击原理:当 HS256 密钥强度较弱时,可直接暴力破解。
防御措施:
- 使用足够复杂(长且随机)的密钥
- 定期更换密钥
- 使用密钥派生函数增强密钥强度
实战工具:
- c-jwt-cracker:专门用于爆破 JWT HS256 密钥的工具
2.5 错误的堆叠加密+签名验证假设
2.5.1 错误的堆叠加密
攻击原理:
- 修改 JWE (JSON Web Encryption) 的 ciphertext
- 即使解密失败,部分库仍会输出部分解密结果
- 可能获得修改后的特权字段
防御措施:
- 解密所有数据,而非提取单个字段
- 使用附加认证数据 (ADD)
- 严格验证解密完整性
2.5.2 签名假设验证
攻击原理:
- 嵌套 JWS (JSON Web Signature) 中,仅验证外层签名
- 内层 payload 可被篡改
防御措施:
- 验证所有层面的签名
- 禁止不必要的数据嵌套
2.6 无效椭圆曲线攻击
攻击原理:
- 利用椭圆曲线加密中未验证输入参数的漏洞
- 通过精心设计的输入恢复私钥
防御措施:
- 验证所有输入参数的有效性
- 检查公钥是否为有效椭圆曲线点
- 验证私钥在有效值范围内
2.7 替换攻击
2.7.1 不同接收方攻击
攻击原理:
- 同一认证机构为多个应用签发 JWT
- 攻击者用 App1 的令牌访问 App2
防御措施:
- 使用 aud (Audience) 声明限制令牌使用范围
- 如
"aud": "App1"
2.7.2 相同接收方攻击/跨越式 JWT
攻击原理:
- 同一站点下多个应用使用相同密钥
- 部分应用未验证 aud 声明
- 攻击者用受限应用的令牌访问未验证应用
防御措施:
- 所有子应用强制验证 aud 声明
- 为不同应用使用不同密钥
2.8 JWT + SQL 注入
攻击原理:
- 通过 kid (Key ID) 参数确定使用哪个密钥
- 当 kid 通过数据库查询获取时,可能引入 SQL 注入
- 注入恶意查询控制返回的密钥内容
防御措施:
- 对 kid 参数严格过滤
- 使用参数化查询访问数据库
- 限制数据库账户权限
3. 实战练习详解
3.1 敏感信息泄露实战
环境:http://demo.sjoerdlangkemper.nl/jwtdemo/hs256.php
步骤:
-
获取公钥/私钥:
- http://demo.sjoerdlangkemper.nl/jwtdemo/private.pem
- http://demo.sjoerdlangkemper.nl/jwtdemo/public.pem
-
使用私钥生成新令牌:
$keychain = new Keychain();
$sign = new Sha256();
$token = "eyJ0eXAiO...";
$token = (new Parser())->parse((string) $token);
$hacktoken = (new Builder())
->setIssuer($token->getClaim('iss'))
->setIssuedAt($token->getClaim('iat'))
->setExpiration($token->getClaim('exp'))
->set("data",["hack"=>"shaobaobaoer"])
->sign($sign,$keychain->getPrivateKey('file://key_box/private.pem'))
->getToken();
echo $hacktoken.PHP_EOL;
var_dump($hacktoken->verify($sign,$keychain->getPublicKey('file://key_box/public.pem')));
3.2 Juice Shop JWT issue 1
目标:伪造无签名 JWT 令牌,冒充用户 jwtn3d@juice-sh.op
步骤:
- 获取正常 JWT 令牌
- 修改头部为
{"alg":"none","typ":"JWT"} - 修改 payload 中的 email 字段
- 删除签名部分,格式为
header.payload. - 提交修改后的令牌
3.3 加密方式更改实战
原理:将 RS256 改为 HS256,用公钥作为 HMAC 密钥
示例代码:
$secret = file_get_contents("./key_box/public.pem");
$sign = new Sha256();
$token = "eyJ0eXAiO...";
$token = (new Parser())->parse((string) $token);
$hacktoken = (new Builder())
->setIssuer($token->getClaim('iss'))
->setIssuedAt($token->getClaim('iat'))
->setExpiration($token->getClaim('exp'))
->set("data",["hack"=>"shaobaobaoer"])
->sign($sign,$secret)
->getToken();
echo $hacktoken.PHP_EOL;
var_dump($hacktoken->verify($sign,$secret));
3.4 HMAC 秘钥爆破实战
工具:c-jwt-cracker
步骤:
- 获取 JWT 令牌,如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ImZhbHNlIn0.oe4qhTxvJB8nNAsFWJc7_m3UylVZzO3FwhkYuESAyUM - 使用工具爆破密钥:
./jwtcrack eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6ImZhbHNlIn0.oe4qhTxvJB8nNAsFWJc7_m3UylVZzO3FwhkYuESAyUM - 获取密钥后(如"54l7y"),使用正确密钥生成新令牌
4. 防御最佳实践
-
算法处理:
- 明确指定允许的算法
- 禁止 none 算法
- 区分对称和非对称密钥使用场景
-
密钥管理:
- 使用足够强度的密钥
- 定期轮换密钥
- 安全存储私钥
-
声明验证:
- 验证 iss (Issuer)、aud (Audience) 等标准声明
- 验证 exp (Expiration Time)、nbf (Not Before) 等时间声明
- 自定义声明也需验证
-
库选择:
- 选择维护良好、安全记录良好的库
- 定期更新库版本
- 评估库的安全特性
-
其他措施:
- 使用 HTTPS 传输 JWT
- 考虑短期有效的令牌
- 实现令牌撤销机制
5. 推荐的 JWT 库
PHP 库评测:
-
firebase/php-jwt (★3786)
- 支持 PHP5/7
- 操作简单但功能有限
-
lcobucci/jwt (★2729)
- 支持 PHP5/7
- 不支持 JWE
- 操作简单
-
spomky-labs/jose (★351)
- 仅支持 PHP7
- 功能齐全,支持多重 JWE/JWS 及序列化
6. 参考资料
通过理解这些攻击手段和防御措施,开发人员可以更安全地实现 JWT,安全研究人员也可以更有效地测试 JWT 实现的安全性。