什么是JWT

Json Web Token简称JWT,顾名思义JWT是用于身份验证的。
是一个非常轻巧的规范,这个规范允许使用JWT在用户和服务器之间传递安全可靠的信息。

为什么要用JWT

这里用@一叶飘零师傅的例子:

HTTP是无状态的,打个比方:
有状态:
A:你今天中午吃的啥?
B:吃的大盘鸡。
A:味道怎么样呀?
B:还不错,挺好吃的。
无状态:
A:你今天中午吃的啥?
B:吃的大盘鸡。
A:味道怎么样呀?
B:???啊?啥?啥味道怎么样?

通过例子我们很容易理解HTTP的无状态,那么怎么才能让http“有记忆力”呢,也就是让http记住对于事务的处理,从而克服无状态的缺陷,保持状态。
很简单,方法也很多,如:Cookie,Session,JWT

Cookie

利用cookie作为身份验证时,因为其作为用户身份的替代,其安全性可能会影整个系统的安全性。 Cookie最大的安全问题无疑是我们熟知的 Cookie欺骗,Cookie会记录用户的身份,如果加密不当,因为Cookie是在客户端的,所以很容易伪造从而伪造身份。
容易发现Cookie保持状态的机制就是客户端保持状态,这种方式存在很大的安全问题。

Session

当客户端访问服务端成功之后,服务端会生成一个session id,返回给客户端,客户端将session id保存到cookie中,再客户端次发起请求的时候,客户端把session id发送到服务端,服务端会缓存此次会话,客户端请求的时候,服务端就会判断请求来自哪个用户,从而实现身份认证。
Session保持状态的机制就是服务端保持状态,虽然这种方式相对于Cookie机制来说比较安全,但是服务端会存储大量的会话,对服务端来说无疑是一种“负担”,当服务器集群时,
采用缓存一致性技术或者第三方缓存来保证可以共享,不够方便。

JWT

在身份验证中,当用户使用他们的凭证成功登录时,JSON Web Token将被返回并且必须保存在本地,而不是在传统方法中创建会话服务器并返回一个cookie。无论何时用户想要访问受保护的路由或资源,用户代理都应使用承载方案发送JWT,通常在授权header中。header的内容应该如下所示:

Authorization:  Bearer<token>

这是一种无状态身份验证机制,因为用户状态永远不会保存在服务器内存中。服务器受保护的路由将在授权头中检查有效的JWT,如果存在,则允许用户访问受保护的资源。由于JWT是独立的,所有必要的信息都在那里,减少了多次查询数据库的需求。这使我们可以完全依赖无状态的数据API,无论哪些域正在为API提供服务,因此跨源资源共享(CORS)不会成为问题,因为它不使用Cookie。

JWT的结构

拿到一段 JWT:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidmlydHVhMSIsInByaXYiOiJvdGhlciJ9.sRRo-VYjADKRnwQQ8JxMtNzTW0ArsxAXs4O6W6Kw_UfJZotPNixzJoU831AQ1UorUBT-4lMTkhAhPSmtp8otYZwXxxemKBZH7xHEiqciBW1K5u5KEEFDzcIzCOvcsqakntlP8pCNGc9q5DbuCl2mAlHOOHN8wBSUAFQ1ovl9wMQ

分为三部分,用 . 连接:

解密一下:

发现解密后分为三个部分,分别是 Header Payload Signature

Header:

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

header部分分别是 算法名称和token类型,Base64对这个JSON编码就得到JWT的第一部分

Payload:

{
  "name": "virtua1",
  "priv": "other"
}

第二部分是payload,它包含声明(要求)。声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型: registered, public 和 private。

  • Registered claims : 这里有一组预定义的声明,它们不是强制的,但是推荐。比如:iss (issuer), exp (expiration time), sub (subject), aud (audience)等。
  • Public claims : 可以随意定义。
  • Private claims : 用于在同意使用它们的各方之间共享信息,并且不是注册的或公开的声明。

payload进行Base64编码就得到JWT的第二部分

Signature:
为了得到签名部分,必须有编码过的header、编码过的payload、一个秘钥,签名算法是header中指定的那个,然对它们签名即可。

例如:

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

再看一下上边解密的JWT,似乎就很明白了:

### 浅谈JWT攻击
关于Cookie和Session的攻击姿势已经很多,下面谈谈JWT用于身份认证时的的攻击手段。
#### 修改算法攻击
首先我们知道非对称密码才存在公私钥,而对称密码只有一个密钥,算法RS256为非对称加密,而HS256为对称加密。

如果我们得知原来用的算法是RS256,并且得到了公钥(且公钥模数极大,不可被分解),那么就可以利用修改算法攻击,利用得到的公钥,然后利用算法HS256对伪造的信息加密,如果后端的验证也是根据header的alg选择算法,那么服务端就会利用公钥进行HS256解密,从而造成身份伪造。
拿某CTF题目举个例子:
已知条件:通过信息收集已知公钥(模数极大,不可被分解),对JWT解密已知用的算法是RS256:

JWT解码:

我们要伪造的部分就是priv,利用修改算法攻击伪造身份:


可以看到攻击成功。

修改算法为none

这种攻击方式也可看作修改算法攻击的特殊方式。
签名算法保证了JWT在传输的过程中不被恶意用户修改,但是header部分的alg字段可被修改为none,一些JWT库支持none算法,即没有签名算法,当alg为none时服务端不会进行签名校验。
将alg修改为none后,去掉JWT中的signature数据(仅剩header + '.' + payload + '.')然后加密提交到服务端即可伪造任意数据。

#### 密钥爆破攻击
以上我们已经说过,HS256为对称加密,只有一个密钥,如果这个密钥不够复杂,那么很容易破解,就很容易伪造身份。
破解方式:

1.PyJWT模块破解:样例代码中使用secret字符串当做密钥那么暴力猜解密钥。
当密钥正确则解密成功,密钥错误解密代码抛出异常:

2.C-jwt-cracker用法:

知道了密钥,我们就可以伪造身份认证。

密钥可控攻击

密钥可控顾名思义就是我们可以修改服务端的密钥,至于修改的方式根据具体情况而定。
例子:(国赛的一道题目)

eyJ0eXAiOiJKV1QiLCJhbGciOiJzaGEyNTYiLCJraWQiOiI4MjAxIn0.eyJuYW1lIjoiYWRtaW4yMzMzIn0.aC0DlfB3pbeIqAQ18PaaTOPA5PSipJe651w7E0BZZRI

JWT解码:

kid为密钥的编号,逻辑类似于:

sql="select * from table where kid=$kid"

我们可以这样伪造:

kid = 0 union select 123456

那么查询出来的key为 123456
这样我们就得到了控制密钥,就可以伪造身份。

另外HITB 2017也有一道可控密钥的题目,题目的逻辑是存在写文件保存的功能,我们可以控制密钥,然后利用kid去查询我们写入的密钥,得到可控密钥的问题,进而伪造身份。

敏感信息泄露

通过对JWT结构的分析,我们不难发现payload部分是明文传输的,如果payload中存在敏感信息就会出现信息泄露。

Referer

[1].Json-Web-Token历险记
[2].Hacking JWT
[3].JWT token破解绕过
[4].HITB CTF 2017-Pasty-writeup
[5].JWT伪造cookie
[6].认识JWT