文章声明:本篇文章内容部分选取网络,如有侵权,请告知删除。
一、介绍
JWT(JSON Web Token)是一种轻量级、自包含的令牌,用于在客户端与服务端之间安全传递身份声明。它由三部分组成:
-
Header(头部)——声明类型与签名算法
-
Payload(负载)——携带用户身份与附加元数据
-
Signature(签名)——对前两部分的防篡改校验
二、核心风险:越权登录 → 高危
JWT 的“无状态”特性把会话控制权完全交给令牌本身。一旦攻击者通过“伪造/篡改/暴力”手段让服务端接受任意 Payload,就能实现“任意账号登录”,在 SRC 评级中直接列为【高危】。与 Cookie 相比,JWT 的漏洞影响面更广:
-
Cookie 越权通常只能影响同站用户
-
JWT 越权可跨接口、跨服务,甚至横向移动至整个微服务集群
三、关键字段:kid(Key ID)
kid 位于 Header,用于告诉服务端“本次签名用的哪把密钥”。若服务端存在以下实现缺陷,攻击者可重置 kid 指向攻击者可控的密钥,从而通过签名验证:
-
kid 可遍历(001、002…)
-
kid 指向外部 URL(如 jku/x5u)且未校验域名
-
kid 指向服务端本地文件,存在目录穿越(../../../public.pem)
-
支持 “none” 算法或公钥作为 HMAC 对称密钥(算法混淆)
四、一句话总结
JWT 把“身份”写在令牌里,谁改认谁;kid 是“签名钥匙的门牌号”,指错门就让攻击者登堂入室。守住 kid、守死算法、管住密钥,才能把“无状态”做成“无风险”。
在实际应用中,可能会使用多个密钥来对T进行签名,这些密钥可以有不同的用途、算法或者安全等级,为了方便管理这些密钥,通常会将其存储在一个密钥库中,并为每个密钥分配一个唯一的 ID。
当需要对 JNT进行签名时,发件人可以从密铜库中选择相应的密钥,并在WT的头部中指定该密钥的ID(kid)。
接收人在验证 NT签名的有效性时,可以根头部中指定的 kd 值选择相应的密钥进行验证签名。
这样,通过指定 kid 可以方便地管理多个密钥,并且可以避免密钥被盗用或者误用。
需要注意的是,kid 只是一个标识符,不包含密钥本身,因此需要确保 kid 的安全性,避免泄漏或者伪造。
【优化版 · 一句话看懂】
JWT 只负责“令牌本身不被改”,不保证“传输过程不被改”;谁持有令牌,谁就能进门。
1. 登录
客户端:POST 账号+密码
服务端:验账号→生成 JWT(Header.Payload.Signature)
2. 返回
服务端:把 JWT 放进响应体或 Set-Cookie
客户端:存本地(localStorage / Cookie / 内存)
3. 请求
客户端:后续请求带 Authorization: Bearer <JWT>
(只保护 JWT 字符串,其余 HTTP 字段任意改)
4. 验签
服务端:
– 切分三段 → 用密钥验签
– 签名有效 → 信任 Payload 里的“身份声明”
– 签名无效 → 直接 401
5. 授权
服务端:从 Payload 取出 uid/role,再做一次业务层权限校验(越权拦截在此处完成,JWT 不负责)
【关键提醒】
– 防篡改范围:Header+Payload 的签名值;HTTP 报文其余部分不在保护之列。
– 传输安全:必须加 TLS,否则 JWT 被重放即可直接通过验签。

【一句话区分】
JWT 是“身份证”,JWS 是“防撕封条”,JWE 是“密码信封”。
【对照表 · 一眼看懂】
|
名称 |
核心作用 |
结构 |
防护点 |
常见场景 |
|
JWT |
携带声明(谁、什么角色、有效期) |
Header.Payload.Signature |
防令牌本身被篡改 |
登录态、API 授权 |
|
JWS |
给任意数据加签名,验完整性 |
Header.Payload.Signature(Payload 可任意二进制) |
防“这段数据”被篡改 |
公开接口回调、Webhook、License |
|
JWE |
给任意数据加密,防偷看 |
Header.EncryptedKey.IV.Ciphertext.Tag |
防“这段数据”被泄露 |
传输敏感 PII、医疗记录、信用卡号 |
【易错点纠正】
-
JWS 并非“没有载荷”,而是“载荷可以是任意内容”,签名仍只覆盖 Header+Payload。
-
“签名防整个数据包”说法错误——签名只保护被签的那一段;想保护全包请再套 TLS。
-
JWT 可以嵌进 JWS(签)或 JWE(加密),也能先签后加密,形成 Nested JWT。
【速记口诀】
JWT 里装声明,JWS 加签名,JWE 上锁寄。
【一句话总结】
把“解码”当“验签”或把“alg:none”当合法,就等于把门禁当摆设,任意改 Payload 就能换身份。
【原理拆解 · 两张图】
-
解码≠验权——“裸奔”越权
代码坑:
const jwt = require('jsonwebtoken');const token = req.headers.authorization;// 坑:只解码,不验签const user = jwt.decode(token);攻击:
把令牌扔 Burp → 改 Payload 里的 user_id:1001→1 → 重放 → 直接拥有 1 号管理员身份。
-
alg=none——“自带万能钥匙”
步骤:
① 抓包 → 改 Header
'alg':'HS256'→'none'② 删 Signature 段(变成 xx.yy.)
③ 改 Payload
role:'user'→'admin'④ 发包 → 某些库(尤其是旧版 pyjwt、java-jwt)直接放行。
【一句话修复】
-
永远调用
verify()/parseClaimsJws()/jwt.decode(token, key, algorithms=[...]) -
白名单算法列表,拒绝
'none'、'None'、'NONE'大小写变种 -
加“公钥锁”(JWK Set)+ 强制密钥索引(kid),让伪造密钥成为不可能任务。
设置加密为None造成不验证签名,从而达到的越权,依旧是使用默认账号密码进行登录,然后抓包。
获取到返回包,这里进行两部分修改
修改前:
修改后,记得重新编码回去。
把 alg 改成 none 只是第一步,真正让服务端“认账”还要满足它的额外洁癖:大小写、签名段必须完全消失、Token 里不能有多余的点,否则直接格式报错,根本走不到验签逻辑。
查看后发现需要删除后面的签名字段,但是要保留那个点。
在JT(JSON Web Token)中,将 alg 设置为’none”后,表示不使用任何算法进行签名,这时就不需要在 T 中添加签名字段了。但是需要注意的是,将ag设置为’none会导致 WT的安全性大大降低,因为此时无法验证 NT的真实性和完整性,这意味着,攻击者可以简单地更改 NT 中的声明,而服务器无法发现这一点,从而造成身份认证和授权方面的安全问题。
因此,在实际应用中,通常应该选择一种安全可靠的算法进行签名,来确保T的安全性,一般情况下,常用的签名算法包括 HMAC、RSA和ECDSA等。选择身法所场景、安全需求和性能要求等因素进行综合考虑。
重新请求就成功了。经过测试这个要使用none,不能使用None,然后jwt不需要url编码也能使用。
在alg修改为none,一般都会过滤这种危险的设置,实战中较少,可以尝试大小写等等绕过。
登录成功后(建议修改jwt返回包进行登录),访问/admin文件夹,进行删除操作,就成功完成关卡。
Tips
欢迎大家在下面点赞评论加关注,让我们一起在网安之路越走越远!!!
#artContent h1{font-size:16px;font-weight: 400;}#artContent p img{float:none !important;}#artContent table{width:100% !important;}