1. 产生原因
为了限制 HTTP API 资源访问,如一个用户更改另一个用户的密码,而保护 API 的困难在于请求是无状态的,通过 API 本身是无法知道有两个请求是否来自同一用户。
2. JWT
JWT(Json Web Token)
是一种用于在网络应用之间安全地传输信息的开放标准。它通过将用户信息以 JSON 格式加密并封装在一个 token 中,然后将该 token 发送给服务端进行验证,从而实现身份验证和授权。
JWT
特别适用于分布式站点的单点登录(SSO)场景,它本质上是一种令牌格式,与终端设备、服务器、传输类型都无关,它只规范了令牌的格式而已。
3. 加密解密流程
- 生成:客户端登录成功后,服务器使用用户的信息(如用户ID、用户名等)以及服务器端的密钥,通过特定的加密算法生成
JWT
- 发送:客户端请求时携带
JWT
发送给服务器,通常是通过 HTTP 请求的头部Authorization
字段发送。 - 服务器验证:服务器收到
JWT
后,校验格式并解析出头部和载荷部分,再使用存储在服务器端的密钥和相同的加密算法,对头部和载荷进行签名验证来确认请求的合法性 - 响应处理:当
JWT
验证成功后,服务器才正式响应请求
4. 认证区别
- 传统 Token :登录成功后,服务端生成一个随机 Token 并分配给用户,同时在服务端(如数据库或缓存)保存一份记录,用户在后续的请求中需携带它,服务端会进行验证
JWT
:登录成功后,服务端使用JWT
生成一个随机 Token 并分配给用户,但不存储它,用户在后续的请求中需携带它,服务端通过JWT
进行验证
5. 组成
JWT
包含三个部分:头部(Header
)、载荷(Payload
)、签名(Signature
),以句点分隔,分别使用 Base64url
编码。
jwt.io 可用于在线测试
1. Header
Header
是 Token
的元信息,头部承载两部分信息,由 json 格式经 Base64url
(对 Base64
编码后的字符串,用 -
替代 +
,用 _
替代/
) 编码得到。
- 声明令牌类型
typ
: 这里是JWT
- 声明加密的算法
alg
: 通常直接使用HMAC SHA256
1 | 定义 Header |
2. Payload
Payload
存放有效信息,如用户ID、用户名等,也可以包含其他自定义信息,载荷的内容是经过Base64url
编码,能够被解码。它可以包含三个部分
- 标准中注册的声明
- 公共的声明
- 私有的声明
1. 标准中注册的声明 (建议但不强制使用)
iss
: JWT 签发者sub
: JWT 所面向的用户aud
: 接收 JWT 的一方exp
: JWT 的过期时间,这个过期时间必须要大于签发时间nbf
: 定义在什么时间之前,该 JWT 都是不可用的.iat
: JWT 的签发时间jti
: JWT 的唯一身份标识,主要用来作为一次性 Token,从而回避重放攻击。
2. 公共的声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息。其可在客户端解密,不建议添加敏感信息。
3. 私有的声明
私有声明是提供者和消费者所共同定义的声明。其可在客户端解密,不建议添加敏感信息。
1 | 定义 Payload |
3. Signature
用于验证 Token 的合法性,签名是由三部分并结合特定的加密算法生成:
Header
:Base64url
后的头部Payload
:Base64url
后的载荷Secret
: 密钥
1 | // Header + Payload,确保了签名对于此特定令牌是唯一的 |
6. 攻击方式
1. 修改加密算法伪造 Token
JWT
中最常用的两种算法为 HMAC 和 RSA,若某程序在 JWT
传输过程中使用 RSA 算法,同时使用密钥 private_src 对 JWT
进行签名,公钥 public_abc 对签名进行验证。获取公钥后,将 JWT
的加密算法修改为 HMAC,同时使用获取到的公钥作为算法的密钥,对 token 进行签名,发送到服务器端,服务器端会将 RSA 的公钥视为当前算法(HMAC)的密钥,而 HMAC 是对称加密算法,故服务器使用密钥 abc 对接收到的签名进行验证,从而造成 token 伪造问题。
2. None 算法攻击绕过验证
在 Header 中指定 alg 为 None,同时不添加 Signature,服务器在验证JWT时会认为这个JWT是不需要签名的,从而跳过了对签名的验证,直接信任 JWT
中的信息,实现伪造身份绕过服务器验证。
3. kid 参数
kid
是 JWT
头部中的一个可选参数,全称为 Key ID,它用于指定加密算法所使用的密钥。这可能导致以下问题:
- 任意文件读取:系统并不会验证
kid
参数路径指向的文件是否是有效的密钥文件。因此,在没有对参数进行过滤或验证的情况下,可造成任意文件读取
1 | { |
- SQL 注入:
kid
参数也可能从数据库中提取数据,故存在 SQL 注入攻击的风险
1 | { |
- 命令注入:若服务器后端使用的是 Ruby,在读取密钥文件时使用了 open 函数,通过构造参数可能实现命令注入
1 | { |