一文理解JWT鉴权登录的安全加固
有关JWT的基础知识,可以查看之前的博客: 快速了解会话管理三剑客cookie、session和JWT
在之前的博客《一文理解JWT在鉴权登录的应用》介绍了JWT在鉴权登录中的使用。但是不恰当地使用 JWT 可能会对应用程序安全产生负面影响。
本文将针对JWT在鉴权登录业务场景下的安全进行讲解。
JWT使用时的安全建议
1. 切勿在令牌中传输用户的敏感数据
由于JWT的载荷部分是可以被明文获取的,因此,如果有效载荷中存在敏感信息的话,就会发生信息泄露。
2. 传输令牌时一定要使用安全连接
理由同上。
3. 使用“刷新令牌”机制
由于JWT是公开传输的,获取了令牌的黑客能够继续使用该JWT访问应用程序,所以使用最好双JWT机制降低安全风险。使用方法在《一文理解JWT在鉴权登录的应用》有详细讲解。
4. 增加JWT的业务参数,用于校验
可以在Payload中增加一些业务上的字段,用于校验当前JWT是否被滥用。例如增加设备号,用户uuid、权限的信息,可以与上下文的信息进行对照,提高爬虫的门槛与接口安全性。
5. 始终验证并过滤从用户接收的数据
kid是JWT header中的一个可选参数,它用于指定加密算法的密钥。因为该参数可以由用户输入,系统并不知道用户想要读取的到底是不是密钥文件。如果在没有对参数进行过滤的前提下,攻击者是可以读取到系统的任意文件的、造成SQL注入或命令注入等漏洞。
{
"alg" : "HS256",
"typ" : "jwt",
"kid" : "/etc/passwd"
}
{
"alg" : "HS256",
"typ" : "jwt",
"kid" : "key11111111' || union select 'secretkey' -- "
}
所以,验证并过滤从用户接收的数据是必要的。
6. 提高对称加密的秘钥强度
如果加密的密钥强度较弱的话,攻击者可以直接通过蛮力攻击方式来破解密钥,可以使用PyJWT、John Ripper或c-jwt-cracker进行破解测试。PyJWT库具体地址看参考文档4。c-jwt-cracker库集体地址看参考文档5。
秘钥不定期的变化。这对用户来说不太方便,因为他们将不得不再次通过身份验证,但这有助于避免安全问题的发生。
7. 服务端增加授权签名算法的白名单
签名算法可以确保JWT在传输过程中不会被恶意用户所篡改,但头部中的alg字段却可以改为None,即不使用签名算法。这样的话,当signature设置为空时,后端将不执行签名验证。
将alg字段改为none后,系统就会生成这样形式的JWT,然后将其提交给服务器并通过校验:
base64UrlEncode(header) + "." + base64UrlEncode(payload)
所以,有必要在服务端增加已授权算法的白名单,并删除所有与服务端上授权算法不同的签名算法的JWT。
8. 最好只使用一个签名算法
在使用非对称算法进行令牌签名的情况下,签名应使用私钥,而签名验证应使用公钥。由于使用JWT的某些库包含逻辑错误——当收到用对称算法签名的令牌时,将使用公钥作为验证签名的secret。由于公钥并不是秘密数据,因此黑客可能会获得公共服务密钥并用于签署自己的令牌。
所以,当网站采用非对称加密验证,且不对签名算法进行限制的话,存在这样的漏洞:
通过一定手段,获取到非对称加密的公钥,将alg字段改为对称加密算法。
使用公钥对JWT进行签名,发送给服务端。
服务端会将对称加密的公钥作为验证签名的秘钥,使用对称加密算法对接收的JWT进行验证。
为了说明这个问题,以下实验部分节选自参考文档7的部分内容:
使用非对称加密算法RS256,新建一个JWT如下:
header:
{
"alg": "RS256",
"typ": "JWT"
}
payload:
{
"id": "1337",
"username": "bizone",
"iat": 1594209600,
"role": "user"
}
转为JWT为:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEzMzciLCJ1c2VybmFtZSI6ImJpem9uZSIsImlhdCI6MTU5NDIwOTYwMCwicm9sZSI6InVzZXIifQ.YLOVSKef-paSnnM8P2JLaU2FiS8TbhYqjewLmgRJfCj1Q6rVehAHQ-lABnKoRjlEmHZX-rufHEocDxGUYiGMjMexUQ3zt-WqZITvozJ4pkvbV-mJ1nKj64NmqaR9ZkBWtmF-PHJX50eYjgo9rzLKbVOKYOUa5rDkJPHP3U0aaBXFP39zsGdOTuELv436WXypIZBeRq2yA_mDH13TvzegWCK5sjD4Gh177bCq57tBYjhGIQrDypVe4cWBPlvwFlmG8tdpWGu0uFp0GcbTAfLUlbTSuGROj88BY0XeUs0iqmGlEICES3uqNx7vEmdT5k_AmL436SLedE0VHcyxve5ypQ
在这种情况下,使用RS256算法签名,将需要公钥和私钥。
公钥:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv
vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc
aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy
tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0
e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb
V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9
MwIDAQAB
-----END PUBLIC KEY-----
私钥:
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWw
kWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mr
m/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEi
NQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV
3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2
QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQABAoIBACiARq2wkltjtcjs
kFvZ7w1JAORHbEufEO1Eu27zOIlqbgyAcAl7q+/1bip4Z/x1IVES84/yTaM8p0go
amMhvgry/mS8vNi1BN2SAZEnb/7xSxbflb70bX9RHLJqKnp5GZe2jexw+wyXlwaM
+bclUCrh9e1ltH7IvUrRrQnFJfh+is1fRon9Co9Li0GwoN0x0byrrngU8Ak3Y6D9
D8GjQA4Elm94ST3izJv8iCOLSDBmzsPsXfcCUZfmTfZ5DbUDMbMxRnSo3nQeoKGC
0Lj9FkWcfmLcpGlSXTO+Ww1L7EGq+PT3NtRae1FZPwjddQ1/4V905kyQFLamAA5Y
lSpE2wkCgYEAy1OPLQcZt4NQnQzPz2SBJqQN2P5u3vXl+zNVKP8w4eBv0vWuJJF+
hkGNnSxXQrTkvDOIUddSKOzHHgSg4nY6K02ecyT0PPm/UZvtRpWrnBjcEVtHEJNp
bU9pLD5iZ0J9sbzPU/LxPmuAP2Bs8JmTn6aFRspFrP7W0s1Nmk2jsm0CgYEAyH0X
+jpoqxj4efZfkUrg5GbSEhf+dZglf0tTOA5bVg8IYwtmNk/pniLG/zI7c+GlTc9B
BwfMr59EzBq/eFMI7+LgXaVUsM/sS4Ry+yeK6SJx/otIMWtDfqxsLD8CPMCRvecC
2Pip4uSgrl0MOebl9XKp57GoaUWRWRHqwV4Y6h8CgYAZhI4mh4qZtnhKjY4TKDjx
QYufXSdLAi9v3FxmvchDwOgn4L+PRVdMwDNms2bsL0m5uPn104EzM6w1vzz1zwKz
5pTpPI0OjgWN13Tq8+PKvm/4Ga2MjgOgPWQkslulO/oMcXbPwWC3hcRdr9tcQtn9
Imf9n2spL/6EDFId+Hp/7QKBgAqlWdiXsWckdE1Fn91/NGHsc8syKvjjk1onDcw0
NvVi5vcba9oGdElJX3e9mxqUKMrw7msJJv1MX8LWyMQC5L6YNYHDfbPF1q5L4i8j
8mRex97UVokJQRRA452V2vCO6S5ETgpnad36de3MUxHgCOX3qL382Qx9/THVmbma
3YfRAoGAUxL/Eu5yvMK8SAt/dJK6FedngcM3JEFNplmtLYVLWhkIlNRGDwkg3I5K
y18Ae9n7dHVueyslrb6weq7dTkYDi3iOYRW8HRkIQh06wEdbxt0shTzAJvvCQfrB
jg/3747WSsf/zBTcHihTRBdAv6OmdhV4/dD5YBfLAkLrd+mX7iE=
-----END RSA PRIVATE KEY-----
为验证JWT有效性,使用参考文档2的网站如下图:
header:
{
"typ": "JWT",
"alg": "HS256"
}
payload:
{
"id": "1337",
"username": "bizone",
"iat": 1594209600,
"role": "admin"
}
转化为JWT的头部和载荷部分如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEzMzciLCJ1c2VybmFtZSI6ImJpem9uZSIsImlhdCI6MTU5NDIwOTYwMCwicm9sZSI6ImFkbWluIn0
现在只需要使用公钥读取签名。
首先,把密钥转移到十六进制表示法,如下图。
然后使用openssl生成一个签名,如下图。
将值e1r1nwnso-h7h5woycbnm6c1zzy-0hu2vwpwgmpk2g
添加到网站上的“secret”中(记住选中“secret base64 encoded”),最终的JWT如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEzMzciLCJ1c2VybmFtZSI6ImJpem9uZSIsImlhdCI6MTU5NDIwOTYwMCwicm9sZSI6ImFkbWluIn0.E1R1nWNsO-H7h5WoYCBnm6c1zZy-0hu2VwpWGMVPK2g
正如网站显示的结果,JWT成功地通过了验证。
所以,最好只使用一种加密方式,防止类似的安全漏洞。
9. 选择知名且可靠的JWT库
网上有不少对JWT库的选型对比,由于这种建议具有时效性,本文不做推荐。
参考文档:
https://github.com/auth0/java-jwt
https://jwt.io/
https://docs.aws.amazon.com/zh_cn/acm/latest/userguide/import-certificate-format.html
https://github.com/jpadilla/pyjwt
https://github.com/brendan-rius/c-jwt-cracker
https://skysec.top/2018/05/19/2018CUMTCTF-Final-Web/#Pastebin/
https://cyberpolygon.com/materials/security-of-json-web-tokens-jwt/