Virtua1's blog

浅谈Json Web Token攻击

字数统计: 1.9k阅读时长: 7 min
2019/05/18 Share

什么是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保持状态的机制就是客户端保持状态,这种方式存在很大的安全问题。

Session

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

JWT

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

1
Authorization:  Bearer<token>

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

JWT的结构

拿到一段 JWT:

1
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidmlydHVhMSIsInByaXYiOiJvdGhlciJ9.sRRo-VYjADKRnwQQ8JxMtNzTW0ArsxAXs4O6W6Kw_UfJZotPNixzJoU831AQ1UorUBT-4lMTkhAhPSmtp8otYZwXxxemKBZH7xHEiqciBW1K5u5KEEFDzcIzCOvcsqakntlP8pCNGc9q5DbuCl2mAlHOOHN8wBSUAFQ1ovl9wMQ

分为三部分,用 . 连接:

解密一下:

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

Header:

1
{
2
  "alg": "RS256",
3
  "typ": "JWT"
4
}

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

Payload:

1
{
2
  "name": "virtua1",
3
  "priv": "other"
4
}

第二部分是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用法:

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

密钥可控攻击

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

1
eyJ0eXAiOiJKV1QiLCJhbGciOiJzaGEyNTYiLCJraWQiOiI4MjAxIn0.eyJuYW1lIjoiYWRtaW4yMzMzIn0.aC0DlfB3pbeIqAQ18PaaTOPA5PSipJe651w7E0BZZRI

JWT解码:

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

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

我们可以这样伪造:

1
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

CATALOG
  1. 1. 什么是JWT
  2. 2. 为什么要用JWT
    1. 2.1. Cookie
    2. 2.2. Session
    3. 2.3. JWT
  3. 3. JWT的结构
  4. 4. 浅谈JWT攻击
    1. 4.1. 修改算法攻击
    2. 4.2. 修改算法为none
    3. 4.3. 密钥爆破攻击
    4. 4.4. 密钥可控攻击
    5. 4.5. 敏感信息泄露
  5. 5. Referer