JWT攻击
JWT是JSON Web Token的缩写,可用于身份认证,会话状态维持以及信息交换等任务。
JWT由三部分构成,分别称为 Header ,Payload 和 Signature ,各部分用“.”相连构成一个完整的Token,形如xxxxx.yyyyy.zzzzz。
遵循 RFC 7519 规范
Header
使用一个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));