JWT攻击

2020-09-22

JWT是JSON Web Token的缩写,可用于身份认证,会话状态维持以及信息交换等任务。

JWT由三部分构成,分别称为 Header ,Payload 和 Signature ,各部分用“.”相连构成一个完整的Token,形如xxxxx.yyyyy.zzzzz。

遵循 RFC 7519 规范

使用一个JSON格式字符串声明令牌的类型和签名用的算法等,形如:

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

该字符串经过Base64Url编码后形成JWT的第一部分xxxxx。

Header声明一些标准:

Token Description Format
typ 令牌类型 (JWT/JWE/JWS等) string
alg 用于签名或加密的算法 string
kid Key ID - 用作查找 string
x5u x509证书的URL URL
x5c 用于签名的x509证书(作为嵌套的JSON对象) JSON object
jku JWKS格式键的URL URL
jwk 用于签名的JWK格式密钥(作为嵌套的JSON对象) JSON object

Payload

Payload 部分也是一个 JSON 对象,同样的,该字符串经过Base64Url编码形成JWT的第二部分yyyyy。

Payload7个官方字段:

Payload Key Description Format
iss 签发人 (issuer) string
sub 主题 (subject) string
aud 受众 (audience) string
exp 过期时间 (expiration time) Date
nbf 生效时间 (Not Before) Date
iat 签发时间 (Issued At) Date
jti 编号 (JWT ID) string

当然也可以使用私有字段。

Signature

Signature 部分是对前两部分的签名,防止数据篡改。

将xxxxx.yyyyy使用alg指定的算法加密,然后需要指定一个私有密钥(secret),再使用 Header 里面指定的签名算法(默认是 HMAC SHA256)得到JWT的第三部分zzzzz。

签名算法:

HMACSHA256(
  base64UrlEncode(header) + "." +  base64UrlEncode(payload),
  secret
)

JWT 的几个特点

1.JWT 不仅用于认证,也携带了Payload信息。对于服务端来说这个Payload可以直接拿来使用,可降低查询数据库的次数。同时也是一种便捷的Auth0解决方案。

2.JWT 由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务端专门部署额外的逻辑。

3.JWT 最大的缺陷是认证私钥,存在于签名里,存在暴力破解可能性,私钥一旦泄露,任何人都可以获得该令牌的所有权限。(该问题可以通过RSA非对称密钥来解决)

JWT攻击

JWT存在如下几个问题

1.敏感信息泄露

由于Header和Payload部分是使用可逆base64方法编码的,因此任何能够看到令牌的人都可以读取数据。

2.算法修改攻击

JWT支持将算法设定为 None 。如果 alg 字段设为 None ,那么签名会被置空,这样任何 token 都是有效的。

如果签名算法为 RS256,那么会选择用私钥进行签名,用公钥进行解密验证。如果服务端不严谨,我们拿到了泄露的公钥 pubkey。此时我们可以尝试将 header 的 alg 算法从 RS256 改为 HS256 ,此时即非对称密码变为对称加密,如果后端的验证也是根据 header 的 alg 选择算法,那么显然正中下怀。

3.密钥可控(SQL注入)

假如header头:

{
    "alg":"SH256",
    "typ":"JWT",
    "kid":"111"
}

其中kid为密钥key的编号id,类似逻辑:

select * from table where kid=$kid

如果在这里对 $kid 进行恶意篡改,例如:

kid = 0 union select 555

这样查询出来的结果为555,这样等同于我们控制了密钥key,拥有了密钥key,即可伪造认证。

4.暴力破解

我们知道 Signature 算法里有私钥,如果这个私钥的复杂度不够,那么显然可以通过暴力破解来攻击。

比如现成的JWT暴力破解工具:https://github.com/brendan-rius/c-jwt-cracker

生成RSA公私钥

命令行:

openssl genrsa -out rsa_private_key.pem 1024
openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem

在线生成:

https://www.bejson.com/enc/rsa/

注意事项

1.secret base64 encoded 对应处理办法

如果在 https://jwt.io/ 网站上勾选 secret base64 encoded,那么意味着secret密钥是经过 base64 encode 的,所以需要先 base64_decode 原来的 secret 密钥,再传入。

比如PHP里:

JWT2::encode($payload, base64_decode($secret));