先记住这个答案
JWT验证中alg=none攻击指攻击者删去签名并将alg改为none,若服务端未禁用none则接受。RS256/HS256混淆指攻击者用公钥作为HS256密钥对token签名,若服务端允许HS256并误用公钥校验则通过。防御:服务端只接受白名单算法,且校验时必须按算法类型使用对应密钥,不信任头部字段。
- 必须禁用none且白名单算法
- 公钥绝不可用作HMAC密钥
- 验证前强制检查alg字段
两类篡改手法解析
JWT由头部、载荷、签名三部分构成,验证时取头部alg的算法值来验签。若服务端不限制alg取值,攻击者可把alg改为none并去掉签名段,有些库会跳过校验直接信任。更隐蔽的是算法混淆:一个用RS256签发的合法token,攻击者修改alg为HS256,并用服务端公钥作HMAC密钥重签,因服务端多数持有公钥,若误允许HS256,验证就会通过。
根因是服务端未将算法选择视为不可信输入。正确做法是验证前先检查alg是否在预设白名单,并且算法对应的密钥类型必须明确:RS256对应公钥验签,HS256对应共享密钥,不能混用。RFC 7518也建议明确声明kid并绑定密钥。
一个被绕过的实际配置
某API使用RSA私钥签发token,验证中间件配置为“支持RS256和HS256”以便兼容旧客户端。攻击者截获一个合法RS256 token,把alg从RS256改成HS256,然后从开放接口拿到该应用的公钥文件,用公钥字符串作密钥,按HS256算法重新计算签名,替换后发送。
服务端在验签时,头部的alg只告诉它用HS256,于是它拿配置中的共享密钥去校验,而配置中共享密钥常被错误地等于公钥内容,结果验签通过,攻击者成功伪装。修复办法是只保留RS256一个算法,并验证kid指向RS256专用密钥,彻底关闭HS256入口。
适用与失效边界
算法混淆攻击的前提是服务端在验签逻辑中同时接受对称与非对称算法,且将验签密钥暴露给客户端或可从公共渠道获得。如果服务端只用独立的共享密钥做HMAC,不涉及公钥,就没法混淆;如果已锁定单一alg,攻击会因白名单拒绝而失败。
但完全依赖白名单并非万无一失:若密钥泄露、库解析缺陷或错误处理绕过都可使防护失效。因此还需配合签名算法强制声明、JWT库安全配置以及监控异常alg组合。防御成本低,嵌入开发规范即可,但需对所有历史签发token同步检查。
容易答错的地方
- alg=none只影响老库
- 误区:alg=none只在老版本库中有效,现代库已免疫。纠正:虽然官方库默认禁用none,但不少开发者在验证逻辑中手动处理异常,或自定义解密路径时忽略校验,仍可能触发。只要头部可控,就需显式拒绝。
- 混淆仅限RS256/HS256
- 误区:算法混淆只存在于RS256/HS256之间。纠正:任何非对称算法与对称算法组合都可能,如ES256与HS256同理。关键在于服务端是否接受多种算法并复用同一公钥当对称密钥。
面试官还会怎么问?
如何编写安全的JWT验证逻辑?
固定允许算法列表,如['RS256'],先校验alg属于该列表再匹配公钥;避免动态分支使用头部token。同时为每个密钥绑定一个kid,并用kid找到对应公钥,公钥与算法强关联。
HS256密钥与RS256公钥泄露的后果有何区别?
RS256公钥可公开,但只能验签不能签名;HS256密钥是共享机密,泄露后攻击者可任意签发。混淆攻击正是把公钥当对称密钥,使公钥变相成为可签名机密,破坏认证完整性。
必须同时支持多种算法时如何防御?
用kid作为唯一分发索引,每个kid明确算法类型和密钥材料,服务端根据kid直接取密钥对象,不允许前端指定算法。同时安装加密库并开启强制算法保护,拒绝未知alg。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。