首页 > 技术文章 > 浅谈jwt

zhaozhaomumu 2022-03-24 19:08 原文

1.什么是jwt?

JWT全称 JSON Web Token(JSON Web令牌)是一种跨域验证身份方案。功能与token类似。JWT不能加密传输数据,但可以通过数字签名来验证信息是否被篡改。简言之,JWT是以JWS(JSON Web签名)或JWE(JSON Web加密)结构编码的JSON格式字符序列。每个选项都必须以紧凑的方式进行序列化,这种结构就称为JWT。

2.JWT基本格式

JWT的格式如下图
image

JWT分为三部分:头部(Header),声明(Claims),签名(Signature),三部分由英文字符.隔开。
三个元素使用base64url算法加密的。(与base64不同的是,会把输出结果的(+)替换为(-),(/)替换为(_),且没有标准的base64填充,将其的(=)去掉)

第一段是header部分,主要声明了类型(一般就是jwt),和加密算法(常用的是HMAC、SHA256),使用的是base64编码,直接解码即可查看明文。

头部(Header)
{
"alg":"HS256",
"typ":"JWT"
}

alg:是说明这个JWT签名所使用的算法的参数,常用HS256(默认)、HS512等,也可以为none。HS256表示的是HMAC SHA256。
type:说明token类型为JWT

第二段是声明部分,一般请求相关信息都会放在此段中,比如用户ID、用户名等等,使用base64编码,可直接查看明文。

声明(Claims)
{
"exp": 1416471934,
"user_name": "user",
"scope": [
"read",
"write"
],
"authorities": [
"ROLE_ADMIN",
"ROLE_USER"
],
"jti": "9bc92a44-0b1a-4c5e-be70-da52075b9a84",
"client_id": "my-client-with-secret"
}
JWT 固定参数有:
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID 用于标识该 JWT

第三段是signature部分,签证部分,会将header和payload进行base64编码,然后以header中的加密方式+secret盐组合加密,看不了明文。服务器有一个不会发给客户端的密码(secret),用头部中指定的算法对头部和声明的内容进行加密,生成的字符串就是JWT的签名。
下面是一个用HS256生成的JWT代码例子

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

一般来说,知道签名的加密字,可以上https://jwt.io/ 来解密,或者在这个站点中加密自己所需要的jwt token。
image
或者在Burp有提供相应的jwt插件叫JSON Web Tokens,可以直接在市场中下载,像上面的jwt内容,解码后如下:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyaWQiOjEsInVzZXJuYW1lIjoiYWRtaW4ifQ==.xxxxxxxxxxxxxxxxxxxxx

image

3.JWT工作流程

工作流程如下图

image

  1. 用户登录:提供用户名和密码;
  2. JWT生成token和refresh_token,返回客户端;(注意:refresh_token的过期时间长于token的过期时间)
  3. 客户端保存token和refresh_token,并携带token,请求服务端资源;
  4. 服务端判断token是否过期,若没有过期,则解析token获取认证相关信息,认证通过后,将服务器资源返回给客户端;
  5. 服务端判断token是否过期,若token已过期,返回token过期提示;
  6. 客户端获取token过期提示后,用refresh_token接着继续上一次请求;
  7. 服务端判断refresh_token是否过期,若没有过期,则生成新的token和refresh_token,并返回给客户端,客户端丢弃旧的token,保存新的token;
  8. 服务端判断refresh_token是否过期,若refresh_token已过期,则返回给客户端token过期,需要重新登录的提示。

攻击方式

1.算法设置为none

jwt的header部分中,alg字段指定了加密算法,可以尝试将其设置为None,把signature签名设置为空,导致任何token都有效。
在程序开发过程中,如果开发为了方便调试开启了运行加密为None,上线又忘记了关闭,则存在该问题。
拿上面jwt为例,修改None后如下:

image

2.算法修改

还有一种就是修改加密算法,jwt常用的一个是RS256非对称,一个是HS256对称,对称加密就只有一个密钥,非对称有两个,即私钥和公钥。

如果程序使用的是RS256,而我们把它改为HS256,即由非对称改为了对称,那么这时后端就会使用公钥作为解密密钥,然后使用HS256算法来验证签名。

即攻击者将header算法改为HS256,使用RS256的公钥对数据进行签名。当后端程序允许同时使用这两种算法时,就会产生这个问题。

3.签名失效问题。

如果后端程序没有对签名做校验,那么攻击者就可以修改声明过等目的,例如user是test,则可以改为admin:

image

4.暴力破解

当程序使用了对称加密,例如HS256,那么就可以尝试去暴破密钥,这里使用c-jwt-cracker,命令如下:

git clone git://github.com/brendan-rius/c-jwt-cracker
cd c-jwt-cracker
apt-get install libssl-dev ##安装 openssl 的头文件,否则会报错
make
./jwtcrack xxxxxxxxxxx.xxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxx

比如暴破的密钥结果为123456,那么就可以利用密钥来修改jwt的内容,jwt官网提供了修改:

image

5.密钥泄露

和第四种很像,总归是获取到了密钥,只不过途径不一样,泄露方面,比如git泄露,目录遍历、文件读取等等。

6.令牌刷新

比如程序使用了jwt,且设置了jwt的失效时间,这时jwt失效后再次请求,为了不让用户再输入账号密码,则添加了一个refersh_token机制,即刷新token字段,该字段一般都是一串随机的字符串,比如请求jwt,返回了如下包内容:


{"code":0,"data":{"access_token":"XXX.YYY.ZZZ","access_token_expiration":"Thursday, November 9th, 2017, 10:27:33 PM","refresh_token":"ABC123"}}

危害在于后端如果没有对refresh_token和access_token做检验匹配,那么我们就可以拿着自己的refresh_token去获取别人的access_token值。

7.KID

KID是header中的一个可选字段,key id的缩写,字面理解即key的序号,用来表示使用密钥几来验证,比如下面kid为1,则代表使用密钥1来做验证:

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

危害在于我们可以修改kid达到一些攻击目的,例如后端接口kid后会放到sql语句中进行查询获取相关密钥,那么就可以尝试下sql注入问题:

"kid":"aaaaaaa' UNION SELECT 'key';--"

如果值为文件路径,则可以尝试文件读取:

"kid":"/etc/passwd"

如果是文件名,那么后端可能会使用一些打开文件的函数,有些函数支持命令执行:

"key_file" | whoami

靶机练习

webgoat靶场练习
参考文章:

jwt官网:https://jwt.io/
webgoat解题说明:https://github.com/vernjan/webgoat/blob/master/02-jwt-tokens.md
c-jwt-cracker:https://github.com/brendan-rius/c-jwt-cracker
webgoat解题博客1:https://pvxs.medium.com/webgoat-jwt-tokens-4-5-ff5bd88e76f
webgoat解题博客2:https://pvxs.medium.com/webgoat-jwt-tokens-8-6ea5f5132499
字典:https://github.com/danielmiessler/SecLists
jwt令牌刷新问题:https://emtunc.org/blog/11/2017/jwt-refresh-token-manipulation/

推荐阅读