JWT 安全漏洞与完整防护方案
一、JWT 基础与原理
JWT(JSON Web Token)是目前最流行的跨域认证解决方案,广泛应用于前后端分离项目、移动 APP、微服务架构等场景。JWT 是一个紧凑的、URL 安全的令牌,由三部分组成:Header(头部,声明类型和签名算法)、Payload(载荷,存放用户信息和声明)、Signature(签名,验证令牌完整性)。三部分用点号连接,格式为xxxxx.yyyyy.zzzzz。
工作原理:用户登录成功后,服务器生成一个 JWT 令牌返回给客户端;之后客户端每次请求都带上这个 JWT;服务器收到请求后,验证 JWT 的签名是否有效,如果有效就信任令牌中的用户信息,不需要再查数据库。JWT 的优点是无状态、易扩展、支持跨域,非常适合分布式系统和微服务架构。但 JWT 的安全性非常依赖正确的实现和配置,如果使用不当,会存在很多安全漏洞,攻击者可以伪造令牌、篡改信息、越权访问,甚至直接登录任意用户账号。JWT 安全是 Web 安全和 API 安全中非常重要的一块,也是渗透测试的重点项。
二、JWT 常见安全漏洞
1. 签名未校验(最严重):服务器收到 JWT 后,根本不验证签名是否正确,直接解析 Payload 中的用户信息。攻击者可以随意伪造任意用户的 JWT,想登录谁就登录谁,危害极大。这种漏洞虽然低级,但在实际项目中并不少见,尤其是一些赶工期的项目或者新手开发的系统。
2. 算法混淆攻击(Algo Confusion):JWT 的 Header 中声明了签名算法(alg字段),比如 HS256(对称加密)、RS256(非对称加密)。如果服务器端没有强制指定验证算法,而是相信 JWT Header 中的 alg 字段,攻击者就可以把算法改成 none(无签名),或者把非对称算法改成对称算法,用公钥作为密钥来伪造签名,从而绕过验证。特别是 none 算法攻击——把 alg 改成 none,然后去掉签名部分,有些 JWT库遇到 none 算法就会跳过签名验证,直接认为令牌有效。这是非常经典的 JWT 漏洞。
3. 弱密钥/密钥泄露:对于 HS256 等对称加密算法,签名和验证用的是同一个密钥。如果密钥太弱(比如 123456、admin 等弱口令),攻击者可以通过爆破的方式破解密钥;如果密钥泄露了,攻击者就可以用密钥伪造任意 JWT。很多开发者为了图方便,用很简单的密钥,或者把密钥硬编码在代码里,很容易泄露。
4. 敏感信息泄露:JWT 的 Payload 部分只是 Base编码的,不是加密的!任何人拿到 JWT 都可以解码看到 Payload 里的所有内容。很多开发者误以为 JWT 是加密的,把用户密码、身份证号、手机号、内部角色等敏感信息直接放在 Payload 里,导致信息泄露。记住:JWT 的内容是公开的,只是通过签名保证了完整性和防篡改,不保证保密性。
5. 令牌无法吊销(JWT 天生缺陷):JWT 是无状态的,一旦签发,在过期之前一直有效,服务器没有办法主动吊销某个 JWT。如果用户退出登录、修改密码、权限变更,或者 JWT 被盗了,原来的令牌仍然可以用,直到过期。这是 JWT 的天生缺陷,也是很多安全问题的根源。
6. 过期时间设置过长:有些系统为了用户体验,把 JWT 的过期时间设得很长,比如几天、几周甚至几个月。这样令牌被盗用后,攻击者可以用很长时间,危害很大。还有的系统干脆不设过期时间,令牌永久有效,这是非常危险的。
7. 重放攻击:攻击者截获了用户的 JWT,就可以反复使用这个令牌来访问系统,直到令牌过期。如果没有防重放机制,被盗的令牌可以被一直滥用。
8. Payload 可篡改(签名失效的情况下):如果签名验证有问题,攻击者可以篡改Payload 中的内容,比如把用户 ID 改成别人的、把角色改成管理员、提升权限等,实现越权访问。
三、JWT 完整防御方案
1. 强制验证签名(最基本最重要):服务器端必须严格验证 JWT 的签名,签名无效的令牌一律拒绝。不要自己实现 JWT 验证逻辑,用成熟的、经过验证的 JWT 库,并且正确配置。很多 JWT 漏洞都是因为自己写验证逻辑或者配置错误导致的。
2. 强制指定算法,禁止 none 算法:服务端验证时必须强制指定使用的签名算法,不能相信 JWT Header 中的 alg 字段。比如后端用的是 RS256,验证时就明确指定用RS256 验证,不管 Header 里写的是什么算法。同时明确禁止 none 算法,防止算法混淆攻击。
3. 使用强密钥,妥善保管密钥:对于对称加密算法(HS256 等),密钥要足够长、足够随机,不要用弱密钥。密钥要妥善保管,不要硬编码在代码里,不要提交到代码仓库,用配置中心、环境变量等安全方式管理。定期轮换密钥,降低泄露后的影响。对于重要系统,建议使用非对称加密算法(RS256、ES256 等),私钥只在签发端保存,验证端只用公钥,更安全。
4. Payload 不放敏感信息:永远记住 JWT 的 Payload 是公开的,只是 Base编码,谁都能解码。不要把密码、身份证、手机号、银行卡号等信息放在 Payload里。Payload 里只放必要的用户标识信息,比如 user_id、username、role 等,而且这些信息也尽量不要放太敏感的。
5. 设置合理的过期时间:JWT 的有效期要设置得短一些,比如 15 分钟到 2 小时,根据业务安全等级调整。令牌有效期越短,被盗用后的危害时间越短。对于需要长时间登录的场景,可以用 Refresh Token 机制来刷新 Access Token,Access Token设短有效期,Refresh Token 设长有效期,并且 Refresh Token 可以被吊销。
6. 实现令牌吊销机制(解决无状态问题):虽然 JWT 天生无状态,但可以通过一些方案实现令牌吊销:
- 黑名单方案:把需要吊销的令牌存入黑名单(Redis 等),验证时检查令牌是否在黑名单中。可以只存过期前的令牌,过期后自动清理,不会无限增长。
- 版本号方案:用户信息或权限变更时,更新用户的 token 版本号,JWT 里带上版本号,验证时比对版本号,不一致就拒绝。
- 短有效期+刷新令牌:把 Access Token 有效期设得很短,即使被盗用也很快失效,配合 Refresh Token 来续期,Refresh Token 可以被服务端吊销。
7. 防重放攻击:可以在 JWT 中加入 jti(JWT ID)字段,每个令牌有唯一 ID,配合黑名单机制,每个令牌只能用一次或者在一定时间内有效。或者结合请求时间戳、nonce 随机数等方式防止重放。
8. 加强传输安全:JWT 必须通过 HTTPS 传输,防止在传输过程中被窃听。不要把JWT 放在 URL 参数中(容易通过 Referer 泄露),建议放在 Authorization 请求头中,或者放在 HttpOnly 的 Cookie 中(可以防 XSS 窃取)。
9. 权限二次校验:不要完全信任 JWT 中的角色和权限信息,关键操作还要从数据库或缓存中二次校验用户的权限状态。特别是高风险操作,不能只靠 JWT 里的信息就放行。
10. 安全审计与测试:定期对 JWT 的实现进行安全测试,检查是否存在签名未校验、算法混淆、弱密钥等常见漏洞。使用自动化安全扫描工具辅助检测,但更重要的是人工渗透测试。
四、常见误区
JWT 是加密的,里面的内容别人看不到:大错特错!JWT 只是 Base编码,谁都能解码看内容,签名只是防篡改,不是加密
用了 JWT 就绝对安全:JWT 只是一种认证方案,用不好漏洞百出
JWT 无状态所以没法吊销:可以通过黑名单、版本号等方案实现吊销,只是需要额外设计
令牌有效期设长一点用户体验好:安全和体验要平衡,重要系统必须设短有效期 用了 HTTPS 就不怕 JWT 泄露:HTTPS 防传输窃听,但防不了 XSS 窃取、防不了用户自己泄露
对称加密和非对称加密随便用哪种都行:重要系统、多服务场景建议用非对称加密,更安全
五、漏洞检测方法
1. 签名测试:把 JWT 的签名部分删掉或者改成错的,看服务器是否还接受,如果接受说明没有校验签名;
2. 算法混淆测试:把 alg 改成 none,去掉签名,看服务器是否接受;把 RS256改成 HS256,用公钥当密钥签名,看是否能绕过;
3. 密钥强度测试:如果是对称算法,尝试用常见弱密钥爆破,看能否破解;
4. 敏感信息检测:解码 JWT 的 Payload,看是否包含信息;
5. 过期时间测试:检查 JWT 的 exp 字段,看过期时间是否合理;
6. 吊销测试:用户退出登录或修改密码后,原来的令牌是否还能使用;
7. 越权测试:篡改 Payload 中的用户 ID、角色等信息,看是否能越权访问(前提是签名验证有问题或者服务端没二次校验)。
