JWT的全稱是Json Web Token。它遵循JSON格式,將用戶信息加密到token里,服務器不保存任何用戶信息,只保存密鑰信息,通過使用特定加密算法驗證token,通過token驗證用戶身份。基於token的身份驗證可以替代傳統的cookie+session身份驗證方法。
jwt由三個部分組成:header.payload.signature
JWT原理
0x01 JWT認證流程

在項目開發中,一般會按照上圖所示的過程進行認證,即:用戶登錄成功之后,服務端給用戶瀏覽器返回一個token,以后用戶瀏覽器要攜帶token再去向服務端發送請求,服務端校驗token的合法性,合法則給用戶看數據,否則,返回一些錯誤信息。
0x02 傳統token方式和jwt在認證方面有什么差異?
傳統token方式:
用戶登錄成功后,服務端生成一個隨機token給用戶,並且在服務端(數據庫或緩存)中保存一份token,以后用戶再來訪問時需攜帶token,服務端接收到token之后,去數據庫或緩存中進行校驗token的是否超時、是否合法。
jwt方式:
用戶登錄成功后,服務端通過jwt生成一個隨機token給用戶(服務端無需保留token),以后用戶再來訪問時需攜帶token,服務端接收到token之后,通過jwt對token進行校驗是否超時、是否合法。
0x03 JWT原理
jwt的生成token格式如下,即:由 . 連接的三段字符串組成。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
生成規則如下:
- 第一段HEADER部分,固定包含算法和token類型,對此json進行base64url加密,這就是token的第一段。
{
"alg": "HS256",
"typ": "JWT"
}
- 第二段PAYLOAD部分,包含一些數據,對此json進行base64url加密,這就是token的第二段。
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
...
}
- 第三段SIGNATURE部分,把前兩段的base密文通過
.拼接起來,然后對其進行HS256加密,再然后對hs256密文進行base64url加密,最終得到token的第三段。
base64url(
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
your-256-bit-secret (秘鑰加鹽)
)
)
最后將三段字符串通過 .拼接起來就生成了jwt的token。
注意:base64url加密是先做base64加密,然后再將 - 替代 + 及 _ 替代 / 。
代碼實現參考:https://pythonav.com/wiki/detail/6/67/
JWT攻擊方式
加密算法
0x01 空加密算法
JWT支持使用空加密算法,可以在header中指定alg為None
這樣的話,只要把signature設置為空(即不添加signature字段),提交到服務器,任何token都可以通過服務器的驗證。舉個例子,使用以下的字段
{
"alg" : "None",
"typ" : "jwt"
}
{
"user" : "Admin"
}
生成的完整token為ew0KCSJhbGciIDogIk5vbmUiLA0KCSJ0eXAiIDogImp3dCINCn0.ew0KCSJ1c2VyIiA6ICJBZG1pbiINCn0
(header+'.'+payload,去掉了'.'+signature字段)
空加密算法的設計初衷是用於調試的,但是如果某天開發人員腦闊瓦特了,在生產環境中開啟了空加密算法,缺少簽名算法,jwt保證信息不被篡改的功能就失效了。攻擊者只需要把alg字段設置為None,就可以在payload中構造身份信息,偽造用戶身份。
0x02 修改RSA加密算法為HMAC
JWT中最常用的兩種算法為HMAC和RSA。
HMAC是密鑰相關的哈希運算消息認證碼(Hash-based Message Authentication Code)的縮寫,它是一種對稱加密算法,使用相同的密鑰對傳輸信息進行加解密。
RSA則是一種非對稱加密算法,使用私鑰加密明文,公鑰解密密文。
在HMAC和RSA算法中,都是使用私鑰對signature字段進行簽名,只有拿到了加密時使用的私鑰,才有可能偽造token。
現在我們假設有這樣一種情況,一個Web應用,在JWT傳輸過程中使用RSA算法,密鑰pem對JWT token進行簽名,公鑰pub對簽名進行驗證。
{
"alg" : "RS256",
"typ" : "jwt"
}
通常情況下密鑰pem是無法獲取到的,但是公鑰pub卻可以很容易通過某些途徑讀取到,這時,將JWT的加密算法修改為HMAC,即
{
"alg" : "HS256",
"typ" : "jwt"
}
同時使用獲取到的公鑰pub作為算法的密鑰,對token進行簽名,發送到服務器端。
服務器端會將RSA的公鑰(pub)視為當前算法(HMAC)的密鑰,使用HS256算法對接收到的簽名進行驗證。
參考2018CUMTCTF-Final-Web Paterbin:https://skysec.top/2018/05/19/2018CUMTCTF-Final-Web/#Pastebin/
爆破密鑰
俗話說,有密碼驗證的地方,就有會爆破。
不過對 JWT 的密鑰爆破需要在一定的前提下進行:
- 知悉JWT使用的加密算法
- 一段有效的、已簽名的token
- 簽名用的密鑰不復雜(弱密鑰)
所以其實JWT 密鑰爆破的局限性很大。
相關工具:c-jwt-cracker
以下是幾個使用示例

可以看到簡單的字母數字組合都是可以爆破的,但是密鑰位數稍微長一點或者更復雜一點的話,爆破時間就會需要很久。
修改KID參數
kid是jwt header中的一個可選參數,全稱是key ID,它用於指定加密算法的密鑰
{
"alg" : "HS256",
"typ" : "jwt",
"kid" : "/home/jwt/.ssh/pem"
}
因為該參數可以由用戶輸入,所以也可能造成一些安全問題。
0x01 任意文件讀取
kid參數用於讀取密鑰文件,但系統並不會知道用戶想要讀取的到底是不是密鑰文件,所以,如果在沒有對參數進行過濾的前提下,攻擊者是可以讀取到系統的任意文件的。
{
"alg" : "HS256",
"typ" : "jwt",
"kid" : "/etc/passwd"
}
0x02 SQL注入
kid也可以從數據庫中提取數據,這時候就有可能造成SQL注入攻擊,通過構造SQL語句來獲取數據或者是繞過signature的驗證
{
"alg" : "HS256",
"typ" : "jwt",
"kid" : "key11111111' || union select 'secretkey' -- "
}
0x03 命令注入
對kid參數過濾不嚴也可能會出現命令注入問題,但是利用條件比較苛刻。如果服務器后端使用的是Ruby,在讀取密鑰文件時使用了open函數,通過構造參數就可能造成命令注入。
"/path/to/key_file|whoami"
對於其他的語言,例如php,如果代碼中使用的是exec或者是system來讀取密鑰文件,那么同樣也可以造成命令注入,當然這個可能性就比較小了。
修改JKU/X5U參數
JKU的全稱是"JSON Web Key Set URL",用於指定一組用於驗證令牌的密鑰的URL。類似於kid,JKU也可以由用戶指定輸入數據,如果沒有經過嚴格過濾,就可以指定一組自定義的密鑰文件,並指定web應用使用該組密鑰來驗證token。
X5U則以URI的形式數允許攻擊者指定用於驗證令牌的公鑰證書或證書鏈,與JKU的攻擊利用方式類似。
信息泄露
JWT保證的是數據傳輸過程中的完整性而不是機密性。
由於payload是使用base64url編碼的,所以相當於明文傳輸,如果在payload中攜帶了敏感信息(如存放密鑰對的文件路徑),單獨對payload部分進行base64url解碼,就可以讀取到payload中攜帶的信息。
