SecMap 系列之 JWT
SecMap 系列停止更新有一段时间了,年初立的 Flag 不能倒!
JWT(Json Web Token) 是一个非常轻量级的规范。它本质上是一个 token,这个 token 我们理解为 访问资源的凭据 即可,即它是一种基于 Token 的会话管理方案。JWT 一个很重要的特点就是,如果要想确认它是否有效,我们只需要看 JWT 本身的内容就可以验证了。
由于 JWT 的使用场景主要还是在认证上,所以本文就不多啰嗦其他场景了。
那在 JWT 之前,我们是怎么做访问资源凭据的验证呢?答案是 cookie-session 机制。对于 cookie-session 这一套我们是比较熟悉的,那 JWT 认证的流程是什么样的呢?
别着急,先来看一个常见的误区。
首先,JWT 标准(见资料 1)中是这么说的:
1 | |
所以,你可以这么说,一个 JWT 要么是一个 JWE,要么是一个 JWS,而我们常说的 JWT 其实特指的是 JWS。
所以为了严谨,下文将严格区分它们。如果提到了 JWT 的话,那就是指的 JWS + JWS。
那么什么是 JWS、JWE 呢?
JWS(JSON Web Signature)它其实就是一个 JSON,由 3 个字段组成,每个字段都需要经过 url 编码 + Base64Url 编码。它们之间用 . 连接:
1 | |
header: 记录 JWT 本身的一些信息,包含以下键
typ: token 类型,这个值是写死的,就是 JWT,它的作用在于出现嵌套的时候,可以识别出哪一层的 json 是个 JWSalg: 签名算法(比如 HMAC 类: HS256等、RSASSA 类: RS256、ECDSA 类ES256等、none)。相信各位在小学六年级就知道了,这里的签名算法都是要用到密钥的。cty: 可选,比较少见,如果这个 JWS 包含另一个 JWS 的话(注意与 typ 的区别),它就需要置为 JWTjwk: 可选,JSON Web Key, 当签名算法所用的密钥有很多个的时候(比如有一个统一提供 JWT 认证的服务,有很多应用接入),服务端在校验 JWT 的完整性时不知道要用哪一个私钥进行验证,有了这个键之后就知道之前颁发 JWT 的时候用的是哪个公钥了。这里的 jwk 需要按照资料 5 的规范,也是一个 json,里面有它自己的键,感兴趣的话可以自行查看 rfc,这里就不赘述了。kid: 可选,jwk 的编号,如果签名算法所用的密钥很多的话(同上面的情况),可以通过这个标识来判断/查询用的是哪一个密钥。它与 jwk 有类似的作用。如果同时出现 kid 和 jwk 那么 kid 的含义是标识用的是哪个 jwk。jku/x5u/x5c/x5t/x5t#S256/crit: 这些用得少就不赘述了,见 rfc 文档即可。payload: JWT 标准将 Payload 中的键称为 JWT 声明(claims),有以下 3 类:
registered claims,包含:
iss: Issuer, 签发人sub: Subject, 主题aud: Audience, 使用对象exp: Expiration Time, 到期时间,到达或者超过到期时间的应当拒绝处理nbf: Not Before, 在此时间之前的应当拒绝处理iat: Issued At, 签发时间jti: JWT ID, JWT 的唯一标识符registered claims 这种申明,有两种选择,要么去 IANA JSON Web Token Registry 中注册(见资料 2),要么取一个不太会被用到的名字(防止重复),比如加上你的域名前缀。这种叫 public claim。我也没懂为啥会有这种需求,我的理解是这玩意自定义之后,可能是给一整个组织去使用的,算是一种定制化的 JWT 规范。private claims,需要注意的是这里的键名不要覆盖了上面两种键名,比如 exp 就是过期时间,不能把它当做用户名来用。当然如果服务端和客户端约定好了,非要这么干,那也不是不行,只是很不符合规范。signture: 根据指定的算法,用密钥对 header + payload 进行签名,用于校验 JWS,避免 伪造/篡改最后有必要说明的是,我们常用的 JWS 其实是紧凑模式。相比之下还有一种通用模式和紧凑通用模式,它们的区别是什么呢?

由于 JWS 只对整个 json 做了签名,其中 paylaod 还是明文的,Base64Url 解开就行。那如果我不想让别人看里面的内容怎么办呢?就可以用 JWE。
JWE(JSON Web Encryption)也是一个 json,由 5 个字段组成,每个字段都需要经过 url 编码 + Base64Url 编码。它们之间用 . 连接:
1 | |
header: 与 JWS 基本一致,但是有几个键不一样
alg: 算法名称,该算法用于下面的 JEKenc: 算法名称,用于加密 payloadzip: 可选,在加密前压缩数据的算法JEK: JWE Encryption Key,它是由随机生成的 CEK(Content Encryption Key) 通过加密得到的,至于是哪个加密算法,就是由 alg 指定的。这个 CEK 后面要用于加密 payload。JIV: JWE Initialization Vector,初始的 IV,有些加密方式需要这玩意Ciphertext: 对 payload 加密后的数据。Tag: 就是 Authentication Tag,加密算法产生的附加数据,用于保护密文的完整性与 JWS 类似,JWE 也有三种模式:紧凑模式、通用模式和紧凑通用模式。
最后举个例子来看一下 JWE 的加密过程,我觉得一图胜千言(紧凑模式):

非常好理解,其实就是服务端在验证身份之后,会给你发一个 token,然后之后你所有的请求都带上这个 token,服务端就知道是你了。
那么 JWT 什么好处呢?
那么 JWT 什么坏处呢?
综上,我对 JWT 的看法是,没有银弹。你不能期望它解决所有认证问题。在使用 JWT 的时候,是否适合用它是需要仔细思考的,每种方案都有它的好、坏与适配的场景,我们在合适的场景下选择合适的方案就行了,不要执着于使用 JWT。
由于 JWE 的使用场景实在是太少了,我也没见过,所以下面就说 JWS 吧,等我遇到了再补上。
JWS 的安全性全靠签名,所以大部分攻击场景都是针对签名的。
JWS 的 header 和 payload 都只经过简单的编码,所以不应该存放敏感数据。根据我的经验,现在其实还是有不少人认为 base64 是“加密算法”,如果是这么认为的那就很有可能将敏感信息放在里面,导致信息泄露。
只要你拥有密钥,那么你就可以构造任意内容的合法的 JWS。所以最直接的思路就是拿到一个 JWS 之后爆破它的签名密钥,这个过程还是离线的,所以速度会比较快。
所以密钥空间一定要足够大。
上面提到 header 中有个键是 alg,用于标识 signature 的签名算法。而其中有一个值是 none,它是 JWT 规范中强制要求实现的(...only HMAC SHA-256 ("HS256") and "none" MUST be implemented by conforming JWT implementations...)。其实我不太理解为什么要加这个值。
所以在自己实现 JWT 的时候,如果没有注意到这种特殊情况(可能有些库的实现也有问题,不过目前我还没找到),攻击者就可以设置 alg 为 none,这样 paylaod 就可以随便伪造了。
比如:
1 | |
它的 JWS 是 eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJuYW1lIjoiVHIweSJ9.
我们将 name 改为 admin 也可以通过验证。
那我们应该怎么处理这种情况呢?如果我们认为所有算法是 none 的 JWS 都是不合法的,那么按照 rfc 文档的规定,这样粗暴地处理其实是不符合标准的,虽然我也感觉真的有这个需求的人应该特别特别少。
我们可以参考 Python 的第三方库 pyJWT 的实现:

默认情况下,只要你的算法是 none,验签的时候直接一棍子打死。但是你可以加上一个参数来使用,这里附上 pyJWT 作者的吐槽:

可以说是很豹笑了。我觉得这个做法不但比较安全,还符合 rfc 规范(可以让你这么用,但是你必须额外设置一个参数),值得我们学习。
这种情况本质上是服务端没有校验算法与密钥是否属于同一种类型的组合。
场景:服务端只使用了 HS256 来做签名算法。那么自然就是用私钥签名,公钥验签。
假设条件:服务端没有校验收到的 JWS header 里的算法是不是自己采用的算法,而是直接采用 JWS 里的算法来验签,且认为算法应该是 RS256(脑子里想的是要用 RS256 算法,手上却用了 JWS 里的算法,至于密钥?那当然用的是 RS256 的公钥了)。
那么攻击者可以构造一个 JWS,算法为 HS256,利用公开的公钥作为签名密钥,那么就可以通过验签了。因为服务端会把算法设定为 HS256,而此时验证签名的密钥就是公钥。
那么怎么避免这个问题呢?
另外,有些库是禁止 HMAC 使用非对称加密的密钥的,比如 pyJWT:
1 | |
这种大多出现在 CTF 上。
当 JWS 的密钥很多的时候,可以通过 kid 来确定使用哪个密钥。如果 kid 的相关逻辑存在问题的话,就会出现安全问题。
任意文件上传 + 可通过 kid 指定特定路径下的密钥
例子:2017 HITB CTF: Pasty
SQL 注入
例子:2021 年网鼎杯的玄武组 Web 题: js_on
其实 JWT 相关的知识点还有一些
但是不太常用
(看 rfc 文档真的是太痛苦了...)
橘友们可以慢慢研究
我就先看到这