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参数
- …
- HTTP头部(推荐)(Authorization: Bearer
严谨的结构化
- 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 payload
携带希望向服务端传递的信息
可以添加官方字段(field(claims))
- iss(lssuer):签发人
- sub(Subject):主题
- exp(Expiration time):过期时间
- aud(audience):受众
- nbf(Not Before):生效时间
- iat(Issued At):签发时间
- jti(JWT ID):编号
也可以添加自定义字段
- userID
- …
1 | { |
3 signature
签名,用于认证
创建签名过程:
- 从接口服务端拿到密钥,假设为secret
- 将header进行base64编码+payload进行base64编码
1 | // $Signature |
- 用私钥对签名进行加密,接收方(即服务器端)用公钥进行解密认证
4 JWT
Header、Payload、Signature三部分Base64URL算法编码后以”.”分割,拼接成一个字符串
- Base64URL:(JWT最为令牌有些场合需要用到URL)
类似Base64,但稍有不同。Base64有三个字符”+”、”/“、”=”,在URL里面有特殊含义。所以要被替换掉:
- “=”被省略
- “+”替换成”-“
- “/“替换成”_”
- Base64URL:(JWT最为令牌有些场合需要用到URL)
1 | //JWT |
- 假设原始Json结构是:
1 | //Header |
- 如果密钥是secret,那么最终的JWT的结果是:
1 | eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjYwYmQifQ.-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM |
- 可以在:https://jwt.io/上检验这个结果
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 爆破秘钥
已知道加密算法,密钥太短,通过暴力破解的方式,可以得到密钥
工具:
JohnTheRipper(https://github.com/magnumripper/JohnTheRipper)
1
2
3
4
5
6git clone https://github.com/magnumripper/JohnTheRipper
cd JohnTheRipper/src
./configure
make -s clean && make -sj4
cd ../run
./john jwt.txtc-jwt-cracker(https://github.com/brendan-rius/c-jwt-cracker)
1
2
3make
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
2sql="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操作。