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中攜帶的信息。