JWT(JSON Web Token)中alg字段被置为none时,攻击者可以绕过签名验证直接伪造任意用户身份,这是一个高危漏洞。具体来说,当服务端在解析JWT时没有强制校验算法类型,攻击者只需将token头部的alg改为"none"、签名部分清空,再用任意payload重新编码,就能以管理员身份登录系统。修复方法很直接:服务端必须白名单限定允许的算法(如HS256、RS256),拒绝接受alg为none或其他未预期算法的token,同时确保密钥足够复杂且不泄露。
这个漏洞在实际渗透测试中出现频率极高,尤其是在使用Java、Node.js、Python等语言开发的Web应用中。很多开发者只关注了JWT的生成和验证逻辑,却忽略了对alg字段的严格校验。下面我从漏洞原理、复现步骤、影响范围、修复方案和防御建议五个维度,把这个问题讲透。
一、JWT基本结构与alg字段的作用JWT由三部分组成:Header(头部)、Payload(载荷)、Signature(签名),用点号分隔。Header中包含两个关键信息:token类型(typ)和签名算法(alg)。alg字段告诉服务端用什么算法来验证签名的合法性。
正常情况下,一个JWT长这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
解码后的Header是:
{
"alg": "HS256",
"typ": "JWT"
}
alg字段的设计初衷是让服务端知道该用哪种算法去验证签名。但问题在于,如果服务端代码没有对alg做白名单限制,攻击者就可以把它改成"none",表示"这个token没有签名",服务端如果照单全收,漏洞就产生了。
二、漏洞复现:完整操作步骤我用一个实际场景来演示。假设目标网站登录后返回一个JWT token,我们抓包拿到原始token。
第一步:解码原始token,查看结构。可以用在线工具或者命令行工具jwt.io来解码。假设解码后得到:
{
"alg": "HS256",
"typ": "JWT"
}
{
"sub": "user123",
"role": "user",
"exp": 1700000000
}
第二步:修改Header,将alg改为none。新的Header为:
{
"alg": "none",
"typ": "JWT"
}
第三步:将修改后的Header和Payload用Base64Url编码,签名部分直接留空(因为alg是none,不需要签名)。拼接格式为:Base64Url(Header).Base64Url(Payload).
第四步:构造恶意Payload。把role改成admin,sub改成admin:
{
"sub": "admin",
"role": "admin",
"exp": 1700000000
}
第五步:用Python脚本快速生成伪造token:
import base64
import json
header = {"alg": "none", "typ": "JWT"}
payload = {"sub": "admin", "role": "admin", "exp": 1700000000}
def base64url_encode(data):
json_str = json.dumps(data, separators=(',', ':'))
return base64.urlsafe_b64encode(json_str.encode()).rstrip(b'=').decode()
token = f"{base64url_encode(header)}.{base64url_encode(payload)}."
print(token)
第六步:将伪造的token放入请求头的Authorization字段,发送请求。如果服务端存在漏洞,直接以admin身份通过验证。
这个复现过程在多个开源JWT库中都能成功,包括Java的jjwt、Node.js的jsonwebtoken(旧版本)、Python的PyJWT等。关键不在于库本身有问题,而在于开发者使用时没有正确配置算法校验。
三、哪些场景和语言最容易中招从我做安全审计的经验来看,以下几种情况最容易出现这个漏洞:
1、Java后端使用JJWT库时,如果调用parse()方法而没有指定expectedAlg参数,库默认会接受alg为none的token。早期版本的jjwt甚至在文档中没有强调这一点。
2、Node.js中使用jsonwebtoken库时,如果verify函数没有传入algorithms选项,或者传入了一个包含"none"的数组,就会出问题。特别是一些老项目升级依赖后,默认行为可能发生变化。
3、Python的PyJWT库在某些版本中,如果不显式指定algorithms参数,也可能接受none算法。虽然新版本已经修复,但很多项目还在用旧版本。
4、自研的JWT验证逻辑。有些团队不用成熟库,自己写解析代码,如果只判断签名是否存在而不校验alg字段,那就是在给攻击者开后门。
5、微服务架构中,网关层做了JWT校验但内部服务没有二次校验,攻击者伪造的token可能在网关就被放行,导致整个内部服务链路被突破。
四、漏洞的真实危害到底有多大这个漏洞的危害等级我给它定为高危甚至严重,原因有三点。
第一,攻击门槛极低。不需要SQL注入、不需要文件上传、不需要复杂的漏洞利用链,只要能拿到一个合法token的结构,改几个字段就能伪造。甚至有些情况下,攻击者根本不需要原始token,只要知道网站用了JWT,就可以从零构造。
第二,影响范围是身份认证体系。一旦攻击者能伪造admin token,就意味着他可以访问所有管理员功能:用户管理、数据导出、系统配置修改、甚至删除其他用户。在SaaS平台上,这可能导致所有租户数据泄露。
第三,隐蔽性强。伪造的token在日志中看起来和正常token格式一样,如果没有专门的监控告警,很难被发现。很多企业被入侵后很长时间才察觉,就是因为这种低痕迹的攻击方式。
五、修复方案:从代码层面堵住漏洞修复这个漏洞的核心原则只有一条:服务端必须白名单校验alg字段,绝不信任客户端传来的算法声明。
Java(JJWT)修复示例:
Jwts.parser()
.setSigningKey(secretKey)
.requireAlgorithm("HS256") // 明确指定只接受HS256
.parseClaimsJws(token);
注意这里用的是requireAlgorithm而不是setAlgorithm,前者是强制校验,后者只是设置期望值。如果token中的alg和期望值不匹配,会直接抛异常。
Node.js修复示例:
const jwt = require('jsonwebtoken');
jwt.verify(token, secretKey, {
algorithms: ['HS256'] // 白名单,只接受HS256
}, (err, decoded) => {
if (err) {
return res.status(401).json({ error: 'Invalid token' });
}
// 正常处理
});
Python修复示例:
import jwt
try:
payload = jwt.decode(
token,
secret_key,
algorithms=['HS256'] # 必须指定允许的算法列表
)
except jwt.InvalidAlgorithmError:
# alg不在允许列表中,直接拒绝
raise Exception("Unsupported algorithm")
如果你是自研解析逻辑,那就更简单了:在验证签名之前,先读取Header中的alg字段,判断它是否在你的白名单里。不在就直接返回401,不要继续后续任何操作。
六、纵深防御:不要只靠一个点光修代码还不够,我建议从以下几个层面建立纵深防御。
1、密钥管理。JWT的签名密钥必须足够长(HS256至少256位),定期轮换,不要硬编码在代码里,用环境变量或密钥管理服务来存储。
2、Token有效期控制。Payload中的exp字段不要设得太长,访问令牌建议15分钟到1小时,刷新令牌可以长一些但要有单独的撤销机制。
3、敏感操作二次验证。即使token没问题,涉及修改密码、删除数据、转账等操作时,要求输入密码或短信验证码,防止token被盗后直接造成损失。
4、日志监控。对JWT验证失败的请求做告警,尤其是alg字段异常的情况。如果短时间内出现大量alg为none或未知算法的验证请求,大概率是有人在探测或攻击。
5、依赖库版本管理。定期检查项目中JWT相关库的版本,及时更新到修复了已知问题的版本。很多漏洞在新版本中已经被修复,但老项目没跟上。
6、安全编码规范。团队内部制定明确的安全编码规范,把"JWT验证必须白名单校验alg"写进去,代码审查时作为必查项。
七、总结与建议JWT alg置none漏洞看似简单,但它暴露的是开发者对安全细节的忽视。这个漏洞不需要高深的技术就能利用,却能造成严重的身份伪造后果。对于安全从业者来说,这是渗透测试中必查的项目;对于开发者来说,这是写JWT验证代码时必须守住的底线。
记住一句话:永远不要信任客户端告诉你的算法类型。服务端自己说了算,白名单写死,其他一律拒绝。做到这一点,这个漏洞就跟你没关系了。
