01 JWT簡介
JWT全稱為JSON Web Token,將json對象作為載體來傳輸信息,一般被用在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便于從資源服務器獲取資源,也可以增加一些額外的業務邏輯所必須聲明信息,該token可被直接用于認證,也可用作加密。

JSON Web令牌(JWT)是一種標準化的格式,用于在系統之間發送經過加密簽名的JSON數據。理論上JWT可以包含任何類型的數據,但最常用于進行身份認證、會話處理和訪問控制。與傳統的會話令牌不同,服務器需要的所有數據都存儲在JWT本身的客戶端。這使得JWT成為高度分布式網站的熱門選擇,在這些網站中,用戶需要與多個后端服務器進行無縫交互。
1.1 JWT格式
JWT由3部分組成:頭部、載荷和簽名。這些部分之間用點號隔開,如下所示:
eyJraWQiOiI5MTM2ZGRiMy1jYjBhLTRhMTktYTA3ZS1lYWRmNWE0NGM4YjUiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJwb3J0c3dpZ2dlciIsImV4cCI6MTY0ODAzNzE2NCwibmFtZSI6IkNhcmxvcyBNb250b3lhIiwic3ViIjoiY2FybG9zIiwicm9sZSI6ImJsb2dfYXV0aG9yIiwiZW1haWwiOiJjYXJsb3NAY2FybG9zLW1vbnRveWEubmV0IiwiaWF0IjoxNTE2MjM5MDIyfQ.SYZBPIBg2CRjXAJ8vCER0LA_ENjII1JakvNQoP-Hw6GG1zfl4JyngsZReIfqRvIAEi5L4HV0q7_9qGhQZvy9ZdxEJbwTxRs_6Lb-fZTDpW6lKYNdMyjw45_alSCZ1fypsMWz_2mTpQzil0lOtps5Ei_z7mM7M8gCwe_AGpI53JxduQOaB5HkT5gVrv9cKu9CsW5MS6ZbqYXpGyOG5ehoxqm8DL5tFYaW3lB50ELxi0KsuTKEbD0t5BCl0aCR2MBJWAbN-xeLwEenaqBiwPVvKixYleeDQiBEIylFdNNIMviKRgXiYuAvMziVPbwSgkZVHeEdF5MQP1Oe2Spac-6IfA
JWT的頭部和載荷部分其實就是用base64url編碼的JSON對象。其中頭部包含關于令牌本身的元數據,而載荷包含關于用戶的實際“聲明”。可以對上述令牌的載荷進行解碼,結果如下:
{
"iss": "portswigger",
"exp": 1648037164,
"name": "Carlos Montoya",
"sub": "carlos",
"role": "blog_author",
"email": "carlos@carlos-montoya.net",
"iat": 1516239022
}
大多數情況下,任何有權訪問令牌的人都可以輕松地讀取或修改這些數據。因此任何基于JWT機制的安全性都嚴重依賴于密碼簽名。
1.2 JWT簽名
頒發令牌的服務器通常通過對頭部和載荷計算哈希值來生成簽名。有時會對產生的哈希值進行加密處理。但是無論哪種方式,這個過程都涉及一個秘密密鑰。如果密鑰未知,就無法為給定的頭部和載荷生成有效的簽名。這實際上就為服務器提供了一種驗證令牌頒發以來數據是否被篡改的機制,因為任何對頭部或載荷部分的修改都會使簽名不再匹配。
02 JWT、JWS與JWE
JWT規范的約束實際上是非常有限的。因為它只定義了將信息(“聲明”)表示為可以在雙方之間傳輸的JSON對象的格式。在實際使用中,JWT并沒有真正作為一個獨立的實體使用。JWT規范由JSON Web簽名(JWS)和JSON Web加密(JWE)規范組成,共同定義了實際實現JWT的具體方法。

也就是說,JWT通常是指JWS或JWE令牌,JWE同理,只是令牌的實際內容是經過加密的。
2.1 JWT攻擊
JWT攻擊是指用戶向服務器發送修改過的JWT,執行惡意操作。通常情況下,比較常見的是冒充已經通過身份認證的用戶,繞過認證和訪問控制。如果攻擊者能夠用任意值創建自己的有效令牌,他們就能夠提升自己的權限或冒充其他用戶,從而完全接管這些用戶的賬戶。
2.2 攻擊原理
JWT漏洞通常是由于應用程序本身對JWT的處理有缺陷而產生的。與JWT有關的各種規范在設計上相對靈活,例如允許網站開發人員自行決定許多實現細節。但這也可能會引發一些安全問題。這些實現缺陷通常意味著JWT的簽名沒有被正確驗證。即使嚴格檢查簽名,攻擊者也可以通過令牌的載荷篡改傳遞給應用程序的值。簽名是否可以信任在很大程度上也取決于服務器的秘鑰是否仍然是安全的。如果這個密鑰被泄露或者破解,那么攻擊者就可以為任意令牌生成有效的簽名,從而攻陷整個機制。
2.3 JWT簽名驗證
根據設計,一般情況下服務器不存儲任何關于頒發的JWT的信息。因此每個令牌都是一個完全獨立的實體。雖然這樣做有許多優點,但也導致了一個隱患,即服務器實際上不知道關于令牌的原始內容,甚至不知道原始簽名是什么。正因如此,如果服務器沒有正確效驗簽名,也就無法阻止攻擊者對令牌的其他部分任意篡改。
例如,一個包含以下聲明的JWT如下:
{
"username": "carlos",
"isAdmin": false
}
如果服務器是根據username來識別會話,那么攻擊者就能夠通過修改用戶名來冒充其他已登錄的用戶。同樣,如果isAdmin值被用于訪問控制,攻擊者也可以提通過篡改這個值來實現提權。
03 JWT相關漏洞類型
3.1 接受任意簽名
JWT庫通常會提供一個驗證令牌的方法,同時提供對其解碼的方法。例如,對于Node.js庫jsonwebtoken來說,這兩個方法分別是verify()和decode()。
但有時開發人員會混淆這兩個方法,只把傳入的令牌傳給decode()方法。這實際上意味著應用程序根本就沒有對簽名進行驗證。
3.2 接受未簽名的令牌
JWT頭部還包含一個alg參數。該參數的作用就是告訴服務器對令牌進行簽名時使用的是哪種算法,換句話說就是在驗證簽名時需要使用哪種算法。
{
"alg": "HS256",
"typ": "JWT"
}
但本質上這種方法存在安全隱患,因為服務器只能隱式地信任提供令牌的用戶的輸入(注意,這些輸入受控于該用戶),而該令牌根本沒有被驗證過。換句話說攻擊者可以直接影響服務器檢查令牌是否值得信任的方式。
JWT既可以使用一系列不同的算法進行簽名,也可以不簽名。在這種情況下,alg參數被設置為None,表示所謂的 "不安全的JWT"。由于這種情況明顯存在安全問題,因此服務器通常會拒絕沒有簽名的令牌。但由于這種過濾依賴于字符串解析,所以攻擊者可以使用混淆技術繞過這些過濾器,如混合大寫和非預期的編碼等。(需要注意的是,即使令牌是未簽名的,載荷部分也必須以點號結尾。)
3.3 暴力破解密鑰
某些簽名算法,例如HS256(HMAC + SHA-256),會像密碼一樣使用一個任意的、獨立的字符串作為秘密密鑰。需要保證這個秘鑰不被輕易猜到或暴力破解,否則攻擊者能以任意的頭部和載荷值來創建JWT,然后用密鑰重新給令牌簽名。
在實現JWT應用時,開發人員有時會忘記改變默認或占位的密碼,甚至可能復制和粘貼在網上找到的代碼片段,然后忘記改變作為示例提供的硬編碼的密碼。在這種情況下,攻擊者使用流行的密碼本,可以輕松對服務器的登陸憑據進行暴力破解。
3.4 JWT頭部參數注入
根據JWS規范,只有頭部參數alg是必需的。然而實際中JWT頭部(也稱為JOSE頭部)通常包含其他幾個參數。以下是攻擊者特別感興趣的參數:
jwk(JSON Web Key):提供一個表示密鑰的嵌入式JSON對象。
jku(JSON Web Key Set URL):提供一個URL,服務器可以從中獲取一組包含正確密鑰的密鑰。
kid(Key ID):提供一個ID,在有多個密鑰可供選擇的情況下,服務器可以使用該ID來識別正確的密鑰。根據密鑰的格式可能還有一個匹配的kid參數。
如上,這些用戶可控制的參數用于告訴接收方服務器在驗證簽名時使用哪些密鑰。
3.5 jwk參數注入
JSON Web簽名(JWS)規范描述了一個可選的jwk頭部參數,服務器可以用它將其公鑰直接嵌入JWK格式的令牌本身。JWK(JSON Web密鑰)是一種標準化的格式,用于將密鑰表示為JSON對象。
JWT頭部示例如下:
{
"kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
"typ": "JWT",
"alg": "RS256",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"kid": "ed2Nf8sb-sD6ng0-scs5390g-fFD8sfxG",
"n": "yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9m"
}
}
理想情況下,服務器應該只使用有限的公鑰白名單來驗證JWT簽名。然而配置錯誤的服務器有時會使用jwk參數中嵌入的任何密鑰來驗證簽名。
因此攻擊者可以利用這種行為,用自己的RSA私鑰對修改過的JWT進行簽名,然后在jwk頭部中嵌入對應的公鑰。
雖然也可以在Burp中手動添加或修改jwk參數,但JWT編輯器擴展提供了一個非常方便的功能,用于幫助研究人員測試這個漏洞:
在加載該擴展后,在Burp的主選項卡欄中,轉到JWT Editor Keys選項卡。
創建一個新的RSA密鑰。
向Burp Repeater發送一個包含JWT的請求。
在消息編輯器中,切換到擴展生成的JSON Web Token選項卡,并以你喜歡的方式修改令牌的載荷。
點擊Attack按鈕,然后選擇Embedded JWK。當收到提示時,選擇新生成的RSA密鑰。
發送請求,測試服務器的響應情況。
有些服務器并不會直接使用jwk頭部參數來嵌入公鑰,而是使用jku(JWK Set URL)頭部參數來引用一個包含密鑰的JWK Set。當驗證簽名時,服務器會從這個URL中獲取相關的密鑰。
實際上所謂JWK Set就是一個JSON對象,其中包含一組表示密鑰的JWK,例如:
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"kid": "75d0ef47-af89-47a9-9061-7c02a610d5ab",
"n": "o-yy1wpYmffgXBxhAUJzHHocCuJolwDqql75ZWuCQ_cb33K2vh9mk6GPM9gNN4Y_qTVX67WhsN3JvaFYw-fhvsWQ"
},
{
"kty": "RSA",
"e": "AQAB",
"kid": "d8fDFo-fS9-faS14a9-ASf99sa-7c1Ad5abA",
"n": "fc3f-yy1wpYmffgXBxhAUJzHql79gNNQ_cb33HocCuJolwDqmk6GPM4Y_qTVX67WhsN3JvaFYw-dfg6DH-asAScw"
}
]
}
像這樣的JWK集有時會通過一個標準的端點對外公開,如/.known/jwks.json,攻擊者有時可以利用URL解析的差異來繞過這種過濾機制。
3.7 kid參數注入
服務器可能會使用多個加密密鑰來為不同類型的數據進行簽名。出于這個原因,JWT的頭部可能包含一個kid(密鑰ID)參數,用來幫助服務器識別在驗證簽名時要使用的密鑰。
驗證密鑰通常被存儲為JWK Set。在這種情況下,服務器可以直接尋找與令牌具有相同kid參數的JWK。然而JWS規范并沒有為這個ID定義具體的結構,它只是開發人員任意選擇的一個字符串。例如可以使用kid參數來指向數據庫中的一個特定條目,甚至文件名稱。
如果該參數受到目錄遍歷的影響,攻擊者就有可能迫使服務器使用其文件系統中的任意文件作為驗證密鑰。
{
"kid": "../../path/to/file",
"typ": "JWT",
"alg": "HS256",
"k": "asGsADas3421-dfh9DGN-AFDFDbasfd8-anfjkvc"
}
如果服務器也支持使用對稱算法為JWT簽名,攻擊者可以將kid參數指向一個可預測的靜態文件,然后用一個與該文件內容相匹配的秘密來給JWT簽名,簡單的方法之一是使用/dev/null。該文件是一個空文件,讀取時將返回null,可以用一個Base64編碼的null字節來給令牌簽名將得到一個有效的簽名。
如果服務器將其驗證密鑰存儲在數據庫中,kid頭部參數也是一個潛在的SQL注入攻擊的載體。
3.8其他JWT頭部參數
cty(內容類型):用來聲明JWT載荷中內容的媒體類型。通常情況下會省略該參數,但底層解析庫可能還是支持它。如果攻擊者已經找到了繞過簽名驗證的方法,可能會嘗試注入cty參數,將內容類型改為text/xml或application/x-java-serialized-object,這有可能為XXE和反序列化攻擊提供新的向量。
x5c(X.509證書鏈):用于傳遞用于對JWT進行數字簽名的X.509公鑰證書或證書鏈。這個頭部參數可用于注入自簽證書,類似于上面討論的jwk頭部注入攻擊。由于X.509格式及其擴展的復雜性,解析這些證書也很可能會引入漏洞。
3.9 JWT算法混淆
即使服務器使用了攻擊者無法破解的強密碼,對方仍然可以使用開發人員沒有預料到的算法簽名令牌來偽造有效的JWT,這就是所謂的算法混淆攻擊。
04 防御JWT攻擊
使用最新的庫來處理JWT,并確保開發人員對相關安全問題足夠了解。現代代碼庫的使用降低了在代碼實現中引入安全漏洞的可能性,但由于相關規范固有的靈活性,也并非萬無一失。
確保對收到的任何JWT進行嚴格的簽名驗證,并考慮邊緣情況,如使用非預期的算法簽名的JWT。
為jku頭部提供允許主機白名單,并嚴格執行。
確保不會受到kid頭部參數路徑穿越或SQL注入的影響。
05 參考鏈接
https://portswigger.net/web-security/jwt
https://blog.csdn.net/cdyunaq/article/details/122561096
- 關鍵詞標簽:
- 檢測與防護能力 JWT攻擊類型