Ashing's Blog

想学的太多 懂得的太少

0%

WEB认证授权3—JWT Auth及其安全问题

0x01 起源

  • 在前后端分离的应用中,后端为Model层,为前端提供数据访问的API。为了保证数据安全可靠地在用户与服务端之间传输,实现服务端的认证就极为必要。

  • 常见的服务端认证方法有:

    • 基于Cookie的认证:session

      • 每次请求都带上cookie,取出相应字段并与服务端进行对比,以实现身份的认证(具有CORS问题)
    • 基于Token的认证:Json Web Toekn

      • 请求在HTTP的头部(或其他位置)附上token,由服务器check signature来实现
  • JWT不一定完全取代Cookie-Session Auth体制,其也存在缺点。JWT更适合用于认证签名的过程,更适合一次性操作的认证。

0x02 什么是Json Web Token?

  • JWT是一套开放的Json标准,定义了一套简介且URL安全的方案,以安全地在客户端和服务器之间传输Json格式的信息

0x03 JWT的特点

  • JWT默认不加密。也可以加密,生产原始Token以后,可以用密钥再加密一次
  • JWT不加密情况下,不能将秘密数据写入JWT

1 自身优点

  • 体积小(一串字符串):传输速度快

  • 传输方式多样

    • HTTP头部(推荐)(Authorization: Bearer
    • URL
    • POST参数
  • 严谨的结构化

    • payload中包含了所有与用户相关的信息,且支持定制。故,如果用户访问路由、有效期等信息则无需再去连接数据库验证信息的有效性(避免了传统session的CORS问题)
  • 支持跨域验证:多应用于单点登录

2 相比较与传统的服务端验证

  • 1.充分以来无状态API,契合RESTful设计原则(无状态的HTTP)

    • 状态:请求的状态是 client 与 server 交互过程中,保存下来的相关信息,客户端的保存page/request/session/application 或者全局作用域中,而 server 的一般存在 session 中
    • 有状态的API:server 保存了 client 的请求状态, server 通过 client 传递的 sessionID 在其 session 作用域内找到之前交互的信息并应答
    • 无状态 API:无状态是 RESTful 架构设计的一个非常重要的原则。无状态 API 的每一个请求都是独立的,它要求客户端保存所有需要的认证信息,每次发请求都要带上自己的状态,以 url 的形式提交包含 cookies 等状态的数据
    • JWT 的设计契合无状态原则:用户登录之后,服务器会返回一串 token 并保存在本地,在这之后的服务器访问都要带上这串 token,来获得访问相关路由、服务及资源的权限。比如单点登录就比较多地使用了 JWT,因为它的体积小,并且经过简单处理(使用 HTTP 头带上 Bearer 属性 + token )就可以支持跨域操作
  • 2.易于实现CDN,将静态资源分布式管理

    • 传统的session验证中,服务端必须保存sessionID,用于与用户传过来的cookie验证。
    • 而一开始 sessionID 只会保存在一台服务器上,所以只能由一台 server 应答,就算其他服务器有空闲也无法应答,无法充分利用到分布式服务器的优点。 JWT 依赖的是在客户端本地保存验证信息,不需要利用服务器保存的信息来验证,所以任意一台服务器都可以应答,服务器的资源也能被较好地利用。
  • 3.验证解耦,随处生成

    • 无需使用特定的身份验证方案,只要拥有生成 token 所需的验证信息,在何处都可以调用相应接口生成 token,无需繁琐的耦合的验证操作,可谓是一次生成,永久使用
  • 4.比 cookie 更支持原生移动端应用

  • 原生的移动应用对 cookie 与 session 的支持不够好,而对 token 的方式支持较好

3 缺点

  • 1.权限签发在有限期内始终有效:

  • 由于服务器不保存Session的状态,因此无法在使用过程中废止某个Token或者更改Token权限。也就是说,一旦JWT签发了,在到期前会始终有效,除非服务器部署额外的逻辑,即不易应对数据过期

  • 2.本身包含了认证信息,一旦泄漏任何人都可以获得令牌的权限。(如果保存在Local Storage中容易受到XSS攻击)

  • 为了减少盗用:

    • JWT的有效期应该设置比较短,对于一些重要的权限,使用时应该再次对用户进行认证。
    • JWT使用HTTPS协议进行传输

0x04 JWT工作原理

  • 登录过程

  • 请求认证

  • 1.client使用自己的账号密码发送post请求login
  • 2.因为首次接触,服务器会校验账号与密码是否合法,如果一致,则根据密钥生成一个token并返回
  • 3.Browser收到后,client收到这个token并保存在本地
  • 4.如果后面需要访问一个受保护的路由或资源时,只要附加上token(通常使用Header的Authorization属性)发送到服务器,然后服务器来检查这个token是否有效并作出响应即可

0x05 组成结构

1 header

  • 用于描述元信息,例如:产生signature的算法:

    • typ:表示JWT
    • alg:指定使用哪种一种密码算法来创建signature(默认时HS256)
1
2
3
4
{    
"typ": "JWT",
"alg": "HS256"
}

2 payload

  • 携带希望向服务端传递的信息

  • 可以添加官方字段(field(claims))

    • iss(lssuer):签发人
    • sub(Subject):主题
    • exp(Expiration time):过期时间
    • aud(audience):受众
    • nbf(Not Before):生效时间
    • iat(Issued At):签发时间
    • jti(JWT ID):编号
  • 也可以添加自定义字段

    • userID
1
2
3
4
5
6
7
8
9
{
"userId": "b08f86af-35da-48f2-8fab-cef3904660bd"
"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提供唯一标识
}

3 signature

  • 签名,用于认证

  • 创建签名过程:

    • 从接口服务端拿到密钥,假设为secret
    • 将header进行base64编码+payload进行base64编码
1
2
// $Signature
HS256(Base64(Header) + "." + Base64(Payload), secretKey)
  • 用私钥对签名进行加密,接收方(即服务器端)用公钥进行解密认证

4 JWT

  • Header、Payload、Signature三部分Base64URL算法编码后以”.”分割,拼接成一个字符串

    • Base64URL:(JWT最为令牌有些场合需要用到URL)
      • 类似Base64,但稍有不同。Base64有三个字符”+”、”/“、”=”,在URL里面有特殊含义。所以要被替换掉:

        • “=”被省略
        • “+”替换成”-“
        • “/“替换成”_”
1
2
//JWT
JWT = Base64(Header) + "." + Base64(Payload) + "." + $Signature
  • 假设原始Json结构是:
1
2
3
4
5
6
7
8
9
//Header
{
"typ": "JWT",
"alg": "HS256"
}
// Payload
{
"userID": "b08f86af-35da-48f2-8fab-cef3904660bd"
}
  • 如果密钥是secret,那么最终的JWT的结果是:
1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjYwYmQifQ.-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM

0x06 应用

  • 客户端收到服务器返回的JWT,可以存储在Cookie里面,也可以储存在LocalStorage

  • 此后,客户端每次与服务端通信,都带上这个JWT。

    • 携带在Cookie里:不能跨域
    • 携带在HTTP头Authorization里:可以跨域
  • JWT适合一次性的命令认证,颁发一个有效期极短的JWT,即使暴露了危险也很小,由于每次操作都会生成新的JWT,因此也没必要保存JWT,真正实现无状态。

1 认证

  • JWT 的目的不是为了隐藏或者保密数据,而是为了确保数据确实来自被授权的人创建的(不被篡改)

    • header和payload完全可知
    • 签名->认证

2 接口调用

  • 认证是否有权利来调用API,通常在API调用请求的HTTP Header中附上JWT

3 有状态会话

  • HTTP无状态,所以客户端和服务端需要解决的问题是如何让他们之间变得有状态。

    • 例如:只有是登录状态的用户才有权调用某些接口,一般记住用户登录状态传统方法是session机制
  • 而JWT理论上可以代替session机制,且有两点好处

    • 1.用户不需要提前登录
    • 2.后端不需要记录用户的登录信息
  • 客户端的本地保存一份合法的 JWT, 当用户需要调用接口时,附带上该合法的 JWT,每一次调用接口,后端都使用请求中附带的 JWT 做一次合法性的验证。这样也间接达到了认证用户的目的

4 分布式站点的单点登录(SSO)

0x07 JWT Auth安全问题

1 密码算法篡改攻击

  • 1.修改算法为none(低版本)

    • 若后端支持none算法,则可以把alg字段修改为none
  • 2.修改算法RS256为HS256

    • RS256是非对称加密算法

    • HS256是对称加密算法

    • 如果JWT内部函数支持的RS256算法又同时支持HS256算法

      • 已知公钥
      • 把算法改成HS256,后端就会把公钥当作密钥来加密

2 爆破秘钥

  • 已知道加密算法,密钥太短,通过暴力破解的方式,可以得到密钥

  • 工具:

    • JohnTheRipperhttps://github.com/magnumripper/JohnTheRipper)

      1
      2
      3
      4
      5
      6
      git clone https://github.com/magnumripper/JohnTheRipper
      cd JohnTheRipper/src
      ./configure
      make -s clean && make -sj4
      cd ../run
      ./john jwt.txt
    • c-jwt-crackerhttps://github.com/brendan-rius/c-jwt-cracker)

      1
      2
      3
      make
      make OPENSSL=/usr/local/opt/openssl/include OPENSSL_LIB=-L/usr/local/opt/openssl/lib
      ./jwtcrack eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.cAOIAifu3fykvhkHpbuhbvtH807-Z2rI1FS3vX1XMjE

3 伪造JWT

  • 1.敏感信息泄露问题

  • JWT以base64URL编码传输,数据基本上等于明文传输,如果有重要内容,则可以base64解码得到敏感信息

  • 2.信息可控问题:

    • Kid可控:ket的值在数据库中,数据库代码为:

      1
      2
      sql="select * from table where kid=$kid "
      res=exec(sql)

      所以可以构造以下语句,使得res=1,然后用1作为秘钥来签名加密,可以顺利伪造JWT。

      1
      kid = 0 union select 1
    • 公钥地址可控(HITB-CTF-2017):利用OPENSSL生成公钥、私钥对,利用注册写一个paste,内容为公钥,利用下载功能获取到公钥的保存地址。

    • 接下来用 https://jwt.io/ 来生成伪造的jwt。由于要伪造admin,所以sub字段要修改为admin。在公钥和私钥部分填上生成的公钥和私钥,在头部kid字段填上公钥的地址。

    • 得到伪造的JWT,之后每次操作,把Toekn替换成伪造的JWT操作。

4 重放攻击

5 MITM(Man-In-The-Middel)Attacks