一文理解JWT鑒權(quán)登錄的安全加固
有關(guān)JWT的基礎(chǔ)知識(shí),可以查看之前的博客: 快速了解會(huì)話管理三劍客cookie、session和JWT
在之前的博客《一文理解JWT在鑒權(quán)登錄的應(yīng)用》介紹了JWT在鑒權(quán)登錄中的使用。但是不恰當(dāng)?shù)厥褂?JWT 可能會(huì)對應(yīng)用程序安全產(chǎn)生負(fù)面影響。
本文將針對JWT在鑒權(quán)登錄業(yè)務(wù)場景下的安全進(jìn)行講解。
JWT使用時(shí)的安全建議
1. 切勿在令牌中傳輸用戶的敏感數(shù)據(jù)
由于JWT的載荷部分是可以被明文獲取的,因此,如果有效載荷中存在敏感信息的話,就會(huì)發(fā)生信息泄露。
2. 傳輸令牌時(shí)一定要使用安全連接
理由同上。
3. 使用“刷新令牌”機(jī)制
由于JWT是公開傳輸?shù)?,獲取了令牌的黑客能夠繼續(xù)使用該JWT訪問應(yīng)用程序,所以使用最好雙JWT機(jī)制降低安全風(fēng)險(xiǎn)。使用方法在《一文理解JWT在鑒權(quán)登錄的應(yīng)用》有詳細(xì)講解。
4. 增加JWT的業(yè)務(wù)參數(shù),用于校驗(yàn)
可以在Payload中增加一些業(yè)務(wù)上的字段,用于校驗(yàn)當(dāng)前JWT是否被濫用。例如增加設(shè)備號(hào),用戶uuid、權(quán)限的信息,可以與上下文的信息進(jìn)行對照,提高爬蟲的門檻與接口安全性。
5. 始終驗(yàn)證并過濾從用戶接收的數(shù)據(jù)
kid是JWT header中的一個(gè)可選參數(shù),它用于指定加密算法的密鑰。因?yàn)樵搮?shù)可以由用戶輸入,系統(tǒng)并不知道用戶想要讀取的到底是不是密鑰文件。如果在沒有對參數(shù)進(jìn)行過濾的前提下,攻擊者是可以讀取到系統(tǒng)的任意文件的、造成SQL注入或命令注入等漏洞。
{
"alg" : "HS256",
"typ" : "jwt",
"kid" : "/etc/passwd"
}
{
"alg" : "HS256",
"typ" : "jwt",
"kid" : "key11111111' || union select 'secretkey' -- "
}
所以,驗(yàn)證并過濾從用戶接收的數(shù)據(jù)是必要的。
6. 提高對稱加密的秘鑰強(qiáng)度
如果加密的密鑰強(qiáng)度較弱的話,攻擊者可以直接通過蠻力攻擊方式來破解密鑰,可以使用PyJWT、John Ripper或c-jwt-cracker進(jìn)行破解測試。PyJWT庫具體地址看參考文檔4。c-jwt-cracker庫集體地址看參考文檔5。
秘鑰不定期的變化。這對用戶來說不太方便,因?yàn)樗麄儗⒉坏貌辉俅瓮ㄟ^身份驗(yàn)證,但這有助于避免安全問題的發(fā)生。
7. 服務(wù)端增加授權(quán)簽名算法的白名單
簽名算法可以確保JWT在傳輸過程中不會(huì)被惡意用戶所篡改,但頭部中的alg字段卻可以改為None,即不使用簽名算法。這樣的話,當(dāng)signature設(shè)置為空時(shí),后端將不執(zhí)行簽名驗(yàn)證。
將alg字段改為none后,系統(tǒng)就會(huì)生成這樣形式的JWT,然后將其提交給服務(wù)器并通過校驗(yàn):
base64UrlEncode(header) + "." + base64UrlEncode(payload)
所以,有必要在服務(wù)端增加已授權(quán)算法的白名單,并刪除所有與服務(wù)端上授權(quán)算法不同的簽名算法的JWT。
8. 最好只使用一個(gè)簽名算法
在使用非對稱算法進(jìn)行令牌簽名的情況下,簽名應(yīng)使用私鑰,而簽名驗(yàn)證應(yīng)使用公鑰。由于使用JWT的某些庫包含邏輯錯(cuò)誤——當(dāng)收到用對稱算法簽名的令牌時(shí),將使用公鑰作為驗(yàn)證簽名的secret。由于公鑰并不是秘密數(shù)據(jù),因此黑客可能會(huì)獲得公共服務(wù)密鑰并用于簽署自己的令牌。
所以,當(dāng)網(wǎng)站采用非對稱加密驗(yàn)證,且不對簽名算法進(jìn)行限制的話,存在這樣的漏洞:
通過一定手段,獲取到非對稱加密的公鑰,將alg字段改為對稱加密算法。
使用公鑰對JWT進(jìn)行簽名,發(fā)送給服務(wù)端。
服務(wù)端會(huì)將對稱加密的公鑰作為驗(yàn)證簽名的秘鑰,使用對稱加密算法對接收的JWT進(jìn)行驗(yàn)證。
為了說明這個(gè)問題,以下實(shí)驗(yàn)部分節(jié)選自參考文檔7的部分內(nèi)容:
使用非對稱加密算法RS256,新建一個(gè)JWT如下:
header:
{
"alg": "RS256",
"typ": "JWT"
}
payload:
{
"id": "1337",
"username": "bizone",
"iat": 1594209600,
"role": "user"
}
轉(zhuǎn)為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-----
為驗(yàn)證JWT有效性,使用參考文檔2的網(wǎng)站如下圖:

header:
{
"typ": "JWT",
"alg": "HS256"
}
payload:
{
"id": "1337",
"username": "bizone",
"iat": 1594209600,
"role": "admin"
}
轉(zhuǎn)化為JWT的頭部和載荷部分如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEzMzciLCJ1c2VybmFtZSI6ImJpem9uZSIsImlhdCI6MTU5NDIwOTYwMCwicm9sZSI6ImFkbWluIn0
現(xiàn)在只需要使用公鑰讀取簽名。
首先,把密鑰轉(zhuǎn)移到十六進(jìn)制表示法,如下圖。

然后使用openssl生成一個(gè)簽名,如下圖。

將值e1r1nwnso-h7h5woycbnm6c1zzy-0hu2vwpwgmpk2g添加到網(wǎng)站上的“secret”中(記住選中“secret base64 encoded”),最終的JWT如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEzMzciLCJ1c2VybmFtZSI6ImJpem9uZSIsImlhdCI6MTU5NDIwOTYwMCwicm9sZSI6ImFkbWluIn0.E1R1nWNsO-H7h5WoYCBnm6c1zZy-0hu2VwpWGMVPK2g
正如網(wǎng)站顯示的結(jié)果,JWT成功地通過了驗(yàn)證。

所以,最好只使用一種加密方式,防止類似的安全漏洞。
9. 選擇知名且可靠的JWT庫
網(wǎng)上有不少對JWT庫的選型對比,由于這種建議具有時(shí)效性,本文不做推薦。
參考文檔:
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/
