JWT原理和安全漏洞总结

前言

JWT全称Json Web Token,所以他是Token的一种实现方式

Token的机制

  • 客户端输入用户名和密码,发送到服务器端

  • 服务器验证用户名和密码,验证成功后签发token返回给客户端

  • 客户端将服务器签发的token存储起来

  • 此后客户端向服务器获取资源时会携带token

  • 服务器收到请求验证token是否正确

传统的session认证方式需要服务器端存储用户的会话信息,消耗内存资源,而通过Token认证例如JWT,服务器端无需存储,只需要验证客户端发送的JWT令牌即可。

Cookie+session方式由于Cookie无法跨域,因此JWT成为了替代方案,多适用于解决单点登录、分布式服务等问题

名词解释

  • JWS:Signed JWT,签名过的JWT

  • JWK:Secret,JWT密钥

  • JWE:Encrypted,经过加密的密文

  • JKU:JWK Set URL,服务器通过访问该URI可以获取JWK集合中的密钥

  • KID:密钥ID,在有多个密钥可供选择的情况下,服务器可以使用这个 ID 来识别正确的密钥

JWT的构成

JWT由三部分构成:头部(Header)、有效载荷(Payload)、签名(Signature),每一部分通过.分割,如下

红色部分是Header,紫色是Payload,蓝色是Signature

1.Header

typ表示令牌的类型,JWT统一都写为JWT

alg表示签名使用的加密算法,默认为HMAC SHA256

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

JWT支持的加密算法如下,其中HMAC是对称加密算法,RSA和ECDSA是非对称加密算法

JWS算法名称描述
HS256 HMAC256 HMAC with SHA-256
HS384 HMAC384 HMAC with SHA-384
HS512 HMAC512 HMAC with SHA-512
RS256 RSA256 RSASSA-PKCS1-v1_5 with SHA-256
RS384 RSA384 RSASSA-PKCS1-v1_5 with SHA-384
RS512 RSA512 RSASSA-PKCS1-v1_5 with SHA-512
ES256 ECDSA256 ECDSA with curve P-256 and SHA-256
ES384 ECDSA384 ECDSA with curve P-384 and SHA-384
ES512 ECDSA512 ECDSA with curve P-521 and SHA-512

2.Payload

有效载荷部分官方提供了7个字段可供使用

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

除此之外用户可以自定义字段,一般可能会存放一些用户的相关信息,例如

{
"name": "Xiaoming",
"admin": "False"
}

3.Signature

签名部分是对前面两部分数据通过指定的加密算法计算Hash值,从而保证数据不被篡改

生成密钥后将Header和Payload部分的内容分别用Base64Url编码并通过.连接,然后用指定的加密算法和密钥计算Hash值后发送给客户端

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

最后前两部分Base64Url编码后的内容与生成的签名拼接,每个部分通过.分割,最终组成完整的JWT对象

 

安全漏洞

1.未验证签名

未对签名进行校验,会导致攻击者随意篡改JWT的内容,从而造成越权等危害

以PortSwagger靶场为例 Lab: JWT authentication bypass via unverified signature

登录测试账户后我们发现,服务器生成JWT添加到了Cookie

JWT前两部分内容解码,内容如下,sub为当前登录的用户名

 

访问/admin时提示只允许administrator才能访问

我们把sub的值改为administrator,base64编码后替换掉原来的

发送后成功访问到了/admin页面

2.加密算法验证缺陷

将头部中的"alg"加密算法设置为"none",签名置空,此时构造的任何JWT都是有效的

修改头部的"alg"为none

同样修改"sub"为administrator

将修改完后的内容拼接替换,签名部分直接删掉就可以,但是要保留最后一个.,维持JWT格式

3.爆破弱密钥

上面提到了JWT支持的加密算法有HMAC、ECDSA、RSA,其中HMAC是对称加密算法,只用一个密钥去加解密

如果使用了HMAC并且密钥是简单的字符串,就可以利用hashcat等工具离线爆破密钥

-a 0代表指定攻击模式为字典攻击

-m 16500是指定哈希算法的模式编号,16500对应JWT的HMAC

hashcat -a 0 -m 16500 <jwt> <wordlist>

hashcat会通过给定的密钥字典生成JWT,然后与提供的JWT对比,一致说明加密密钥正确

4.JWT-Header注入

JWT头部有很多参数可能被利用,例如jwk,jku,kid等,如果校验不全可能会受到攻击

4.1 Jwk参数注入

服务器不检查密钥的提供者,我们自己生成RSA算法和公私钥,用生成的私钥签名,公钥放到JWK里发送给服务器(不清楚JWK是什么可以看文首介绍)

{
"kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
"typ": "JWT",
"alg": "RS256",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
"n": "yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9m"
}
}

可以利用Brup的JWK Editor插件一键生成

生成JWK后,回到Repeter,选择Embedded JWK,选择刚才生成的RSA,修改Payload为administrator后,点击Encrypt

替换JWT

 

4.2 Jku参数注入

服务器接收Jku参数,但是不检查提供的 该URL 是否属于受信任的域

创建Jwk Set,将刚才生成的Jwk公钥,复制到靶场提供的服务器上,注意格式

kid的值与生成的对应,jku添加上返回Jwk Set服务器的地址,payload部分跟之前一样,sub修改为administrator

修改好之后点击Sign,生成JWT

替换JWT,完成后续利用

4.3 Kid参数注入

在JwkSet中可能存在很多密钥,因此用Kid来唯一标识。

服务器接收kid参数,从kid指定的路径获取相关密钥,且使用对称加密。如果kid没有限制格式,配合目录遍历,例如设置kid为Linux中的/dev/null空文件,就会造成用空字符串加密。

在JWT Editor中创建对称加密,密钥设置为AA==也就是0x00空字符。

来到Repeter,修改kid指向"/dev/null",payload中的sub为administrator

 

 

 

 

 

 

 

 

 

 

JWT attacks | Web Security Academy

PortSwigger JWT 安全问题 - FreeBuf网络安全行业门户

什么是JWT?深入理解JWT从原理到应用(上)-阿里云开发者社区