参考文章:

JWT及JWT伪造-CSDN博客

^v^ (cnblogs.com)

JWT概述

Json web token (JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519)。

在了解JWT之前,先了解一下传统的认证方式。

传统的Session认证

http协议是一种无状态的协议,其不保存客户端的任何状态信息,这意味着http本身不具有身份认证作用。也就是说,基于http,服务端并不能知道当前请求源于哪个用户。

为了解决这个问题,需要在服务端存储一份用户登录的信息,这份登录信息会在响应时传递给客户端的浏览器,让客户端的浏览器保存为cookie,以便下次请求时发送给服务端。这样应用就能识别请求源自哪个用户,这就是传统的基于session认证。

随着用户基数的扩大,Session的问题也随之明显:

  • 每次认证通过都需要在服务端做一次记录,通常而言Session都是保存在内存中,而随着认证通过次数的增加,服务端的开销会明显增加。
  • 认证的记录存储在内存中,这意味着用户下次请求还必须要请求在这台服务器上才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
  • CSRF: Session认证方式是基于cookie 来进行用户识别的,cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

基于token的鉴权机制

token本身也是无状态的,其不需要服务端去记录信息,而是采用客户端存储,服务端验证的机制。具体的流程如下:

  1. 用户向服务器请求登录
  2. 服务器验证用户的信息
  3. 服务器通过验证并发送给用户一个token
  4. 客户端存储token,并在每次请求时附送上该token
  5. 服务端验证token值,并返回数据

这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *。

JWT构成

先来看一个JWT字符串:

plaintext
1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

JWT由三段信息组成,以点号分割。第一部分称为头部(header),第二部分称为载荷(payload),第三部分是签证(signature)。

头部(header)

JWT头部承载两部分信息:

  • 声明类型,这里是jwt
  • 声明加密的算法,通常直接使用 HMAC SHA256。这的加密算法也就是签名算法。

完整的头部信息是一个JSON格式的字符串,如下:

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

再将头部进行base64编码即可得到JWT第一部分:

plaintext
1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

可以将JWT的alg算法设置成None,此时JWT的第三部分会被置空,这样任何token都是有效的,如此就可以伪造token进行随意访问。

载荷(payload)

载荷即是存放有效信息的地方这些有效信息包含三个部分:

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明
  • 标准中注册的声明 (建议但不强制使用) :

iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

  • 公共的声明

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息。但不建议添加敏感信息,因为该部分在客户端可解密。

  • 私有的声明

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

如下payload:

json
1
2
3
4
5
6
7
8
9
{
"sub": "1234567890",
"iss": "http://localhost:8000/auth/login",
"iat": 1451888119,
"exp": 1454516119,
"nbf": 1451888119,
"name": "John Doe",
"admin": true
}

再进行base64加密就成为了第二部分:

plaintext
1
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

签证(signature)

jwt的第三部分是一个签证信息,这个签证信息由三部分组成

  • header (base64加密后的)
  • payload (base64加密后的)
  • secret

具体构成方式如下:

javascript
1
2
3
4
5
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

将前两部分用点号连接,然后通过header中声明的加密方式进行加盐secret组合加密,就构成了第三部分。

secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret,那就意味着客户端是可以自我签发jwt了。

通过JWT 进行认证

客户端接收服务端返回的JWT,将其存储在Cookie或localStorage中。之后客户端在与服务器交互中都会携带JWT。如果将它存储在Cookie中,就可以自动发送,但是不会跨域,因此一般是将它放入HTTP请求的Header Authorization字段中。当跨域时,也可以将JWT被放置于POST请求的数据主体中。

服务端每次收到信息都会对JWT的前两部分进行加密,然后比对加密后的结果是否跟客户端传送过来的第三部分相同,如果相同则验证通过,否则失败。

两端利用JWT的交互流程如下:

image-20241008085649808

JWT本身包含认证信息,因此一旦信息泄露,任何人都可以获得令牌的所有权限。为了减少盗用,JWT的有效期不宜设置太长。对于某些重要操作,用户在使用时应该每次都进行身份验证。为了减少盗用和窃取,JWT不建议使用HTTP协议来传输代码,而是使用加密的HTTPS协议进行传输。

JWT安全问题

JWT安全问题一般有以下几个:

  1. 修改算法为none进行JWT伪造
  2. 修改算法从RS256到HS256
  3. 信息泄漏 密钥泄漏
  4. 爆破密钥

JWT伪造

从一道题入手

image-20241008090849654

尝试伪造成admin,先解析JWT内容:

image-20241008091109556

改变如下:

image-20241008091326502

服务端响应如下:

image-20241008092020510

注意:

  • base64编码后的=号要去掉
  • 保留服务端的sses:aoksses:aok.sig,后面有用

再利用得到的sses:aoksses:aok.sig访问/api/flag即可:

image-20241008092402067

那么sses:aoksses:aok.sig是什么呢,我猜应该是签证信息,毕竟请求包也就这个可以算作验证信息。

拓展:JWT爆破

再认真看看这张图片:

image-20241008091109556下面那个框框就是secret,如果填写正确,就可以得到正确的secret,伪造任意JWT了。

具体就不展开说了。