Web

浅谈JWT绕过

 Von's Blog     2019-11-01   3500 words    & views

写在前面

这半个月来各种事情实在是纷繁复杂,单给一个期中考就搞得半死,更别说还有上面马原的方言展示,C1测试之类的了。被这些事情所扰,技术的学习也就有了相当的停滞,现在终于逐渐抽出时间来学习技术了,在此立下FLAG,11月更新10篇博客!!!

什么是jwt?

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

从认证谈起

我们常见的网络上的认证方式有两种:cookie-session认证和基于token的认证

传统的session认证,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来.例如而随着认证用户的增多,服务端的开销会明显增大,这样在分布式的应用上,相应的限制了负载均衡器的能力,因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到XSS攻击。

基于token的认证

基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。

jwt的生成与验证

我们的jwt便是一种基于token的认证方法,一个jwt字符串包括以下三个部分:头部(header),载荷(payload),签名(signature)
1.头部(header) 用于描述JWT的最基本的信息,其所用的签名与算法类似这样

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

2.载荷(payload) 也是json形式的,官方定义的有如下六个部分

{
"sub": "1", //该JWT所面向的用户
"iss": "http://localhost:8000/auth/login", //该JWT的签发者 
"iat": , //iat(issued at): 在什么时候签发的token
"exp": , //exp(expires): token什么时候过期
"nbf": , //nbf(not before):token在此时间之前不能被接收处理
"jti": "" //JWT ID为web token提供唯一标识
}

当然,我们在具体使用时可以不需要这么多部分,可以定义自己需要的数据,如admin:false之类。

3.签名(signature) 签名这里我们需要传入一个私钥(key),具体生成方法如下:

# 定义私有密钥
key = 'secretkey'

# header和payload拼接生成令牌
unsignedToken = encodeBase64(header) + '.' + encodeBase64(payload)

#生成签名
signature = HMAC-SHA256(key, unsignedToken)

4.最终生成jwt数据 当我们分别生成了头部,载荷,签名之后,我们就可以生成最终数据了。

#最后拼接生成JWT
JWT = encodeBase64(header) + '.' + encodeBase64(payload) + '.' + encodeBase64(signature)

#最后生成的JWT大概就长这个样子:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.cAOIAifu3fykvhkHpbuhbvtH807-Z2rI1FS3vX1XMjE

5.jwt的验证 服务器应用在接受到JWT后,会首先对头部和载荷的内容用同一算法再次签名。如果服务器应用对头部和载荷再次以同样方法签名之后发现,自己计算出来的签名和接受到的签名不一样,那么就说明这个Token的内容被别人动过的,我们应该拒绝这个Token。

针对jwt的攻击

从上面的过程可以看出,jwt的安全性完全寄托于私钥key.只要我们知道了key,我们就可以伪造出jwt数据。

暴力破解

当key的安全性不高时,容易被人爆破攻击。
jwt破解工具 我们使用这个软件就可以破解出jwt的key,进而实现数据的伪造了。

例子

我们已知一段jwt数据:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoibGluaG9uZyJ9.9_A9rDTLAHfptbAlzNmnWV3qZzkyTcGbViYHadpZwIw

我们运用软件可以破解出key: 接下来我们就要借助一个网址来进行jwt数据的重构了:网址
我们将payload里面的user改为admin,key输入我们获取的key,便可以得到新的token了。

修改算法攻击

将非对称加密算法改为对称加密算法

算法HS256使用秘密密钥对每条消息进行签名和验证。
算法RS256使用私钥对消息进行签名,并使用公钥进行验证。
如果将算法从RS256更改为HS256,后端代码会使用公钥作为秘密密钥,然后使用HS256算法验证签名。
如果我们可以获取到公钥,那么我们就可以通过将算法从RS256修改为HS256,然后使用RSA公钥对数据进行签名,后端代码会使用RSA公钥+HS256算法进行签名验证。从而达到了绕过的效果。

具体例子

可以看这个网站的例子:http://demo.sjoerdlangkemper.nl/jwtdemo/hs256.php
公钥文件 解题代码如下:

import jwt
public = open('public.pem', 'r').read()
print jwt.encode({"user": "admin"}, key=public, algorithm='HS256')

即可以成功绕过。

修改算法为NONE

签名算法保证了JWT在传输的过程中不被恶意用户修改,但是header中的alg字段可被修改为none。
一些JWT库支持none算法,即没有签名算法,当alg为none时后端不会进行签名校验,将alg修改为none后,去掉JWT中的signature数据(仅剩header + ‘.’ + payload + ‘.’)然后提交到服务端即可。
当然,这种只适用于低版本的JWT库,高版本的JWT库一般都不支持这种算法。故在此不给出例子。

密钥可控攻击

这里以2018年国赛的一道题目作为讲解。在题目中,我们遇到了这样的JWT:

eyJ0eXAiOiJKV1QiLCJhbGciOiJzaGEyNTYiLCJraWQiOiI4MjAxIn0.eyJuYW1lIjoiYWRtaW4yMzMzIn0.aC0DlfB3pbeIqAQ18PaaTOPA5PSipJe651w7E0BZZRI

其header和payload如下所示: 这里的kid是key的id,在数据库中查询key的逻辑类似于:

select * from table where kid = $kid

查询出来的结果即为key的值。那么我们就可以构造类似于sql注入的语句,执行联合查询

kid = 0 union select 1

这样我们就可以使得查询出来的key结果一定为1,也就达到了控制密钥的目的。 接下来我们就可以根据密钥生成相应的JWT以达到伪造登录的目的了。

其他

除了文中使用的jwt破解工具,我们也可以使用另外一个jwt破解工具。 网址 这个工具可以自己添加字典进行破解,还可以自动实现上面提到的多种攻击方式,本文在此就不再赘述。

总结

1.在Web应用中,别再把JWT当做session使用,绝大多数情况下,传统的cookie-session机制工作得更好。
2.JWT适合一次性的命令认证,颁发一个有效期极短的JWT,即使暴露了危险也很小,由于每次操作都会生成新的JWT,因此也没必要保存JWT,真正实现无状态。
3.JWT不要存储任何敏感性数据.可以用于存储用户的一些基本的登录信息等。

参考文章

吴靖丰的博客
jwt-token的破解
知乎的文章
一叶飘零师傅的文章
Chybeta师傅的文章
dlive师傅的文章
简书上的文章