JWT相关

昨天女朋友突然问了一些关于json web token(jwt)的问题,正好想起来之前看过的一个通过修改json web token获取网站admin权限的例子,于是翻evernote找出今年四月份就写好的笔记,才发现本来是要在总结完shellshock之后就总结一下jwt,但拖到现在,整整三个月。看来我真的是个懒人…(笑 总结的多有疏漏,请大佬指教。

0x00 what 什么是JWT

JWT是一种基于Json的开放标准,web应用通过使用jwt传递声明,而这个声明通常用来在身份提供者和服务提供者之间传递被认证的用户信息,以便于从资源服务器获取资源,也可以增加一些额外的其他业务逻辑所必须的声明信息,jwt可以被直接用于认证,也可以被加密。

0x01 why 为什么用JWT

HTTP协议是无状态协议(废话),那服务器怎么才能记住用户?用户向web应用提供用户名密码等来进行用户认证,当进行下一次请求的时候,为了向服务器表明身份,用户还需要再输入一次用户名密码,因为HTTP无状态,所以如果我们不再次表明自己的身份,服务器完全不知道这个请求是谁发出的,为了让服务器能识别是哪个用户发出的请求,我们需要在服务器存储一份登陆信息,这个登陆信息会在服务器响应时传给浏览器,并保存为cookie,以便于下一次请求时发送给服务器,这样服务器就知道这些请求来自哪些用户了。
以上是基于session的认证方法。这个认证方法有一些弊端:

  1. 安全性。举个例子,比如某社交网站,互相关注的时候,通过如下链接实现https://www.fool.com/follow/?from_user=A&to_user=B
    如果用户需要互相关注,就要求用户必须在线(又是一句废话),用户在线执行这一操作就会有CSRF的风险,如果这个社交网站对referer检测不全面,或者cookie被截获,很容易造成蠕虫。

  2. 服务器开销。每个用户经过web应用的认证之后,都要在服务器端留下一个记录,为的是下一次登陆时表明自己的身份,但是这些session通常都是存储在内存中的,如果是一个大型的web应用, 势必会有大量的用户,服务端的开销就会明显增大。

  3. 拓展性。与服务器开销有关,用户将自己的session存放在服务器中,意味着之后用户下一次的请求还是要从这台服务器上去获取资源。这样就限制了应用的拓展能力(比如将应用改为分布式)

基于以上的几点缺点,JWT这种基于token的机制就显现出优势: 基于token的机制类似于HTTP的无状态,他省却了在服务器存储用户的session,减少了服务器的开销,同时意味着应用不需要去考虑用户在哪一台服务器登陆, 这就解决了拓展性的问题。

0x02 How JWT是如何工作的

JWT的大致流程如下:
1.用户使用用户名与密码请求服务器。
2.服务器验证用户的身份信息
3.服务器通过验证发送token给用户
4.用户存储token至客户端,并且每次请求时发送这个token值。
5.服务器对token进行验证,返回数据
token是每次请求时都要包含在请求头中的,同时服务端要支持跨站策略CORS,因此需要在服务端对access-control-allow-origin进行配置。

那么JWT的构成是什么样的?

JWT由三个部分组成,header, payload,signature.
我们分别来介绍这三个部分.

header中包含两部分内容,声明类型与声明加密算法
声明类型在这里是jwt,而加密算法可以有如下选择: RSA based, Elliptic curves, HMAC, None 举一个比较安全的JWT头部

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

然后将这个头部进行base64编码(是编码不是加密),构成了第一部分header。

payload

这部分中存放着真正有效的信息,有以下几个标准定义字段:

  1. iss:该JWT的签发者
  2. sub:该JWT所面向的用户
  3. aud:接受该JWT的以防
  4. exp:过期时间(UNIX时间戳)
  5. iat:签发日期
  6. jti:jwt的唯一身份标识,常用于一次性token,避免重放攻击

举个例子,以上文社交网站互相关注为例

{
    "iss": "evilc",
    "iat": 1234567896,
    "exp": 1365842139,
    "aud": "www.fool.com",
    "sub": "foolking@fool.com",
    "from_user": "A",
    "to_user": "B"
}

将这部分进行base64编码,就可以得到第二部分payload

signature

这一部分需要将前两个部分进行拼接并加密,这个操作需要用到三样东西 1.base64encode(header)
2.base64encode(payload)
3.secret
具体操作是将b64encode(header)和b64encode(payload)用"."进行拼接,然后用header中声明的加密方式,使用secret进行加密 大概过程是:

encodedstring = b64encode(header)+'.'+b64encode(payload)  
signature = HMACSHA256(encodedstring, secret)  

至此我们获得了所有的三个部分 将这个三个部分用"."拼接起来,就形成了JWT。 这样的话当我们去执行社交网站关注的时候就只要执行

https://www.fool.com/follow/?jwt=[jwt content]  

就可以了。

安全问题

这里有一篇blog列举了很多jwt的安全问题 而我之前碰到的问题是关于signature的:

这个题目的传送门 jwt就像ssl一样可以提供none加密算法,这也就给了攻击者利用的机会。

假设这样的情景: 一个论坛,有一个管理员账号admin,这个应用通过JWT来认证用户。JWT使用的算法是HMACSHA256.我们的目标是以admin的权限登陆该论坛。

这里我们可以先注册一个普通用户,劫持发送的cookie,根据jwt使用"."来分割三部分,我们可以解码header与payload。 对其中的内容进行修改,将header中的alg选项修改为None,将payload中的user对应名称由普通用户名修改为admin。 因为我们选择的加密算法是none,所以signature部分就是一个空的string. 这样我们就可以组成一个新的jwt b64encode(new_header).b64encode(new_payload).""(just an empty string)
forward这个请求,就可以以Admin权限登陆网站。