JWT认证机制和漏洞利用

image-20220408105946033

JSON Web Token(JWT) 简称JWT

JWT的组成部分

1、头部

2、payload 数据

3、验证签名

img

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
28
{
"alg": "None",
"typ": "jwt"
}

#alg
#是说明这个JWT的签名使用的算法的参数,常见值用HS256(默认),HS512等,也可以为None。HS256表示HMAC SHA256。
#typ
#说明这个token的类型为JWT
[
{
"iss": "admin",
"iat": 1649316004,
"exp": 1649323204,
"nbf": 1649316004,
"sub": "user",
"jti": "2df98b5b4695c8a6a555a74ab1566e3f"
}
]

#下方是声明部分一些常见的键,其中一些键不是必须的
#iss:发行人
#exp:到期时间
#sub:主题
#aud:用户
#nbf:在此之前不可用
#iat:发布时间
#jti:JWT ID用于标识该JWT

下面是一个用HS256生成JWT的代码例子的结构
HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload),secret)

#头部base64.数据段base64 然后加上自己的一个密钥 构成了一个jwt认证

1、用户端登录,用户名和密码在请求中被发往服务器
2、(确认登录信息正确后)服务器生成JSON头部和声明,将登录信息写入JSON的声明中(通常不应写入密码,因为JWT的声明是不加密的),并用secret用指定算法进行加密,生成该用户的JWT。此时,服务器并没有保存登录状态信息。
3、服务器将JWT返回给客户端
4、用户下次会话时,客户端会自动将JWT写在HTTP请求头部的Authorization字段中
5、服务器对JWT进行验证,若验证成功,则确认此用户的登录状态

img

稍稍解释下,就是客户端登录,带着一些账号密码等信息,服务器接收并判断登录成功后,通过秘钥生成jwt返回给浏览器,在之后每次客户端发请求都会带上jwt表示身份,然后服务器验证令牌并根据身份匹配权限,对行为进行相应。

JWT加密算法

JWT最常用的两种算法是HMAC和RSA。

HMAC(对称加密算法)用同一个密钥对token进行签名和认证。

RSA(非对称加密算法)需要两个密钥,先用私钥加密生成JWT,然后使用其对应的公钥来解密验证。

解密: https://jwt.io/

通常用该网站进行解密

img

科普完了,正好在刷ctfshow(再次安利一波)

这类题目我本身做的也不多,实战的话,实习时候在公司遇到过,不过不好拿出来放到博客。

就先演示ctfshow吧,会一直更新

web 345

img

查看源码发现有admin 我直接访问是会跳转到首页的。

img

auth参数貌似就是jwt,这里我直接base64解密看看

img

jwt头部的加密方式是None。所以这是一个jwt不加密验证漏洞。 JWT支持将算法设定为“None”。如果“alg”字段设为“ None”,那么签名会被置空,这样任何token都是有效的。

alg字段设置为“None”就可以伪造我们想要的任何token,接着便可以使用伪造的token冒充任意用户登陆网站。

直接把user改为admin 在进行base64编码后提交给cookie 访问admin 直接提交发现get flag。

img

web 346

img

跟上题类似,我这边直接url访问admin发现还是会跳转到首页,看下cookie 把auth参数拿去官网解密一下看看

img

第一反应应该是一个弱密钥,爆破一下试试看

这个脚本可以进行解密,和伪造jwt https://github.com/ticarpi/jwt_tool

python3 jwt_tool.py <jwt值> -C -d <字典>

img

img

填上密钥并修改 sub的值为admin重新生成了jwt 替换掉

img

web 349

img

此题目给了源码

发现公私钥都放在了public文件夹下面,nodejs中可以直接访问此文件。直接访问私钥

img

这回看一下jwt解码的结果。

加密为RS256 非对称加密 利用私钥生成 jwt ,利用公钥解密 jwt。

而 HS256这种对称加密,双方之间仅共享一个密钥,要千万注意密钥不要被泄露。

img

既然我们已经拿到了私钥,我就用Python生成jwt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import jwt

#payload
token_dict = {
"user":"admin"
}
#headers
headers = {
'alg':"RS256",
}
#私钥
key = open('private.key','r').read()

jwt_token = jwt.encode(payload=token_dict,key=key,algorithm='RS256',headers=headers)
print(jwt_token)

img

这里补充下 我用Python最开始无法生成加密后的jwt后来发现是 加密版本很低

pyjwt 2 需要 加密包版本稍高

1
python3 -m pip install --upgrade cryptography

拿来解密发现没任何问题

img

直接替换 并用post请求

img

web 350

这题直接给了源码。

源码里面呢也附带了 公钥

img

这题属于是密钥混淆攻击 。

如果将算法RS256修改为HS256(非对称密码算法=>对称密码算法)?

那么,后端代码会使用公钥作为秘密密钥,然后使用HS256算法验证签名。由于公钥有时可以被攻击者获取到,所以攻击者可以修改header中算法为HS256,然后使用RSA公钥对数据进行签名。

直接用上面的Python脚本去改,也会生成响应的jwt但是,提交到题目却拿不到flag,猜测难道是

jwt在python和nodejs的库不同。

直接用node

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)

npm install jsonwebtoken 安装模块
node jwt.js 运行

img

img

其实直接使用jwt.io也是可以的,但是经过测试如果密钥较复杂,例如有换行,粘贴到jwt.io以后会被替换为空格,最后导致结果不正确,所以直接采用了node,方便快捷。