什么是JWT
JSON Web Token(JWT https://jwt.io )是一种跨域身份验证解决方案,其主要认证原理是提供一个可信签名,利用存在客户端的secret_key将明文的鉴权数据做一个签名,用于跨域校验权限的合法性。
举一个例子:
Authorizatione: yJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4ifQ.MXi3JVNa96zFigIouJL9dSmGxXmLkyO2lMyiJpw-sEs
JWT分为三段,由"."分开,第一段是加密算法,这里选择的是HS256,也就是基于SHA-256的HMAC,对称加密签名算法,介于安全性,其实用RS256(基于SHA-256的RSA256,非对称加密签名算法)更好,原因后面会提到。第二段是内容,这里就是给用户标上了admin的标签,在用jwt时,切记勿将敏感数据直接存入JWT,因为JWT本身只提供签名认证作用,其数据还是明文传输,任何人拿到都可以解密,并不能起到保密作用。
echo eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 | base64 --d
{"alg":"HS256","typ":"JWT"}
echo eyJ1c2VyIjoiYWRtaW4ifQ== | base64 --d
{"user":"admin"}
第三段就是加密签名内容了,客户端在接收到用户端传输过来的JWT后,会首先利用secret_key校验签名,如果签名正确,就会去解密第二段的内容,用于认证。
关于JWT的安全性
用JWT代替cookie做站点鉴权,对多个业务方共享一套账户体系的确带来了很大的便利,相互获取数据不用考虑带cookie跨域导致的一系列安全风险(例如JSONP、CORS等)也不用在交互身份信息时每次都实现一次OAUTH,对开发和安全有极大的帮助,但是由于JWT的安全性全部依赖与最后的签名,其实还是有安全风险的,下面分别说下自己遇到过的几个JWT伪造攻击成功的例子。
1、测试站点与主站点secret_key相同
之前在挖掘漏洞的时候遇到过,某需要拿下来的后台main.xxx.com,存在一个测试站点dev.xxx.com,两个站点可能功能存在部分不同,但都是利用JWT鉴权,并且main.xxx.com登陆存在验证码,而dev.xxx.com不用验证码可以爆破,于是我尝试爆破了dev.xxx.com。
发现存在弱口令 :
123456 / xxxxxxxxx
登陆成功后发现返回了一段token,是JWT。并且发现在之后请求数据的数据包里,是利用此段JWT鉴权。
于是猜想,main.xxx.com与dev.xxx.com有没有可能存在是利用的同一段secret_key,这样我就能利用这段JWT去登陆main.xxx.com主站系统,burpsuite修改返回包为测试站点登陆成功的返回包:
直接登陆成功main.xxx.com:
这里,我们引出了一个安全隐患:对于不同的站点,JWT的secret_key不能设置为相同。
2、泄露secret_key导致任意用户登录
在很多开发框架里面,对DEBUG模式下的异常处理,都会将一些配置信息显示出来,以便于开发者调试,例如Django的调试页面:
来源:fofa.so
有的开发过程中,如果是自己实现的JWT,可能会将secret_key存在配置信息中,在部分框架的报错页面中,会导致secret_key泄漏,最后导致任意用户伪造登陆,例如hackerone上面最近披露的一个漏洞:
https://hackerone.com/reports/460545
我在之前测试中也遇到过,当时是遇到一个利用JWT鉴权的框架开发的APP下找到一个Debug页面,发现secret_key直接回显在了Debug页面前端:
这样,我们可以利用低权限用户的JWT,去伪造高权限的JWT,具体方法如下:
base64 decode原本JWT的加密算法与JWT内容
伪造自己想伪造的内容部分
利用泄漏的secret_key对伪造的内容进行签名
利用得到的JWT字符串,直接用管理员权限登陆了刚刚的系统。
当然,不止是DEBUG页面,类似泄漏的方法很多,例如Flask模板注入、格式化字符串漏洞可以直接读取配置信息、配置文件在WEB目录下可下载、CMS默认存在secret_key使用未修改等,这里就不一一叙述了。
对于secret_key泄漏情况,其实JWT本身设计的时候是有考虑到的,就是之前提到的JWT会提供不同的签名算法,其中说到了RS256算法会更好,其原因就是RS256(采用SHA-256 的 RSA签名)是一种非对称算法,它使用公共/私钥对: 标识提供方采用私钥生成签名, JWT 的使用方获取公钥以验证签名。也就是说,我们这里只需要维护一个私钥服务用于保护私钥,而用于验证签名的公钥就可以分发到下面各个业务方,就算他们配置不当,将公钥泄漏,也无法通过公钥对我们的JWT认证系统伪造。
3、secret_key过弱,或者长期不更换导致可爆破
之前提到过,JWT有一个缺陷是无法对数据进行加密,也就是说数据是对用户都是明文可知的,这就出现一个问题:
既然需要加密的数据、算法我都知道,为什么不能考虑爆破密钥?
所以过弱的密钥肯定是很容易被爆破成功的,毕竟是签名算法,爆破是否成功也无需传入服务端进行校验,能否爆破成功完全在于本地主机的算力大小。有人可能就会想着,那设置一个较长、随机的强key就行了。
但实际情况是随着现在超算的发展,算力越来越强的情况下,只要secret_key一直能保持不变,就肯定是能在本地被算出来,只不过是一个成本大小的问题,有没有人愿意为了你的数据,去付出这样的成本。
当然,最后这一点只是一个经验猜想之谈,我也没有实际上去操作过,也没有例子可以说明,大家可以自己尝试,说不定就真的能爆破成功~。
利用JWT认证的安全建议
严格保护自己的secret_key,防止泄漏
禁止线上站点与测试站点用一套JWT
利用RS算法代替HS算法
线上环境一定要禁止debug
设置较强的secret_key,并且定期更换