JWT 攻击面:
敏感信息泄露:
当服务端的秘钥泄密的时候,JWT的伪造就变得非常简单容易。对此,服务端应该妥善保管好私钥,以免被他人窃取。
header和payload部分实际上只是进行了base64编码
alg=None签名绕过:
签名算法确保恶意用户在传输过程中不会修改JWT。
但是标题中的alg字段可以更改为none。
一些JWT库支持无算法,即没有签名算法。当alg为none时,后端将不执行签名验证。将alg更改为none后,从JWT中删除签名数据(仅标题+’.‘+ payload +’.’)并将其提交给服务器
例子:
http://demo.sjoerdlangkemper.nl/jwtdemo/hs256.php
1 | import base64 |
1 | # 输出 |
server源码:
1 | # https://github.com/Sjord/jwtdemo/blob/master/MishalHS256.php |
加密方法是读取JWT的header部分吗?
将签名算法从非对称类型改为对称类型:
使用非对称加密算法(主要基于RSA、ECDSA,如S256)分发JWT的过程是使用私钥(private)加密生成JWT,使用公钥(public)解密验证。
使用对称加密算法(主要基于HMAC,如HS256)分发JWT的过程是使用同一个密钥(secret)生成和验证JWT。
1 | class FirebaseRS256 { |
一开始的流程是,服务端用私钥加密作为签名,然后用公钥进行验证。
由于我们很难知道私钥,所以我们想自己构造数据的话,没法用相同的私钥签名回去。
但解密时支持HS256
这种对称加密方式的话,它只有一个秘钥,如图是公钥。
也就是说我们只要找到公钥,就能篡改数据后,再把数据签名回去,这样能通过延签的部分。
HS256(对称加密)密钥破解:
如果HS256密钥强度较弱,则可以直接强制使用,通过爆破 HS256的秘钥可以完成该操作。
不过对 JWT 的密钥爆破需要在一定的前提下进行:
-
知悉JWT使用的加密算法
-
一段有效的、已签名的token
-
签名用的密钥不复杂(弱密钥)
所以其实JWT密钥爆破的局限性很大。
可以看到简单的字母数字组合都是可以爆破的,但是密钥位数稍微长一点或者更复杂一点的话,爆破时间就会需要很久。
修改KID参数:
kid
是jwt header
中的一个可选参数,全称是key ID
,它用于指定加密算法的密钥
1 | { |
因为该参数可以由用户输入,所以也可能造成一些安全问题。
任意文件读取:
kid
参数用于读取密钥文件,但系统并不会知道用户想要读取的到底是不是密钥文件,所以,如果在没有对参数进行过滤的前提下,攻击者是可以读取到系统的任意文件的。
1 | { |
SQL注入:
kid
也可以从数据库中提取数据,这时候就有可能造成SQL注入攻击,通过构造SQL语句来获取数据或者是绕过signature
的验证
1 | { |
命令注入
对kid
参数过滤不严也可能会出现命令注入问题,但是利用条件比较苛刻。如果服务器后端使用的是Ruby,在读取密钥文件时使用了open
函数,通过构造参数就可能造成命令注入。
1 | "/path/to/key_file|whoami" |
对于其他的语言,例如php,如果代码中使用的是exec
或者是system
来读取密钥文件,那么同样也可以造成命令注入,当然这个可能性就比较小了。
总结:
与许多其他方面一样,JWT基本上是安全的,但某些实现则不是。
该应用程序可能在JWT中存储敏感信息,允许更改签名算法,或者签名中使用的密钥强度不足、kid参数可控。
其他:
RSA的公钥和私钥区别
公钥和私钥是成对的,它们互相解密。
公钥加密,私钥解密。
私钥数字签名,公钥验证。
A 要给 B 发信息要用 B 的公钥加密,B 用自己的私钥解密。
A 发布了一篇文章,说这是我发的,我用自己的私钥签名了。由于之前 A 已经发布了他的公钥,所以其它人可以用这个公钥去验证。因为知道 A 私钥的只可能是 A 自己。
这样的话:加密是保护数据,签名是核对身份,解决的是不同需求。那么我的理解:不应该是需要解决什么问题就用什么吗?不希望第三方收到内容就加密,不希望第三方冒充发布就签名,或者同时使用。