JWT

exp3n5ive Lv1

JWT

前置知识

JWT:JSON Web Token

JWT 本质上就是一组字串,通过(.)切分成三个为 Base64 编码的部分

  • Header(头部)

    描述 JWT 的元数据,定义了生成签名的算法以及 Token 的类型。Header 被 Base64Url 编码后成为 JWT 的第一部分

  • Payload(载荷)

    用来存放实际需要传递的数据,包含声明(Claims),如sub(subject,主题)、jti(JWT ID)。Payload 被 Base64Url 编码后成为 JWT 的第二部分

  • Signature(签名)

    服务器通过 Payload、Header 和一个密钥(Secret)使用 Header 里面指定的签名算法(默认是 HMAC SHA256)生成。生成的签名会成为 JWT 的第三部分

JWT 通常是这样的:

1
2
3
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

https://jwt.io/ 这个网站上可对 JWT 进行解码,解码之后得到的就是 Header、Payload、Signature 这三部分

Header 通常由两部分组成:

  • typ(Type):令牌类型,也就是 JWT
  • alg(Algorithm):签名算法,比如 HS256
1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

Payload

Payload 也是 JSON 格式数据,其中包含了 Claims(声明,包含 JWT 的相关信息)

Claims 分为三种类型:

  • Registered Claims(注册声明)

    预定义的一些声明,建议使用,但不是强制性的。

  • Public Claims(公有声明)

    JWT 签发方可以自定义的声明

    但是为了避免冲突,应该在 https://www.iana.org/assignments/jwt/jwt.xhtml 中定义它们

  • Private Claims(私有声明)

    JWT 签发方因为项目需要而自定义的声明,更符合实际项目场景使用

下面是一些常见的注册声明:

  • iss(issuer):JWT 签发方。
  • iat(issued at time):JWT 签发时间。
  • sub(subject):JWT 主题。
  • aud(audience):JWT 接收方。
  • exp(expiration time):JWT 的过期时间。
  • nbf(not before time):JWT 生效时间,早于该定义的时间的 JWT 不能被接受处理。
  • jti(JWT ID):JWT 唯一标识
1
2
3
4
5
6
7
8
{
"uid": "ff1212f5-d8d1-4496-bf41-d2dda73de19a",
"sub": "1234567890",
"name": "John Doe",
"exp": 15323232,
"iat": 1516239022,
"scope": ["admin", "user"]
}

Signature

Signature 部分是对前两部分的签名,作用是防止 JWT(主要是 payload) 被篡改

这个签名的生成需要用到:

  • Header + Payload
  • 存放在服务端的密钥
  • 签名算法

对于 HMAC SHA-256,签名过程是:

1
2
3
4
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

对于 RSA 签名,签名过程是:

1
2
3
4
5
RS256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
privateKey
)

RSA

非对称加密

算法描述 变量表示
① 选择两个大质数 p 和 q p, q
② 计算 n = p * q n
③ 欧拉公式 φ(n) = (p-1) * (q-1) φ(n)
④ 选择一个整数e,使得 1 < e < φ(n) ,且 e 和 φ(n) 互质 e
⑤ 计算 e 关于 φ(n) 的模逆元 d ,即 e * d ≡ 1(mod φ(n)) d

公钥指数 e

在RSA中,公钥指数 e 是一个与 φ(n) 互质的整数,通常是一个较小的数值(如 3 或 65537)它用于加密操作

私钥指数 d

私钥指数 d 是与公钥指数 e 相关的数值,用于解密操作

欧拉函数 φ(n)

φ(n) 是 n 的欧拉函数值,定义为小于 n 的正整数中与 n 互质的数量

模逆元

e*d ≡ 1(mod φ(n))中,意思是使得(e*d) mod φ(n) = 1

经过计算后,公钥(e,n)私钥(d,n),明文是m,密文是c

公钥加密
$$
c = m^e\ mod\ n
$$
私钥解密
$$
m=c^d\ mod\ n
$$

名称 英文 符号
(公钥)加密 encryption e
(私钥)解密 decryption d
明文 message plaintext m
密文 ciphertext c

常规通信中,A先发送公钥,B可以用公钥对自己的数据进行加密,这样只有A可以通过自己的私钥解密

下面介绍一下RSA签名,与对称加密不同的是,它没有 secret

RSA签名步骤
1. 对要签名的数据进行哈希
2. 用 私钥 d 对哈希值进行加密
3. 接收方使用公钥 e 对签名进行解密
4. 接收方判断解密的结果与数据哈希是否一致

漏洞

漏洞 详情
CVE-2015-2951 alg=none签名绕过漏洞
CVE-2016-10555 RS/HS256公钥不匹配漏洞
CVE-2018-0114 密钥注入漏洞
CVE-2019-20933 空白密码漏洞
CVE-2020-28637 空白密码漏洞
CVE-2020-28042 空签名漏洞

None算法

当不校验算法时,我们可以替换算法,甚至可以使用空的算法,来达到数据篡改目的

1
2
3
4
5
6
7
{
"alg" : "None",
"typ" : "jwt"
}
{
"user" : "Admin"
}

密钥爆破

JWT 爆破工具:c-jwt-cracker

https://github.com/brendan-rius/c-jwt-cracker

私钥泄露

可以根据私钥生成任意的jwt字符串,从而伪造身份令牌

例如发现了私钥文件泄露,就可以根据私钥重新生成一个adminjwt


除此之外,JWT还可能造成别的漏洞

利用KID标头

在JWT的头部中,除了存在typ以及alg之外,还可以存在其他内容。KID(全称 key id)标头是一个密钥标识符,是RFC标准中用来表示密钥存储位置的地方,服务端可以根据这个提示来寻找解密密钥
根据代码的编写情况,可能存在以下问题:

SQL注入

这种情况是密钥存储在数据库中,使用kid字段查询相关内容,代码如下所示

1
query = "select pub_key from keystore where kid='"+String(kid)+"'";

目录遍历

kid的值表示的是密钥存储的文件,通过构造payload,就可能会造成目录遍历漏洞

1
String key_location = "home/users/keystore"+String(kid);

命令执行

kid的值会被带入到要执行的命令之后,代码如下所示

1
command = "cat "+file_location;

https://blog.csdn.net/qingzhantianxia/article/details/129104228

剩余未学习到的见上…

  • Title: JWT
  • Author: exp3n5ive
  • Created at : 2024-09-03 15:36:07
  • Updated at : 2024-09-03 15:37:11
  • Link: https://redefine.ohevan.com/2024/09/03/JWT/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
JWT