全文共4545字,預計學習時長13分鐘
想像一下,你正安逸地躺在家裡刷公眾號,突然,你的手機嘀嘀嘀,一筆又一筆巨款從你的卡裡不翼而飛,急得你驚起直跺腳,咋回事啊?!
突然你想到了剛剛在「讀芯術」看到的有關「JSON Web Token」的文章。
你恍然大悟,哦~原來是有人偽造token來冒充自己登錄銀行網站取款呀,原來如此。
弄明白,安心了……個鬼,趕緊報警啊。
接下來,小芯就帶你看看這篇JWT的文章,共同探討使用JWT(和一般的基於籤名的token)的安全隱患,以及攻擊者利用這些隱患繞過訪問控制的方法。
是JSONWeb Token?
有問必答:JSON Web Token是一種在商業應用程式中廣泛使用的訪問token。它基於JSON格式,並包含一個籤名以確保token的完整性。
JWT的運行機制是什麼?
JWT由三部分構成:頭部(header),載荷(payload)和籤名(signature)。
頭部
JWT的頭部聲明了籤名算法。下面這段代碼就是一個JSON blob對象的base64url編碼字符串的頭部:
{
"alg" :"HS256",
"typ" : "JWT"
}base64url encoded string: eyBhbGcgOiBIUzI1NiwgdHlwIDogSldUIH0K
(Base64url編碼是base64編碼在URL格式上的修改版本。它同base64十分類似,但它使用了不同的符號字符並省略了填充。)
JWT最常用的算法是HMAC(Hash-based Message Authentication Code,哈希運算消息認證碼)和RSA(一種非對稱加密算法)。
載荷
載荷包含了用於訪問控制的信息。該部分在用於生成token前也使用base64url進行編碼。
{
"user_name" :"admin",
}base64url encoded string: eyB1c2VyX25hbWUgOiBhZG1pbiB9Cg
籤名
籤名用於驗證token是否被篡改。其計算方法是:先將編碼後的頭部與載荷串聯在一起,再使用頭部聲明的算法來籤名。
signature= HMAC-SHA256(base64urlEncode(header) + '.' + base64urlEncode(payload),secret_key)// Let's just say the value of secret_key is "key".->signature function returns 4Hb/6ibbViPOzq9SJflsNGPWSk6B8F6EqVrkNjpXh7M
這個token使用密鑰為「key」的HS256算法為字符串「eyBhbGcgOiBIUzI1NiwgdHlwIDogSldUIH0K.eyB1c2VyX25hbWUgOiBhZG1pbiB9Cg」籤名。編碼後得到字符串「4Hb /6ibbViPOzq9SJflsNGPWSk6B8F6EqVrkNjpXh7M」。
完整的token
將頭部、載荷和籤名用「.」連接起來可以構成一個完整的token。如下所示:
eyBhbGcgOiBIUzI1NiwgdHlwIDogSldUIH0K.
eyB1c2VyX25hbWUgOiBhZG1pbiB9Cg.
4Hb/ 6ibbViPOzq9SJflsNGPWSk6B8F6EqVrkNjpXh7M
如何繞過JWT訪問控制
正常情況下,由於無法篡改JWT載荷的數據,它能提供一種安全途徑來標識用戶(由於用戶無權訪問密鑰,她便無法在token上簽名。)
然而,若JWT出現異常,攻擊者可以通過多種方式繞過其安全機制來偽造token。
更改算法類型
攻擊者偽造token的方法之一是篡改頭部的alg欄位。如果應用程式不限制JWT使用的算法類型,攻擊者可能會通過指定算法破壞token的安全性。
1. 不使用加密算法
JWT支持將算法設定為「None」。如果「alg」欄位設為「 None」,那麼籤名會被置空,這樣任何token都是有效的。例如:
eyAiYWxnIiA6ICJOb25lIiwgInR5cCIgOiAiSldUIiB9Cg.
eyB1c2VyX25hbWUgOiBhZG1pbiB9Cg.
這個token只是下面這兩個blob對象用base64url編碼後得出的值,它並沒有提供籤名。
{
「 alg」:「None」,
「typ」:「 JWT」
}{
「user」:「admin」
}
設定該功能的最初目的是為了方便調試。但是,若不在生產環境中關閉該功能,攻擊者可以通過將alg欄位設置為「None」來偽造他們想要的任何token,接著便可以使用偽造的token冒充任意用戶登陸網站。
2. HMAC算法
JWT兩種最常用的算法是HMAC和RSA。HMAC用同一個密鑰對token進行籤名和認證。而RSA需要兩個密鑰,先用私鑰加密創建token,然後使用其對應的公鑰來認證。
HMAC->
signed with a key, verified with the same keyRSA ->
signed with aprivate key,
verified with the corresponding public key
注意,要保護好HMAC的密鑰和RSA私鑰信息,因為它們都是給token籤名的。
現在,假設有一個用RSA籤名的應用程式。這些token用對公眾保密的私鑰A籤名。再使用任何人都能獲取的公鑰B來驗證。只要不改變該token的算法,這個token就是有效的。
Token signed with key A -> Token verified with key B (RSA scenario)
但是,如果攻擊者把算法改為HMAC,他便能利用原RSA下的公鑰B來籤名偽造token,而這種做法生成的token同樣有效。
這是因為一開始用RSA給token籤名時,應用程式會使用公鑰B來認證用戶身份。當算法更改為HMAC時,仍使用RSA的公鑰B認證,但這次,公鑰B也可以用於籤名了(因為HMAC是對稱性加密算法)。
Token signed with key B -> Token verified with key B (HMAC scenario)
提供無效籤名
到達應用程式後,token籤名可能永遠不會被認證。這樣,攻擊者便可以通過提供無效籤名簡單地繞過安全機制。
暴力破解密鑰
攻擊者也可以暴力破解用於籤名的JWT密鑰。
第一步,攻擊者要掌握一些信息:用於token籤名的算法、加密後的載荷及最終得到的加密字符串。若密鑰不夠複雜,攻擊者可能可以輕易地暴力破解它。
洩漏密鑰
假設攻擊者無法暴力破解密鑰,他可能會想辦法讓密鑰洩露出來。若攻擊者利用文檔漏洞(如目錄遍歷,XXE和SSRF漏洞)讀取儲存了密鑰值的文檔,他便能竊取密鑰從而給任意token籤名。
操縱KID
KID指的是「密鑰序號」(Key ID)。它是JWT頭部的一個可選欄位,開發人員可以用它標識認證token的某一密鑰。KID參數的正確用法如下所示:
{
"alg": "HS256",
"typ": "JWT",
"kid": "1" // use key number 1 to verify the token
}
由於該欄位由用戶控制,因此攻擊者可能會操縱它,這會引發危險後果。
1. 目錄遍歷
由於KID通常用於從文件系統中檢索密鑰文件,因此,如果在使用前不清理KID,文件系統可能會遭到目錄遍歷攻擊。這樣,攻擊者便能夠在文件系統中指定任意文件作為認證的密鑰。
「kid」: 「../../public/css/main.css」// use the publicly available file main.css to verify the token
例如,攻擊者可以強行設定應用程式使用公開可用文件作為密鑰,並用該文件給HMAC加密的token籤名。
2. SQL注入
KID也可以用於在資料庫中檢索密鑰。在該情況下,攻擊者很可能會利用SQL注入來繞過JWT安全機制。
如果可以在KID參數上進行SQL注入,攻擊者便能使用該注入返回任意值。
「kid」:"aaaaaaa' UNION SELECT 'key';--"// use the string "key" toverify the token
上面這個注入會導致應用程式返回字符串「 key」(因為資料庫中不存在名為「 aaaaaaa」的密鑰)。然後使用字符串「 key」作為密鑰來認證token。
操縱頭部參數
除KID外,JWT標準還能讓開發人員通過URL指定密鑰。
1. JKU頭部參數
JKU全稱是「JWKSet URL」,它是頭部的一個可選欄位,用於指定連結到一組加密token密鑰的URL。若允許使用該欄位且不設置限定條件,攻擊者就能託管自己的密鑰文件,並指定應用程式,用它來認證token。
jku URL -> file containing JWK set -> JWK used to verify the token
2. JWK頭部參數
頭部可選參數JWK(JSON Web Key)使得攻擊者能將認證的密鑰直接嵌入token中。
3. 操縱X5U,X5C URL
同JKU或JWK頭部類似,X5U和X5C頭部參數讓攻擊者能夠指定認證的公鑰證書或證書鏈。X5U以URI形式指定信息,而X5C允許將證書值嵌入token中。
其他安全隱患
若JWT安全機制不能正常運行,還會產生其他安全隱患。這些問題不常見,但絕對值得關注:
信息洩漏
由於JWT用於訪問控制,因此它通常包含用戶的相關信息。
如果不加密token,則任何人都能通過base64解碼並讀取token上的載荷信息。因此,如果token包含敏感信息,它便可能成為信息洩漏的來源。正常的token籤名僅保障數據完整性,而非機密性。
命令注入
有時,將KID參數直接傳到不安全的文件讀取操作可能會讓一些命令注入代碼流中。
一些函數就能給此類型攻擊可乘之機,比如Ruby open()。攻擊者只需在輸入的KID文件名後面添加命令,即可執行系統命令:
「key_file」 | whoami;
類似情況還有很多,這只是其中一個例子。理論上,每當應用程式將未審查的頭部文件參數傳遞給類似system(),exec()的函數時,都會產生此種漏洞。
本質上,JWT僅僅是另一種用戶輸入的形式。程序開發者應謹慎使用JWT並對其進行嚴格審查。
免責聲明:這篇文章是為了引起人們對JWT安全漏洞的關注,並幫助開發人員認識到常見的陷阱。請不要使用這些信息來攻擊網站或冒充他人身份哦。
在您沒有測試權限的系統上嘗試此操作是非法的。如果您發現了安全漏洞,請務必向供應商披露,以營造一個更為安全的網際網路環境。大家都是好孩子~
留言點讚關注
我們一起分享AI學習與發展的乾貨
如轉載,請後臺留言,遵守轉載規範